<?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[Metrics - 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[Metrics - 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/metrics</link>
    </image>
    <link>https://www.elastic.co/observability-labs/blog/category/metrics</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/category/metrics.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Tue, 15 Sep 2026 21:38:08 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Native OTLP metrics ingestion on Elastic Cloud Hosted]]></title>
    <description><![CDATA[Send an exponential OpenTelemetry histogram and Elasticsearch keeps the scale and buckets you sent. All four type and temporality combinations work now, and your SDK and Collector config stay exactly as they are.]]></description>
    <content:encoded><![CDATA[<p>Upgrade an Elastic Cloud Hosted (ECH) deployment to Elastic Stack 9.5.3 or later and Elasticsearch stores every OpenTelemetry histogram you send it. Cumulative histograms are included, and exponential buckets keep the scale and offset you sent, which covers all four combinations of histogram type and temporality. The <a href="https://www.elastic.co/docs/reference/opentelemetry/managed-inputs/managed-otlp-endpoint">Elastic Cloud Managed OTLP Endpoint</a> picks the ingestion path per deployment, so nothing changes in your SDK or Collector config. Older deployments keep working through the Elasticsearch bulk API. Four runnable <code>curl</code> examples follow, one per histogram case.</p>
<h2 id="whatistheelasticcloudmanagedotlpendpoint">What is the Elastic Cloud Managed OTLP Endpoint?</h2>
<p>Managed inputs are ingestion frontends that Elastic operates on your behalf.
You point a shipper at an Elastic-owned endpoint, authenticate with an API key, and Elastic takes care of receiving, buffering, and indexing the data into your deployment.
There is no gateway to size, patch, or monitor, and no backend credentials to distribute to edge agents.</p>
<p>The Managed OTLP Endpoint is the managed input for OpenTelemetry data.
It accepts standard OTLP over HTTP and gRPC from any OpenTelemetry SDK or Collector distribution, buffers it durably, and indexes it into your Elasticsearch deployment using the OpenTelemetry data model.
It is generally available on both Elastic Cloud Serverless and ECH.
For an overview across logs, traces, and metrics, read <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-ga-elastic-cloud-hosted">Now GA: Managed OTLP Endpoint on Elastic Cloud Hosted</a>.</p>
<p>For metrics, clients send OTLP/HTTP requests to the <code>/v1/metrics</code> signal path and authenticate with an Elasticsearch API key that has the <code>event:write</code> privilege for the <code>apm</code> application:</p>
<pre><code>POST https://&lt;managed-otlp-endpoint&gt;/v1/metrics
Authorization: ApiKey &lt;encoded-api-key&gt;
</code></pre>
<p>The <code>/v1/metrics</code> client-facing path never changes.
This post focuses on what happens after the request is accepted, during the last hop between the managed pipeline and Elasticsearch.</p>
<h2 id="twowaystoingestopentelemetrymetricsthebulkapiandthenativeotlpendpoint">Two ways to ingest OpenTelemetry metrics: the bulk API and the native OTLP endpoint</h2>
<p>Elasticsearch offers two ways to receive OpenTelemetry metrics.</p>
<p>The <strong>bulk API</strong> (<code>/_bulk</code>) is the general-purpose document ingestion API.
To use it for OTLP metrics, something upstream must convert each OTLP data point into a JSON document that follows the OpenTelemetry mapping mode, group the documents by target data stream, serialize them as newline-delimited JSON, and send them in bulk requests.
In the OpenTelemetry Collector ecosystem, this conversion is handled by the Elasticsearch exporter, and until recently, it was the only option.</p>
<p>The <strong>native OTLP endpoint</strong> (<code>/_otlp/v1/metrics</code>) accepts OTLP/HTTP Protobuf directly.
Elasticsearch decodes the payload, builds the documents itself, and indexes them into time series data streams.
Because Elasticsearch sees the original OTLP structure, it can perform these steps directly:</p>
<ul>
<li>It reads the aggregation temporality of every data point and stores it as a dimension</li>
<li>It hashes the shared resource attributes once for all data points in a resource</li>
<li>It maps exponential histograms to the <code>exponential_histogram</code> field type without an intermediate T-Digest conversion</li>
</ul>
<p>The following diagram shows the two lanes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt411743a98069ab16/6aa9324ea812980378e3d7e9/two-paths.png" alt="Clients send to a single entry point, /v1/metrics on the Managed OTLP Endpoint. Behind it the service forks by deployment version: the bulk API path converts OTLP into JSON documents for /_bulk, the native path forwards OTLP Protobuf to /_otlp/v1/metrics" /></p>
<p>The two paths differ in three ways that matter for users:</p>
<ul>
<li><strong>Work distribution.</strong> On the bulk path, the managed pipeline converts and serializes documents, then Elasticsearch parses them again. On the native path, the pipeline forwards batched Protobuf and Elasticsearch builds documents once.</li>
<li><strong>Temporality.</strong> The Elasticsearch exporter used by the managed bulk path does not support cumulative histograms and drops those points. The native path preserves temporality and accepts both delta and cumulative histograms.</li>
<li><strong>Histogram fidelity.</strong> The bulk path converts every histogram to a T-Digest style representation. The native path stores exponential histograms natively and converts explicit-boundary histograms into the same exponential representation.</li>
</ul>
<p>Do not confuse the two paths with the client-facing path.
Clients always send to <code>/v1/metrics</code> on the Managed OTLP Endpoint.
The service chooses between <code>/_bulk</code> and <code>/_otlp/v1/metrics</code> behind it.</p>
<h2 id="nativeopentelemetrymetricssupportbyelasticstackversion">Native OpenTelemetry metrics support by Elastic Stack version</h2>
<p>Native OTLP metrics support in Elasticsearch arrived in several steps.</p>
<ol>
<li><strong>Elastic Stack 9.2</strong> introduced the Elasticsearch OTLP/HTTP metrics endpoint as a technical preview.</li>
<li><strong>Elastic Stack 9.3</strong> added the <code>exponential_histogram</code> field type as a technical preview.</li>
<li><strong>Elastic Stack 9.4</strong> made <code>exponential_histogram</code> generally available and the default mapping for OTLP histograms received on the native endpoint.</li>
<li><strong>Elastic Stack 9.5</strong> added metric temporality support as a generally available feature. The native endpoint now stores the temporality of every data point, which is what makes cumulative histograms possible. The same release added OTLP/HTTP endpoints for logs and traces as a technical preview.</li>
<li><strong>Elastic Stack 9.5.3</strong> is the current minimum version for native OTLP metrics routing through the Managed OTLP Endpoint on ECH. The routing floor was raised from 9.2.0 because earlier versions can fail native OTLP requests when audit request-body logging processes Protobuf payloads. The 9.5.3 floor includes the required fixes.</li>
</ol>
<p>The Elasticsearch Prometheus remote write endpoint arrived in Elastic Stack 9.4 and follows the same capability-gating model, but it is out of scope for this post.</p>
<p>Keep two things apart when reading version numbers in this post.
The first four milestones are capabilities of Elasticsearch itself, available to anyone who calls <code>/_otlp/v1/metrics</code> directly.
The fifth is a routing decision of the Managed OTLP Endpoint, which is stricter than "the endpoint exists" because the managed service has to work reliably for every tenant.</p>
<h2 id="howelasticcloudhostedroutesopentelemetrymetrics">How Elastic Cloud Hosted routes OpenTelemetry metrics</h2>
<p>On Serverless, Elastic operates and upgrades the backend, so the Managed OTLP Endpoint always uses the native path and users never see a version matrix.</p>
<p>ECH is different.
Users pick and control their Elastic Stack version, and the Managed OTLP Endpoint supports deployments on any version from 9.0 onward.
A single multi-tenant service therefore receives metrics for deployments that have no native endpoint at all, deployments that have one but predate the fixes the service relies on, and deployments that are fully ready.
It cannot enable the native path unconditionally.</p>
<h3 id="howthemanagedotlpendpointroutesametricsrequest">How the Managed OTLP Endpoint routes a metrics request</h3>
<p>The following diagram shows what happens to a metrics request for an ECH deployment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf804b169dfac4995/6aa9326135eddc32b211d938/version-aware-routing.png" alt="Sequence of a metrics request through the Managed OTLP Endpoint: authenticate, resolve the target deployment and its Elasticsearch version, derive capability flags, buffer the request, then select the OTLP/HTTP exporter or the bulk API exporter at indexing time" /></p>
<ol>
<li>The endpoint authenticates the API key and resolves the target ECH deployment.</li>
<li>It looks up the deployment's Elasticsearch version from Elastic's own control plane, not from anything the client sends.</li>
<li>It derives a small set of capability flags for the target, such as "native OTLP metrics supported", and attaches them to the request as metadata.</li>
<li>The request is buffered durably together with its metadata.</li>
<li>At the indexing stage, the consumer reads the flags and hands the batch to one of two exporters: the OTLP/HTTP exporter, which forwards Protobuf to <code>/_otlp/v1/metrics</code>, or the Elasticsearch exporter, which converts the batch into documents and posts them to <code>/_bulk</code>.</li>
</ol>
<p>Because the flags are derived inside Elastic's trust boundary, a client cannot opt a deployment into a path its version does not support.
Elastic can also pin specific deployments to the bulk API for compatibility reasons, so the version rule below describes the normal case rather than a guarantee.</p>
<h3 id="whichingestionpathdoesmydeploymentuse">Which ingestion path does my deployment use?</h3>
<p>| ECH Elastic Stack version | Path the Managed OTLP Endpoint normally uses |
| ------------------------- | ------------------------------------------- |
| 9.0 to 9.5.2              | Bulk API (<code>/_bulk</code>)                          |
| 9.5.3 or later            | Native OTLP endpoint (<code>/_otlp/v1/metrics</code>)   |</p>
<p><strong>No client-side change is required.</strong>
If you already send OTLP metrics to the Managed OTLP Endpoint on ECH, your traffic moves to the native path when you upgrade the deployment to 9.5.3 or later.</p>
<h2 id="whatchangeswhenopentelemetrymetricsmovetothenativepath">What changes when OpenTelemetry metrics move to the native path</h2>
<p>Client configuration and data stream routing remain unchanged.
Histogram representation and temporality handling change: the native path uses <code>exponential_histogram</code> by default and records temporality as a dimension.
Review dashboards, alerts, and queries that depend on the previous histogram representation or aggregate cumulative snapshots.
Queries spanning historical <code>histogram</code> fields and newer <code>exponential_histogram</code> fields may need an explicit <code>::exponential_histogram</code> cast, as described in <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-histogram-fields#query-historical-data-alongside-new-data">querying historical data alongside new data</a>.</p>
<p>The distribution of work across the ingestion chain also changes.
On the bulk path, the managed pipeline converts every data point into a JSON document, serializes it, and Elasticsearch parses that JSON back into a document and hashes the dimensions of each one to compute its time-series identifier.
On the native path, Elasticsearch decodes a compact Protobuf payload once, and because it sees the OTLP resource and scope structure, it can <a href="https://github.com/elastic/elasticsearch/pull/134982">hash the resource attributes once</a> and reuse that partial hash for every data point that shares the resource, instead of re-hashing the full dimension set per document.
The <a href="https://github.com/elastic/elasticsearch/pull/133057">pull request that introduced the endpoint</a> names both effects, the binary encoding and the reused partial hashes, as the reasons the native endpoint is more efficient than bulk ingestion of the same data.</p>
<p>There are also capabilities you gain compared with the Elasticsearch exporter currently used by the managed bulk path.
The native endpoint records the aggregation temporality of each data point, stores histograms in the <code>exponential_histogram</code> field type, and accepts cumulative histograms.
These are differences between the two managed ingestion implementations, not inherent restrictions of the Bulk API.
Custom bulk clients can index exponential histogram documents and <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/metric-temporality#configure-temporality">configure a temporality dimension</a>.
The rest of this post is about those differences.</p>
<h2 id="opentelemetryhistogramtypesandtemporalityexplained">OpenTelemetry histogram types and temporality explained</h2>
<p>The most visible difference between the two paths is what happens to histograms, so the rest of this post is a tour through every histogram case.</p>
<p>OpenTelemetry defines two histogram data types.
<code>Histogram</code> uses explicit bucket boundaries chosen by the producer.
<code>ExponentialHistogram</code> uses exponentially spaced buckets controlled by a <code>scale</code> parameter, so the SDK adapts resolution to the data.</p>
<p>Each type can use one of two temporalities.
With <strong>delta</strong> temporality, every data point covers one collection interval.
With <strong>cumulative</strong> temporality, every data point covers everything since a fixed start time.</p>
<p>That gives four combinations, and the two ingestion paths treat them differently.</p>
<p>| Path                             | Explicit, delta      | Explicit, cumulative | Exponential, delta | Exponential, cumulative |
| -------------------------------- | -------------------- | -------------------- | ------------------ | ----------------------- |
| Bulk API (ECH 9.0 to 9.5.2)      | Supported, converted | Dropped              | Supported, converted | Dropped               |
| Native (ECH 9.5.3 or later)      | Supported, converted | Supported, converted | Supported          | Supported               |</p>
<p>"Converted" means the metric is accepted but stored with a different bucket representation than the one on the wire.
On the native path, that representation is the <code>exponential_histogram</code> field type, which is the default mapping for OTLP histograms since 9.4.
Cumulative histograms on the native path require that default; if a cluster overrides <code>xpack.otel_data.histogram_field_type</code> to <code>histogram</code>, cumulative histograms are not supported.
If Elastic has pinned a deployment to the bulk API for compatibility, the bulk row applies regardless of version.</p>
<p>Each of the four examples below is a complete <code>curl</code> request you can run against your own deployment.
The JSON encoding of OTLP/HTTP is verbose but self-documenting, which makes it a good learning tool.
Production shippers use Protobuf (<code>application/x-protobuf</code>) instead.</p>
<h3 id="prerequisitesyourendpointurlandapikey">Prerequisites: your endpoint URL and API key</h3>
<p>In the Elastic Cloud Console, find your deployment under <strong>Hosted deployments</strong> and select <strong>Manage</strong>.
In <strong>Application endpoints, cluster and component IDs</strong>, select <strong>Managed OTLP</strong> and copy the public endpoint.</p>
<p>Then open the <strong>API keys</strong> management page in Kibana and create an API key with the <code>event:write</code> privilege for the <code>apm</code> application.
Use the <strong>Encoded</strong> value of the key.
It is already base64-encoded in the <code>id:api_key</code> format that the <code>Authorization</code> header expects.</p>
<pre><code>export MANAGED_OTLP_URL="https://your-endpoint.elastic-cloud.com"
export ELASTIC_API_KEY="your-encoded-api-key"

# Nanosecond-precision timestamps: T0 = 2 min ago, T1 = 1 min ago, T2 = now
T0="$(python3 -c 'import time; print(time.time_ns() - 120_000_000_000)')"
T1="$(python3 -c 'import time; print(time.time_ns() -  60_000_000_000)')"
T2="$(python3 -c 'import time; print(time.time_ns())')"
</code></pre>
<p>All four examples report the same metric, <code>http.server.request.duration</code> in seconds, for the same service.
Each has a distinct <code>example.case</code> resource attribute so the examples form separate time series and can be queried independently.
The explicit and exponential examples describe different latency distributions; each cumulative example contains two snapshots whose difference matches its delta counterpart.
Run the timestamp setup again before repeating the examples to avoid resending points with the same dimensions and timestamp.</p>
<h3 id="explicithistogramwithdeltatemporalitycase1">Explicit histogram with delta temporality (case 1)</h3>
<p>This is the case that works on every supported ECH version, so it is the right place to learn the shape of an OTLP histogram.</p>
<p>A histogram distributes individual measurements across buckets.
For an explicit histogram, you choose the bucket boundaries in advance. For example: 5 ms, 10 ms, and 25 ms, and the SDK counts how many requests fell into each bucket.
Each data point carries the bucket counts, the total count, and the sum, which is enough to estimate percentiles at query time.</p>
<p>Percentiles are what you want for latency.
An average of 50 ms hides the fact that 1% of requests take 5 seconds, while a histogram shows p50, p95, and p99 side by side.</p>
<pre><code>curl --fail-with-body --silent --show-error \
  -H "Authorization: ApiKey ${ELASTIC_API_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "resourceMetrics": [
      {
        "resource": {
          "attributes": [
            {"key": "service.name", "value": {"stringValue": "my-service"}},
            {"key": "example.case", "value": {"stringValue": "explicit-delta"}}
          ]
        },
        "scopeMetrics": [
          {
            "scope": {"name": "http"},
            "metrics": [
              {
                "name": "http.server.request.duration",
                "unit": "s",
                "histogram": {
                  "aggregationTemporality": 1,
                  "dataPoints": [
                    {
                      "startTimeUnixNano": "'"${T1}"'",
                      "timeUnixNano":      "'"${T2}"'",
                      "count": 100,
                      "sum": 12.5,
                      "explicitBounds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
                      "bucketCounts":   [2,     5,    10,    20,   30,  18,   10,  4,   1,  0],
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    }
                  ]
                }
              }
            ]
          }
        ]
      }
    ]
  }' \
  "${MANAGED_OTLP_URL}/v1/metrics"
</code></pre>
<p><code>aggregationTemporality: 1</code> means delta.
The data point covers the one-minute window from <code>startTimeUnixNano</code> to <code>timeUnixNano</code> and nothing before it.</p>
<p><code>explicitBounds</code> lists nine upper boundaries, so <code>bucketCounts</code> has ten entries.
The last entry is the overflow bucket for values above 2.5 seconds.</p>
<p>A successful response is HTTP 200 with an empty or absent <code>partialSuccess</code> object, meaning the managed endpoint durably accepted the data for processing.
It does not confirm that Elasticsearch indexed the points: unsupported cumulative histograms can still be dropped on the managed bulk path, and indexing can fail downstream.
Use the queries below to verify ingestion. See <a href="https://www.elastic.co/docs/reference/opentelemetry/managed-inputs/managed-otlp-endpoint#indexing-errors-and-the-failure-store">indexing errors and the failure store</a> for troubleshooting.</p>
<p><strong>How Elastic stores it.</strong>
Both paths accept this payload, and both convert it.
On the bulk path, the Elasticsearch exporter turns the buckets into a T-Digest style <code>histogram</code> field.
On the native path, Elasticsearch converts the explicit boundaries into an <code>exponential_histogram</code>.
In both cases percentile precision is bounded by the boundaries you chose on the producer side, because the conversion cannot recover detail that the original buckets did not have.</p>
<h3 id="explicithistogramwithcumulativetemporalitycase2">Explicit histogram with cumulative temporality (case 2)</h3>
<p>This case uses the same metric and bucket boundaries as case 1, with cumulative counts and a fixed start time.
This is the case that separates the two paths most sharply.</p>
<p>Cumulative temporality is the default in most OpenTelemetry SDKs and the model Prometheus users know.
Every data point reports the totals since the process started, so the counts only grow and a consumer computes the rate over a window by subtracting two points.
It is resilient to lost data points, because the next point still carries the full state.</p>
<pre><code>curl --fail-with-body --silent --show-error \
  -H "Authorization: ApiKey ${ELASTIC_API_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "resourceMetrics": [
      {
        "resource": {
          "attributes": [
            {"key": "service.name", "value": {"stringValue": "my-service"}},
            {"key": "example.case", "value": {"stringValue": "explicit-cumulative"}}
          ]
        },
        "scopeMetrics": [
          {
            "scope": {"name": "http"},
            "metrics": [
              {
                "name": "http.server.request.duration",
                "unit": "s",
                "histogram": {
                  "aggregationTemporality": 2,
                  "dataPoints": [
                    {
                      "startTimeUnixNano": "'"${T0}"'",
                      "timeUnixNano":      "'"${T1}"'",
                      "count": 150,
                      "sum": 18.5,
                      "explicitBounds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
                      "bucketCounts":   [3,     7,    15,    30,   45,  27,   15,  6,   2,  0],
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    },
                    {
                      "startTimeUnixNano": "'"${T0}"'",
                      "timeUnixNano":      "'"${T2}"'",
                      "count": 250,
                      "sum": 31.0,
                      "explicitBounds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
                      "bucketCounts":   [5,     12,   25,    50,   75,  45,   25,  10,  3,  0],
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    }
                  ]
                }
              }
            ]
          }
        ]
      }
    ]
  }' \
  "${MANAGED_OTLP_URL}/v1/metrics"
</code></pre>
<p><code>aggregationTemporality: 2</code> means cumulative.
Both points retain <code>T0</code> as the fixed start of the series.
The first reports 150 requests through <code>T1</code>; the second reports 250 through <code>T2</code>.
Subtracting their counts, sums, and corresponding buckets gives the 100-request distribution for <code>T1</code> to <code>T2</code> shown in case 1.</p>
<p><strong>How Elastic stores it.</strong>
On the native path Elasticsearch stores the point as an <code>exponential_histogram</code> and records <code>cumulative</code> in the temporality dimension.
Time series functions in ES|QL use that dimension to compute per-interval rates from consecutive cumulative points.</p>
<p>On the bulk path this data point is dropped.
The managed bulk path's Elasticsearch exporter does not support cumulative histograms; treating those buckets as delta would produce incorrect interval distributions.
If your ECH deployment is on 9.0 to 9.5.2, configure your SDK or Collector to export histograms with delta temporality, or put a <code>cumulativetodelta</code> processor in front of the export.</p>
<h3 id="exponentialhistogramwithdeltatemporalitycase3">Exponential histogram with delta temporality (case 3)</h3>
<p>Now switch the data type while keeping delta temporality.</p>
<p>An exponential histogram removes the need to pick boundaries.
Its buckets are exponentially spaced and controlled by a single <code>scale</code> parameter, and the SDK lowers the scale automatically when the data range grows.
That is why exponential histograms are the recommended default for latency SLOs where p99 and p99.9 accuracy matters: the resolution is relative to the value, so the tail is captured as precisely as the body.</p>
<pre><code>curl --fail-with-body --silent --show-error \
  -H "Authorization: ApiKey ${ELASTIC_API_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "resourceMetrics": [
      {
        "resource": {
          "attributes": [
            {"key": "service.name", "value": {"stringValue": "my-service"}},
            {"key": "example.case", "value": {"stringValue": "exponential-delta"}}
          ]
        },
        "scopeMetrics": [
          {
            "scope": {"name": "http"},
            "metrics": [
              {
                "name": "http.server.request.duration",
                "unit": "s",
                "exponentialHistogram": {
                  "aggregationTemporality": 1,
                  "dataPoints": [
                    {
                      "startTimeUnixNano": "'"${T1}"'",
                      "timeUnixNano":      "'"${T2}"'",
                      "count": 100,
                      "sum": 62.2,
                      "scale": 3,
                      "zeroCount": 0,
                      "positive": {
                        "offset": -10,
                        "bucketCounts": [2, 5, 10, 20, 30, 18, 10, 4, 1]
                      },
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    }
                  ]
                }
              }
            ]
          }
        ]
      }
    ]
  }' \
  "${MANAGED_OTLP_URL}/v1/metrics"
</code></pre>
<p>Reading the bucket layout takes a moment the first time.</p>
<p>The base of the histogram is <code>2^(2^-scale)</code>.
With <code>scale: 3</code> that is about 1.09, so each bucket is 9% wider than the previous one.</p>
<p>Bucket <code>i</code> in <code>bucketCounts</code> covers the range <code>(base^(offset+i), base^(offset+i+1)]</code>, lower bound excluded and upper bound included.
With <code>offset: -10</code> the first bucket starts at about 0.42 seconds and the ninth ends at about 0.92 seconds.</p>
<p><code>zeroCount</code> counts measurements that are exactly zero or within the zero threshold, and a <code>negative</code> bucket range exists for negative values, which latency never uses.</p>
<p><strong>How Elastic stores it.</strong>
On the native path Elasticsearch stores the scale, offset, and counts as an <code>exponential_histogram</code> without conversion.
This is the highest-fidelity case: what you measured is what you query.</p>
<p>On the bulk path the Elasticsearch exporter converts the exponential buckets into a T-Digest style <code>histogram</code>.
The data point is accepted and percentiles remain usable, but the original scale and bucket layout are not preserved.</p>
<h3 id="exponentialhistogramwithcumulativetemporalitycase4">Exponential histogram with cumulative temporality (case 4)</h3>
<p>The last case combines the adaptive bucket layout of case 3 with the cumulative semantics of case 2.
An SDK configured for exponential histogram aggregation and cumulative export temporality produces this shape.
The <a href="https://opentelemetry.io/docs/specs/otel/metrics/sdk/#default-aggregation">OpenTelemetry SDK default aggregation</a> for histogram instruments is explicit buckets, so selecting exponential aggregation is a separate configuration choice.</p>
<pre><code>curl --fail-with-body --silent --show-error \
  -H "Authorization: ApiKey ${ELASTIC_API_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "resourceMetrics": [
      {
        "resource": {
          "attributes": [
            {"key": "service.name", "value": {"stringValue": "my-service"}},
            {"key": "example.case", "value": {"stringValue": "exponential-cumulative"}}
          ]
        },
        "scopeMetrics": [
          {
            "scope": {"name": "http"},
            "metrics": [
              {
                "name": "http.server.request.duration",
                "unit": "s",
                "exponentialHistogram": {
                  "aggregationTemporality": 2,
                  "dataPoints": [
                    {
                      "startTimeUnixNano": "'"${T0}"'",
                      "timeUnixNano":      "'"${T1}"'",
                      "count": 150,
                      "sum": 93.3,
                      "scale": 3,
                      "zeroCount": 0,
                      "positive": {
                        "offset": -10,
                        "bucketCounts": [3, 7, 15, 30, 45, 27, 15, 6, 2]
                      },
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    },
                    {
                      "startTimeUnixNano": "'"${T0}"'",
                      "timeUnixNano":      "'"${T2}"'",
                      "count": 250,
                      "sum": 155.5,
                      "scale": 3,
                      "zeroCount": 0,
                      "positive": {
                        "offset": -10,
                        "bucketCounts": [5, 12, 25, 50, 75, 45, 25, 10, 3]
                      },
                      "attributes": [
                        {"key": "http.request.method",       "value": {"stringValue": "GET"}},
                        {"key": "http.response.status_code", "value": {"intValue": 200}}
                      ]
                    }
                  ]
                }
              }
            ]
          }
        ]
      }
    ]
  }' \
  "${MANAGED_OTLP_URL}/v1/metrics"
</code></pre>
<p><strong>How Elastic stores it.</strong>
On the native path this is stored exactly like case 3, as an <code>exponential_histogram</code>, plus the <code>cumulative</code> temporality dimension.
Elasticsearch can then downsample and compute rates over the series correctly.
The two points share the same start time and bucket layout; their difference is the delta distribution in case 3.</p>
<p>On the bulk path the data point is dropped, for the same reason as case 2.</p>
<blockquote>
  <p><strong>Requires Elastic Stack 9.5.3 or later on ECH.</strong>
  On ECH 9.0 to 9.5.2, cases 2 and 4 are dropped.
  Emit delta histograms until you upgrade.</p>
</blockquote>
<h2 id="queryopentelemetryhistogrampercentileswithesql">Query OpenTelemetry histogram percentiles with ES|QL</h2>
<p>Sending is only half of the showcase.
Open Discover in Kibana, switch to ES|QL mode, and first confirm that the documents arrived:</p>
<pre><code>FROM metrics-*.otel-*
| EVAL duration = metrics.http.server.request.duration::exponential_histogram
| WHERE service.name == "my-service" AND duration IS NOT NULL
| KEEP @timestamp, resource.attributes.example.case, metrics.http.server.request.duration, attributes.http.request.method
| SORT @timestamp DESC
| LIMIT 10
</code></pre>
<p>On a native-path deployment the histogram field shows the scale, the bucket indices, and the counts you sent.
On a bulk-path deployment you see the converted <code>values</code> and <code>counts</code> arrays instead, which is the visible trace of the conversion.</p>
<p>Then ask the question the histogram was collected for, the request latency percentiles:</p>
<pre><code>TS metrics-*.otel-*
| WHERE service.name == "my-service" AND @timestamp &gt;= NOW() - 5 minutes
| STATS p50 = PERCENTILE(metrics.http.server.request.duration::exponential_histogram, 50),
        p95 = PERCENTILE(metrics.http.server.request.duration::exponential_histogram, 95),
        p99 = PERCENTILE(metrics.http.server.request.duration::exponential_histogram, 99)
    BY resource.attributes.example.case, attributes.http.request.method, TBUCKET(5 minutes)
</code></pre>
<p><code>TS</code> merges histograms per time series and respects their temporality before computing percentiles.
For cumulative series, it accounts for the change between consecutive snapshots instead of counting their overlapping observations repeatedly.
<code>FROM</code> is useful for inspecting individual documents, but it ignores temporality when aggregating them.
See <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/metric-temporality#how-temporality-affects-queries">how temporality affects queries</a>.</p>
<p>Grouping by <code>resource.attributes.example.case</code> keeps the four examples separate.
The explicit examples place the median in the 0.05 to 0.1 second bucket; the exponential examples place it around 0.6 seconds.
These are different input distributions, so their percentile results should differ. The cumulative examples describe the same final one-minute distribution as their corresponding delta examples.
Refer to the <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/aggregation-functions">ES|QL aggregation functions</a> reference for the full list of functions that accept exponential histogram fields.</p>
<h2 id="summaryopentelemetryhistogramsupportonelasticcloudhosted">Summary: OpenTelemetry histogram support on Elastic Cloud Hosted</h2>
<ul>
<li>ECH deployments on Elastic Stack 9.5.3 or later receive OTLP metrics through the native Elasticsearch endpoint, with no client change.</li>
<li>Those deployments accept all four histogram cases, and exponential histograms are stored without conversion.</li>
<li>ECH deployments on 9.0 to 9.5.2 continue through the bulk API and should emit delta histograms.</li>
<li>Serverless deployments always use the native path.</li>
</ul>
<p>Clients keep sending to the same <code>/v1/metrics</code> path on the Managed OTLP Endpoint in every case.</p>
<h2 id="learnmoreaboutopentelemetrymetricsonelastic">Learn more about OpenTelemetry metrics on Elastic</h2>
<ul>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/managed-inputs/managed-otlp-endpoint">Elastic Cloud Managed OTLP Endpoint documentation</a></li>
<li><a href="https://www.elastic.co/docs/manage-data/ingest/otlp-endpoint">Elasticsearch OTLP/HTTP endpoint documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram">Exponential histogram field type</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-ga-elastic-cloud-hosted">Now GA: Managed OTLP Endpoint on Elastic Cloud Hosted</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-histograms-elastic-cloud-hosted</link>
    <guid isPermaLink="false">opentelemetry-histograms-elastic-cloud-hosted</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Maurizio Branca]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77ab0ff8549edba9/6aa7dd83afd9a8c1a8120ed9/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How one ES|QL query builds a metric chart for every metric in Elasticsearch]]></title>
    <description><![CDATA[METRICS_INFO reports what metrics are in your data and how to aggregate each one, so Kibana Discover can chart counters,  gauges and histograms correctly with no configuration and no field names to look up.]]></description>
    <content:encoded><![CDATA[<p>Type <code>TS metrics-*</code> in Kibana Discover and you get a chart for every metric in your data, already using the right aggregation and unit. No field names, no per-metric setup.</p>
<p><code>METRICS_INFO</code>, an ES|QL command, reports which metrics and time series exist in the scope of your query, one row each. Discover appends it to your query behind the scenes, builds every metric chart from that single response, and can split those charts by any dimension your data exposes. Gauges are averaged, counters use <code>SUM(RATE())</code>, histograms take a percentile. You never need to know which aggregation a metric requires.</p>
<p>For the design and internals, including the per-series sibling command <code>TS_INFO</code>, see the <a href="https://www.elastic.co/search-labs/blog/esql-metrics-info-ts-info-time-series-catalog">METRICS_INFO and TS_INFO deep dive</a>.</p>
<h2 id="whatmetrics_inforeturns">What METRICS_INFO returns</h2>
<p><code>METRICS_INFO</code> retrieves information about the metrics available in your <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">time series data streams</a>, together with applicable dimensions and other metadata, all scoped to the current <code>TS</code> query.</p>
<pre><code>TS metrics-* | METRICS_INFO
</code></pre>
<p>You get one row describing each metric in the query scope:</p>
<p>| metric_name                         | data_stream                       | unit      | metric_type | field_type | dimension_fields                   |
| ----------------------------------- | --------------------------------- | --------- | ----------- | ---------- | ---------------------------------- |
| <code>system.cpu.user.pct</code>               | <code>metrics-system.cpu-default</code>      | <code>percent</code> | <code>gauge</code>     | <code>double</code>   | <code>[host.name, cloud.region]</code>        |
| <code>activemq.broker.connections.count</code> | <code>metrics-activemq.broker-default</code> | <code>null</code>    | <code>counter</code>   | <code>long</code>     | <code>[activemq.broker.mbean,agent.id]</code> |</p>
<p>For syntax, see the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/metrics-info">METRICS_INFO command</a>. For the design and internals, including the per-series sibling command <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts-info">TS_INFO</a>, see the <a href="https://www.elastic.co/search-labs/blog/esql-metrics-info-ts-info-time-series-catalog">METRICS_INFO and TS_INFO deep dive</a>.</p>
<h2 id="howdiscoverturnsatsqueryintometriccharts">How Discover turns a TS query into metric charts</h2>
<p>Discover provides a dedicated experience for exploring metrics data. When it detects a <code>TS</code> query, it automatically builds an inventory of charts for the metrics available in your data.</p>
<p>Your original query remains unchanged and continues to run as usual. Behind the scenes, Discover derives a second request from it by appending <code>| METRICS_INFO</code>.</p>
<p>For example, if you run:</p>
<pre><code>TS metrics-*
| WHERE `cloud.provider` == "gcp" AND `cloud.region` == "us-central1"
</code></pre>
<p>Discover derives a second request behind the scenes:</p>
<pre><code>TS metrics-*
| WHERE `cloud.provider` == "gcp" AND `cloud.region` == "us-central1"
| METRICS_INFO
</code></pre>
<p>The <code>METRICS_INFO</code> response is parsed once and becomes the source for the dedicated metrics experience in Discover.
From that inventory, Discover can generate charts and provide capabilities such as searching and filtering metrics, breaking them down by dimensions, inspecting the ES|QL query behind each chart, and adding metrics to dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4948cc2b03015e1a/6a96918fd29b4edf151d776b/metrics-grid-overview.png" alt="Kibana Discover metrics inventory with one chart panel per metric returned by ES|QL METRICS_INFO" /></p>
<h2 id="whateachmetrics_infocolumncontrols">What each METRICS_INFO column controls</h2>
<p>Discover uses one <code>METRICS_INFO</code> response to generate every chart in the inventory. The response tells Discover which metrics to render, where to find their data, how to aggregate their values and how to display them.</p>
<p>Each column in the response plays a specific role:</p>
<p>| Column name        | What it controls in the inventory                       |
| ------------------ | ------------------------------------------------------- |
| <code>metric_name</code>      | Which metric panels are rendered                        |
| <code>data_stream</code>      | Where each panel gets its data                          |
| <code>metric_type</code>      | How each metric is aggregated                           |
| <code>field_type</code>       | The type of the field, e.g., <code>double</code>, <code>long</code>           |
| <code>unit</code>             | How values are formatted                                |
| <code>dimension_fields</code> | Which dimensions can be used to filter and split charts |</p>
<h3 id="whichmetricchartsgetrenderedmetric_name">Which metric charts get rendered: metric_name</h3>
<p>Each entry in the <code>METRICS_INFO</code> response represents a metric that Discover can visualize.</p>
<p>Discover iterates over the parsed response and creates a chart panel for each metric. The inventory therefore reflects the contents of the response directly.</p>
<h3 id="whereeachchartgetsitsdatadata_stream">Where each chart gets its data: data_stream</h3>
<p>Each chart queries its data independently, even though all metric metadata comes from the same <code>METRICS_INFO</code> request.</p>
<p>The <code>data_stream</code> value determines the source used to construct the chart's ES|QL query.</p>
<p>For example:</p>
<pre><code>TS metrics-system.cpu-default
| STATS AVG(system.cpu.user.pct) BY TBUCKET(100)
</code></pre>
<p>Querying each data stream separately matters most for searches that span multiple projects.</p>
<p>When the same <code>metric_name</code> appears in multiple <code>data_stream</code> values, Discover creates a separate panel for each stream. For example, in a serverless environment with <a href="https://www.elastic.co/docs/explore-analyze/cross-project-search">cross-project search</a>, each panel queries its own backing data and identifies the stream it represents.</p>
<blockquote>
  <p>This metric exists in multiple data streams. This chart shows data from <code>metrics-system.cpu-default</code> only.</p>
</blockquote>
<p>Discover separates the panels intentionally. Combining results across streams could hide differences between them, which may be important when investigating metrics across projects or environments.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06e3c620101fe303/6a9691a627a5315c5edc8e22/duplicate-metric-name.png" alt="Kibana Discover metrics  showing two panels for the same metric, one per data stream, with a duplicate warning subtitle" /></p>
<h3 id="howcountergaugeandhistogrammetricsareaggregatedmetric_typefield_type">How counter, gauge, and histogram metrics are aggregated: metric_type, field_type</h3>
<p>The <code>metric_type</code> tells <a href="https://www.elastic.co/kibana/kibana-lens">Lens charts</a> how to aggregate the metric, while the <code>field_type</code> describes the field.</p>
<p>Three metric types are currently supported:</p>
<p>| <code>metric_type</code> | What the chart computes                            | Example               |
| ------------- | -------------------------------------------------- | --------------------- |
| <code>gauge</code>       | <code>AVG(field)</code>, representing a point-in-time level   | CPU usage             |
| <code>counter</code>     | <code>SUM(RATE(field))</code>, representing a rate of change  | Bytes sent per second |
| <code>histogram</code>   | <code>PERCENTILE(field, p)</code>, summarizing a distribution | p99 latency           |</p>
<h3 id="howvaluesaredisplayedunit">How values are displayed: unit</h3>
<p>The <code>unit</code> column controls how values are formatted on the Y axis. <a href="https://www.elastic.co/kibana/kibana-lens">Lens</a>, the charting library behind each panel, applies the formatting.</p>
<p>For example:</p>
<ul>
<li><code>bytes</code>: <code>1,024</code> is displayed as <code>1 KB</code>.</li>
<li><code>percent</code>: <code>0.75</code> is displayed as <code>75%</code>.</li>
<li>No unit: the raw value is displayed without unit-specific formatting.</li>
</ul>
<h3 id="howchartsarefilteredandsplitbydimensiondimension_fields">How charts are filtered and split by dimension: dimension_fields</h3>
<p>The <code>dimension_fields</code> column identifies the dimensions associated with each metric, such as <code>host.name</code>, <code>cloud.region</code>, or <code>service.name</code>.</p>
<p>Discover combines these values across the <code>METRICS_INFO</code> response to populate the dimensions dropdown in the inventory toolbar.</p>
<p>Selecting a dimension affects the inventory in two ways:</p>
<ol>
<li><p><strong>It filters the inventory.</strong> Discover re-runs <code>METRICS_INFO</code> with a condition such as <code>WHERE MV_CONTAINS(dimension_fields, "host.name")</code>, removing metrics that do not support the selected dimension.</p></li>
<li><p><strong>It splits each chart.</strong> Discover adds the selected dimension to the <code>BY</code> clause of each chart's ES|QL query, producing one series for each dimension value.</p></li>
</ol>
<p>For example:</p>
<pre><code>TS metrics-system.cpu-default
| STATS AVG(system.cpu.user.pct)
  BY TBUCKET(100), host.name
</code></pre>
<p>The result is a chart with a separate series for each <code>host.name</code>, while the inventory ensures that only metrics supporting that dimension are included in the inventory.</p>
<p>When you query a different data stream, any previously selected dimensions that the new stream does not expose are automatically cleared, so the per-chart queries never reference fields that do not exist there.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf20c62e1f3c807f/6a9691bca163364278371e89/dimensions-dropdown.png" alt="Dimensions dropdown in the Kibana Discover metrics experience filtering the inventory and splitting charts by host.name" /></p>
<h2 id="tryitinkibanadiscover">Try it in Kibana Discover</h2>
<p>Visualizing your metrics requires no configuration, no dashboard setup, and no per-metric query to write.
One <code>TS</code> query is enough.</p>
<h3 id="step1ingestmetricsdata">Step 1: Ingest metrics data</h3>
<p>If you are starting from scratch, you can send Prometheus metrics to Elasticsearch using <a href="https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-getting-started">Prometheus Remote Write</a>.
Any <a href="https://www.elastic.co/integrations">Elastic integration</a> that collects system or application metrics works the same way.
Once data lands in a TSDB-backed <code>metrics-*</code> data stream, Discover picks it up without any extra setup.</p>
<h3 id="step2opendiscoverandrunatsquery">Step 2: Open Discover and run a TS query</h3>
<ol>
<li>Open <strong>Kibana -&gt; Discover</strong>.</li>
<li>Switch to <strong>ES|QL</strong> mode.</li>
<li>Type <code>TS metrics-*</code> and run the query.</li>
</ol>
<h3 id="step3exploreyourmetriccharts">Step 3: Explore your metric charts</h3>
<p>Discover builds the inventory automatically. From there, you can:</p>
<ul>
<li>Search for a metric by name to narrow the inventory.</li>
<li>Select a dimension from the toolbar to split every chart by <code>host.name</code>, <code>cloud.region</code>, or any dimension your data exposes.</li>
<li>Click a chart panel to open the full ES|QL query behind it.</li>
<li>Add individual panels to a dashboard.</li>
</ul>
<p>Your metrics are ready to explore immediately, so you can start investigating your data as soon as you run the query.</p>
<p>See the documentation for how to <a href="https://www.elastic.co/docs/solutions/observability/infra-and-hosts/discover-metrics">Explore metrics data with Discover in Kibana</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-metrics-info-kibana-metrics-charts</link>
    <guid isPermaLink="false">esql-metrics-info-kibana-metrics-charts</guid>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Katerina Patticha]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae655f749e58482e/6a95587bd05d4cb65c5816fe/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 03 Sep 2026 15:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From a 582ms latency spike to the team that owns it, using Kibana Discover]]></title>
    <description><![CDATA[Getting there takes a data view, some filter pills, a KQL query and a switch to Lucene query syntax, but the part that actually names the team is one ES|QL LOOKUP JOIN against a service catalog index.]]></description>
    <content:encoded><![CDATA[<p>A checkout service is running at 582ms p95 against a 350ms SLO target. One KQL query in Kibana <a href="https://www.elastic.co/docs/explore-analyze/discover/discover-get-started">Discover</a> finds it. Working out which team owns that service takes an ES|QL query that joins the metric documents to a small service catalog index using <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>. Below, that investigation runs in order. A <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/data-views">data view</a> narrows the scope and <a href="https://www.elastic.co/docs/explore-analyze/query-filter/filtering">filter pills</a> keep it visible, which matters more than it sounds when someone else has to reconstruct what you searched. <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/kql">KQL</a> does most of the work. <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/lucene-query-syntax">Lucene</a> query syntax handles the one case that needs a regex, and <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> takes over once filtering stops answering the question.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>To follow along, you need:</p>
<ul>
<li>An <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a> cluster with <a href="https://www.elastic.co/kibana">Kibana</a>. Everything up to the ES|QL section works on any recent version; <code>LOOKUP JOIN</code> is generally available in Elasticsearch 9.1 and was a technical preview in 9.0, so use 9.1 or later for the last section.</li>
<li>No special license tier. Everything in this article, including <code>LOOKUP JOIN</code>, works on the free basic license.</li>
<li>The two small sample indices created in the next section.</li>
</ul>
<h2 id="whycheckoutlatencyincreasedinproduction">Why checkout latency increased in production</h2>
<p>The example starts with a common operations question:</p>
<blockquote>
  <p>Why did checkout latency increase in production, and which team owns the service?</p>
</blockquote>
<p>The metrics index contains 15-minute service measurements for four services across three regions. One service, <code>checkout-api</code>, has higher p95 latency in <code>us-central1</code> during the investigation window. The goal is to get from all metrics to the small set of documents that explain the issue.</p>
<p>The walkthrough follows these steps:</p>
<ol>
<li>Select the right data view and time range.</li>
<li>Use UI filters to include, exclude, disable, and pin criteria.</li>
<li>Use KQL for the main field and range search.</li>
<li>Switch to Lucene when regular expression syntax is useful.</li>
<li>Use ES|QL mode with <code>LOOKUP JOIN</code> to enrich metrics with service catalog data.</li>
</ol>
<h2 id="setupthesamplemetricsindex">Set up the sample metrics index</h2>
<p>The walkthrough searches a metrics index named <code>o11y-labs-discover-service-metrics</code>. Create it with keyword fields for the service dimensions and numeric fields for the measurements:</p>
<pre><code>PUT o11y-labs-discover-service-metrics
{
  "mappings": {
    "properties": {
      "@timestamp": { "type": "date" },
      "service": {
        "properties": {
          "name": { "type": "keyword" },
          "environment": { "type": "keyword" },
          "version": { "type": "keyword" }
        }
      },
      "cloud": { "properties": { "region": { "type": "keyword" } } },
      "host": { "properties": { "name": { "type": "keyword" } } },
      "metrics": {
        "properties": {
          "latency": { "properties": { "p95_ms": { "type": "float" } } },
          "cpu": { "properties": { "pct": { "type": "float" } } },
          "error": { "properties": { "rate": { "type": "float" } } }
        }
      }
    }
  }
}
</code></pre>
<p>Each document is one 15-minute measurement for one service in one region:</p>
<pre><code>POST o11y-labs-discover-service-metrics/_bulk
{ "index": {} }
{ "@timestamp": "2026-06-30T16:15:00.000Z", "service": { "name": "checkout-api", "environment": "production", "version": "2026.06.30-1" }, "cloud": { "region": "us-central1" }, "host": { "name": "checkout-api-us-central1-01" }, "metrics": { "latency": { "p95_ms": 582.6 }, "cpu": { "pct": 0.81 }, "error": { "rate": 0.041 } } }
{ "index": {} }
{ "@timestamp": "2026-06-30T16:15:00.000Z", "service": { "name": "payments-api", "environment": "production", "version": "2026.06.29-7" }, "cloud": { "region": "us-central1" }, "host": { "name": "payments-api-us-central1-01" }, "metrics": { "latency": { "p95_ms": 231.4 }, "cpu": { "pct": 0.31 }, "error": { "rate": 0.008 } } }
</code></pre>
<p>To reproduce the screenshots, index one document per service, region, and 15-minute interval:</p>
<ul>
<li><strong>Services:</strong> <code>checkout-api</code>, <code>checkout-worker</code>, <code>payments-api</code>, <code>inventory-api</code></li>
<li><strong>Regions:</strong> <code>us-central1</code>, <code>us-east4</code>, <code>europe-west1</code></li>
<li><strong>Window:</strong> 14:00 to 19:45 UTC on June 30, 2026, giving 24 intervals of 15 minutes</li>
<li><strong>Documents per interval:</strong> 12 production, plus two <code>staging</code> (<code>checkout-api</code> and <code>payments-api</code>, both in <code>us-central1</code>)</li>
<li><strong>Total:</strong> 24 intervals × 14 documents = 336 documents</li>
</ul>
<p>The exact values do not matter, as long as <code>checkout-api</code> in <code>us-central1</code> reports <code>metrics.latency.p95_ms</code> above 500 between 15:45 and 18:45 UTC and stays well under 500 ms everywhere else.</p>
<p>Instead of indexing everything by hand, you can run the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/exploring-discover-search-methods/exploring-discover-search-methods.ipynb">supporting notebook</a>, which generates the full 336-document dataset, creates both indices, and verifies the final ES|QL query.</p>
<p>The ES|QL section also uses a second, four-document lookup index for service catalog data. We will create it when we get there.</p>
<h2 id="chooseadataviewinkibanadiscover">Choose a data view in Kibana Discover</h2>
<p>The data view is the first filter in Discover. It decides which Elasticsearch indices are searched, which time field drives the histogram, and which fields are available in the left field list.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d498fdd3ec06441/6a903c0da59451f9be62c32e/02-data-view.jpg" alt="Discover with the service metrics data view selected and three filter pills" /></p>
<p>For this walkthrough, the Discover data view points to:</p>
<pre><code>o11y-labs-discover-service-metrics
</code></pre>
<p>The time field is <code>@timestamp</code>. That matters because the time picker limits the documents before you add a query, a filter pill, or a selected field.</p>
<p>Use a narrow data view when you can. For example, a data view that targets only service metrics makes Discover easier to scan than a broad <code>logs-*,metrics-*</code> data view when you already know the question is about metrics.</p>
<p>Once the data view is selected, add the fields that support the investigation:</p>
<ul>
<li><code>service.name</code></li>
<li><code>service.environment</code></li>
<li><code>cloud.region</code></li>
<li><code>metrics.latency.p95_ms</code></li>
<li><code>metrics.cpu.pct</code></li>
<li><code>metrics.error.rate</code></li>
</ul>
<h2 id="filterpillsindiscoverincludeexcludedisableandpin">Filter pills in Discover: include, exclude, disable, and pin</h2>
<p>UI filters are useful when you want a visible, editable list of constraints. They are also helpful when you are exploring fields from the document table and want Discover to write the field syntax for you.</p>
<p>In the document table, use the field actions (the +/- icons that appear when you hover over a value) to include or exclude it. For example:</p>
<pre><code>service.environment: production
NOT cloud.region: us-east4
service.version: 2026.06.29-7  (disabled)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt003d380fc9aa026e/6a903c25a8b3230822cc2ac8/03-filter-pills.jpg" alt="Filter pills showing include, exclude, and disabled states in Discover" /></p>
<p>These three filters show the main filter controls:</p>
<ul>
<li>Include a value when you want only matching documents.</li>
<li>Exclude a value when a dimension is not part of the problem.</li>
<li>Temporarily disable a filter when you want to keep it nearby but remove it from the current query.</li>
<li>Pin a filter when it should stay active as you move between Kibana apps.</li>
</ul>
<p>Pinned filters are useful for investigations that cross app boundaries. For example, you can pin <code>service.environment: production</code> before moving from Discover to <a href="https://www.elastic.co/docs/explore-analyze/dashboards">dashboards</a>, <a href="https://www.elastic.co/docs/explore-analyze/visualize/lens">Lens</a>, or another view. Disabled filters are useful for testing a theory without deleting the context that got you there.</p>
<p>The key habit is to keep the filters readable. If a query has a long search expression and many hidden assumptions, another engineer has to reconstruct your thinking. Filter pills make the major scope decisions visible.</p>
<h2 id="kqlquerysyntaxforfieldrangeandbooleansearches">KQL query syntax for field, range, and boolean searches</h2>
<p>KQL, the Kibana Query Language, is a good default for Discover searches. It supports field names, exact values, ranges, wildcards, and boolean logic in a readable form.</p>
<p>For the checkout latency example, this KQL query narrows the view to one service, one region, and high p95 latency:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53fe42de1fc90184/6a903d899230821341c49a11/04-kql-query.jpg" alt="" /></p>
<pre><code>service.name : "checkout-api" and cloud.region : "us-central1" and metrics.latency.p95_ms &gt;= 500
</code></pre>
<p>Read it from left to right:</p>
<ul>
<li><code>service.name : "checkout-api"</code> keeps one service.</li>
<li><code>cloud.region : "us-central1"</code> keeps one cloud region.</li>
<li><code>metrics.latency.p95_ms &gt;= 500</code> keeps latency samples at or above 500 ms.</li>
</ul>
<p>You can add the environment in KQL:</p>
<pre><code>service.environment : "production" and service.name : "checkout-api" and cloud.region : "us-central1" and metrics.latency.p95_ms &gt;= 500
</code></pre>
<p>Or you can keep <code>service.environment: production</code> as a UI filter. Both approaches are valid. For shared investigations, we prefer stable scope, such as environment and service, as filter pills, and the active hypothesis, such as a latency threshold, in the search bar.</p>
<p>KQL also works well for combining fields:</p>
<pre><code>service.environment : "production" and
(service.name : "checkout-api" or service.name : "payments-api") and
metrics.error.rate &gt; 0.02
</code></pre>
<p>This is useful when a user-facing flow crosses multiple services. You can compare a small group of services without switching data views or creating a dashboard first.</p>
<h2 id="lucenequerysyntaxinkibanasearchingwithregularexpressions">Lucene query syntax in Kibana: searching with regular expressions</h2>
<p>Lucene query syntax is the option in Kibana that supports regular expressions. KQL does not, so when you need a regex in the search bar, open the query menu at the right of the search bar and switch the language to <strong>Lucene</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a56107ae6b7d0be/6a903c4f6ea6da9c2d00a2e8/05-lucene-language.jpg" alt="Filter language menu in Discover with Lucene selected" /></p>
<p>For example, this Lucene query searches production services whose names start with <code>checkout-</code> and whose p95 latency is above 500 ms:</p>
<pre><code>service.name:/checkout-.*/ AND service.environment:production AND metrics.latency.p95_ms:&gt;500
</code></pre>
<p>Lucene syntax is more compact, but it is also easier to misread. Use it when it gives you something you cannot express as clearly in KQL, such as a regex pattern over a field. For everyday field, value, and range filtering, KQL is usually easier for a teammate to review.</p>
<h2 id="howtojointwoindicesindiscoverwithesqllookupjoin">How to join two indices in Discover with ES|QL LOOKUP JOIN</h2>
<p>Classic Discover mode is good when you want to search, filter, inspect fields, and look at raw documents. <a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">ES|QL in Discover</a> is better when the question needs transformation before the result is useful. Use the <strong>Query in ES|QL</strong> button in the Discover toolbar to switch modes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2c6e69cd05ecb1e/6a903c6ba8b323f8facc2ad0/06-esql-button.jpg" alt="Query in ES|QL button in the Discover toolbar" /></p>
<p>In this example, raw metrics tell us that <code>checkout-api</code> latency is high. They do not tell us who owns that service or what latency target the service is expected to meet. That data lives in a small service catalog lookup index.</p>
<h3 id="createalookupindexforservicecatalogdata">Create a lookup index for service catalog data</h3>
<pre><code>PUT o11y-labs-service-catalog-lookup
{
  "settings": {
    "index.mode": "lookup"
  },
  "mappings": {
    "properties": {
      "service": {
        "properties": {
          "name": {
            "type": "keyword"
          }
        }
      },
      "owner": {
        "properties": {
          "team": {
            "type": "keyword"
          }
        }
      },
      "slo": {
        "properties": {
          "latency_target_ms": {
            "type": "long"
          }
        }
      },
      "runbook": {
        "properties": {
          "url": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
</code></pre>
<p>One catalog document can attach ownership and an SLO target to the service:</p>
<pre><code>POST o11y-labs-service-catalog-lookup/_doc/checkout-api
{
  "service": {
    "name": "checkout-api"
  },
  "owner": {
    "team": "checkout-platform"
  },
  "slo": {
    "latency_target_ms": 350
  },
  "runbook": {
    "url": "https://runbooks.example.com/checkout-api/latency"
  }
}
</code></pre>
<h3 id="runthelookupjoinquery">Run the LOOKUP JOIN query</h3>
<p>Now Discover can run an ES|QL query that joins the metric documents with that catalog metadata using <code>LOOKUP JOIN</code>. Remember that this command needs Elasticsearch 9.1 or later, that the lookup index must be created with <code>index.mode: lookup</code>, and that the join field, <code>service.name</code> here, must be mapped as <code>keyword</code> in the lookup index.</p>
<pre><code>FROM o11y-labs-discover-service-metrics
| WHERE @timestamp &gt;= "2026-06-30T15:00:00.000Z" AND @timestamp &lt;= "2026-06-30T18:45:00.000Z"
| WHERE service.environment == "production"
| LOOKUP JOIN o11y-labs-service-catalog-lookup ON service.name
| WHERE owner.team == "checkout-platform" AND metrics.latency.p95_ms &gt; slo.latency_target_ms
| KEEP @timestamp, service.name, cloud.region, metrics.latency.p95_ms, slo.latency_target_ms, owner.team
| SORT @timestamp DESC
</code></pre>
<p>This is the part classic mode does not cover. Classic Discover can filter the metric documents, but ES|QL can enrich those rows with data from another index before displaying the result.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt177047f468855477/6a903c7cda6aea445b37bdb3/07-esql-lookup-join.jpg" alt="ES|QL LOOKUP JOIN results in Discover showing 13 rows for checkout-api" /></p>
<p>The result table answers a more operational question than the original search. It shows the affected service, the region, the latency value, the target, and the owning team in one view.</p>
<p>This pattern is useful for more than ownership. You can keep small lookup indices for service tier, deployment ring, escalation channel, business capability, or runbook URL. Then you can join that context into metric searches at investigation time.</p>
<h2 id="howtochoosetherightdiscoversearchmethod">How to choose the right Discover search method</h2>
<p>The most useful workflow is not one search language for everything. It is a progression from broad scope to specific evidence.</p>
<p>| Use case | Discover feature | Why it helps |
| :---- | :---- | :---- |
| Limit the searchable data | Data view and time picker | Removes irrelevant indices and old documents before the query runs |
| Keep scope visible | UI filters | Makes include, exclude, disabled, and pinned criteria easy to review |
| Search exact fields and ranges | KQL | Keeps common metric searches readable |
| Match field values with regex | Lucene mode | Adds regular expression syntax when the search needs it |
| Enrich or reshape results | ES|QL mode | Adds joins, projections, sorting, and transformations |</p>
<p>For a real investigation, start with the smallest data view that still contains the data you need. Add filter pills for stable scope. Use KQL for the active search. Switch to Lucene only when regex syntax is worth the extra complexity. Move to ES|QL when the question needs enrichment, aggregation, or reshaping.</p>
<h2 id="fieldnamingconventionsthatmakemetricseasiertosearch">Field naming conventions that make metrics easier to search</h2>
<p>Metric search works best when the field names carry enough context. The examples above use <a href="https://www.elastic.co/docs/reference/ecs">Elastic Common Schema</a>-style fields where possible:</p>
<ul>
<li><code>service.name</code> for the monitored service.</li>
<li><code>service.environment</code> for production, staging, or development.</li>
<li><code>cloud.region</code> for the deployment region.</li>
<li><code>host.name</code> for host-level drill-down.</li>
<li>Numeric metric fields under <code>metrics.*</code>.</li>
</ul>
<p>You do not need this exact schema to use Discover, but predictable field names make the search bar and filter pills much easier to use. They also make <a href="https://www.elastic.co/docs/explore-analyze/discover/save-open-search">saved searches</a> and screenshots easier to understand during a handoff.</p>
<p>For service catalog data, keep the lookup index small and stable. Fields like service owner, tier, SLO target, and runbook URL change less often than raw metrics. That makes them good candidates for <code>LOOKUP JOIN</code> during analysis.</p>
<h2 id="runthewalkthroughonyourowncluster">Run the walkthrough on your own cluster</h2>
<p>Use Discover as a drill-down path, not only as a document table. In this walkthrough, we:</p>
<ul>
<li>Scoped the search with a narrow data view and the time picker before writing any query.</li>
<li>Made the investigation scope visible and shareable with include, exclude, disabled, and pinned filter pills.</li>
<li>Used KQL for readable field, range, and boolean searches.</li>
<li>Switched to Lucene only for the regex case KQL cannot express.</li>
<li>Enriched metric documents with ownership and SLO data from a lookup index using ES|QL <code>LOOKUP JOIN</code>.</li>
</ul>
<p>To try the full flow on your own cluster, run the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/exploring-discover-search-methods/exploring-discover-search-methods.ipynb">supporting notebook</a>, which creates both indices and the incident data used in every example.</p>
<p>Related documentation:</p>
<ul>
<li><a href="https://www.elastic.co/docs/explore-analyze/discover/discover-get-started">Discover</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/data-views">Data views</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/query-filter/filtering">Filtering</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/kql">KQL</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/lucene-query-syntax">Lucene query syntax</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">ES|QL in Discover</a></li>
<li><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a></li>
</ul>
<p>Related Observability Labs articles:</p>
<ul>
<li><a href="https://www.elastic.co/observability-labs/blog/exploring-metrics-new-data-source-discover">Exploring metrics from a new time series data stream in Discover</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/metrics-explore-analyze-with-esql-discover">Explore and analyze metrics with ease in Elastic Observability</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-discover-traces-apm">Traces in Discover for deeper application insights in Elastic Observability</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-esql-join-observability">Connecting the dots: ES|QL joins for richer observability insights</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/esql-kubernetes-monitoring">Common ES|QL queries for Kubernetes monitoring</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kibana-discover-search-kql-lucene-esql</link>
    <guid isPermaLink="false">kibana-discover-search-kql-lucene-esql</guid>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d63a247bf43f8b7/6a903bbb386ac3e853ae147b/01-header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 28 Aug 2026 15:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kubernetes observability: SLO templates that turn alerts into error budgets]]></title>
    <description><![CDATA[Two bad rollouts burned 88% of a 30-day error budget while the SLI still read 99.56%. This post adds four SLO templates that bring burn-rate tracking to the OTel-based alert rules from Part 1, no new instrumentation required.]]></description>
    <content:encoded><![CDATA[<p>Two bad rollouts on one Deployment burned <strong>88%</strong> of a <strong>30-day</strong> error budget in a day and fired a 26X burn-rate alert while the SLI still read 99.56%. That is the gap <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> alert rules cannot close on their own: they page when replicas drop; they do not tell you how much monthly reliability budget the incident cost.</p>
<p>The <strong>Kubernetes OpenTelemetry Assets</strong> package now ships four <strong>Kubernetes SLO templates</strong> for Deployments, StatefulSets, DaemonSets, and Jobs on those OTel metrics. If you already followed <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> and have the dashboards and alert rules, create an SLO from a template and you get SLI, remaining budget, and burn rate without new instrumentation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4df618f6b39dd743/6a8ea15cf59d7c22f6c9386f/k8s_integration_extension.png" alt="Kubernetes observability with Elastic, flow diagram of OTel metrics and events into Dashboards, Alert rules with Page, ML jobs with Anomaly, and SLOs with Burn rate, converging on Overview to workload detail to pod logs" /></p>
<p>The diagram above extends the stack from <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a>. The <strong>Kubernetes OpenTelemetry Assets</strong> package (<code>kubernetes_otel</code>) 2.3.0 includes:</p>
<ul>
<li>Dashboards designed for drill-down (Part 1)</li>
<li>Alert rule templates that fire on known bad states (Part 1)</li>
<li>ML anomaly detection jobs with workload baselines (Part 1)</li>
<li>SLO templates for rolling 30-day budgets (this post)</li>
</ul>
<p>All four use the same OTel metrics. Burn rate alerts on an SLO send you back into Overview, Workloads, and Deployment Details when the number alone is not enough.</p>
<h2 id="whykubernetesobservabilityneedsslomonitoringalongsidealerts">Why Kubernetes observability needs SLO monitoring alongside alerts</h2>
<p><a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> built the reactive stack for the engineer who gets paged at 3 AM. SLOs serve the planning conversation on a <strong>30-day</strong> horizon: <strong>Are we meeting our reliability commitments?</strong> They give platform and engineering leaders a number for prioritisation: how much error budget remains and which workload is burning it fastest. The table later in this post maps each SLO template to its Part 1 alert counterpart.</p>
<p>The SLO templates in this post are part of the <strong>Kubernetes OpenTelemetry Assets</strong> package (<code>kubernetes_otel</code>). Install the <a href="https://www.elastic.co/docs/reference/integrations/kubernetes_otel">Kubernetes OpenTelemetry Assets package</a> and confirm your cluster is already sending Kubernetes metrics through OpenTelemetry (the same pipeline from Part 1). No additional instrumentation is required.</p>
<h2 id="fourslotemplatesforkubernetesdeploymentsstatefulsetsdaemonsetsandjobs">Four SLO templates for Kubernetes Deployments, StatefulSets, DaemonSets and Jobs</h2>
<p>In <strong>Integrations → Kubernetes OpenTelemetry → Assets</strong>, enable any of the four templates below. Names match Kibana; each includes the <code>[Kubernetes OTel]</code> prefix in the UI.</p>
<p>| <strong>Template</strong>                                                | <strong>Rolling objective</strong> | <strong>Package description</strong>                                                                                                                                                                                                                           |
| ----------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <strong>Deployment Replica Availability 99.5% Rolling 30 Days</strong>   | 99.5% / 30d           | Tracks Deployment availability from OTel metrics: 99.5% of intervals should have each Deployment at its desired replica count. When <code>k8s.deployment.available &lt; k8s.deployment.desired</code>, the workload has fewer healthy replicas than configured. |
| <strong>StatefulSet Replica Availability 99.5% Rolling 30 Days</strong>  | 99.5% / 30d           | Same pattern for StatefulSets, where pod identity and ordering matter for databases, queues, and caches.                                                                                                                                          |
| <strong>DaemonSet Scheduling Availability 99.0% Rolling 30 Days</strong> | 99.0% / 30d           | Tracks whether each DaemonSet runs on all eligible nodes. Covers node-level agents such as log collectors, monitoring, security, and CNI plugins.                                                                                                 |
| <strong>Job Completion Success Rate 99.0% Rolling 30 Days</strong>       | 99.0% / 30d           | Tracks batch Jobs (ETL, backups, pipelines, scheduled tasks) completing without failed pods over the rolling window.                                                                                                                              |</p>
<p>Each is a <strong>timeslice-metric SLO</strong>: Elastic marks every five-minute window good or bad, then rolls those results into a <strong>30-day rolling</strong> objective per namespace and workload.</p>
<p>Reliability is scored at two levels. Each five-minute slice gets one verdict: Elastic aggregates OTel metrics in that window, evaluates the template equation, and compares the result to the metric threshold. For Deployments, that is <code>sum(available) / sum(desired) &gt;= 1</code>. At a ~30-second OTel scrape cadence, that is roughly ten measurements per slice, and the slice passes or fails on the aggregated result. The SLO target (99.5% or 99.0%) is the share of slices that must pass across the rolling window. Over 30 days at five-minute slices, that is 8,640 possible slices per workload (30 × 24 × 12). After you create an SLO from a template, the SLO detail view shows how many slices passed and how much error budget remains.</p>
<p>At 99.5%, a workload can miss roughly 43 of those slices (~3.6 hours of bad slices) before breach. At 99.0%, about 86 slices (~7.2 hours).</p>
<h3 id="howtosetslotargetsbykubernetesworkloadtype">How to set SLO targets by Kubernetes workload type</h3>
<p>We picked defaults per workload type, not one number for the whole cluster.</p>
<p><strong>Deployments and StatefulSets at 99.5%:</strong> We considered 99.9% (~43 minutes per month), which fits a single critical API or a formal SLA buffer. For a default integration template across many Deployments, 99.5% (~3.6 hours) leaves room for normal rollout churn: a 20-minute bad image tag is roughly half the monthly budget at 99.9%, but a small fraction at 99.5%. Tune per workload; payment paths often warrant 99.9% or higher.</p>
<p><strong>DaemonSets and Jobs at 99.0%:</strong> We considered 99.5% for DaemonSets, but node additions, replacements, and rolling updates often leave <code>ready_nodes</code> below <code>desired_scheduled_nodes</code> for several minutes per event. At 99.5%, that normal platform churn would burn error budget on infrastructure agents (log collectors, monitoring, CNI) as if they were user-facing outages. 99.0% (~7.2 hours) absorbs that lifecycle noise. Jobs get 99.0% for a different reason: a failed ETL run usually hurts data freshness, not live request availability, and failures can sit unnoticed until downstream teams see stale reports.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b8c0979458feafa/6a8ea15fd98b5fb81fb15483/k8s-workload-resources.png" alt="Kubernetes observability with Elastic, Workload resources dashboard showing Deployments, DaemonSets, StatefulSets, Jobs, and ReplicaSets with availability and replica metrics" /></p>
<h3 id="deploymentreplicaavailability995">Deployment replica availability (99.5%)</h3>
<pre><code>Metric:     sum(k8s.deployment.available) / sum(k8s.deployment.desired) &gt;= 1
Target:     99.5% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.deployment.name
</code></pre>
<p>When <code>available &lt; desired</code>, the application runs fewer healthy replicas than configured. Failed rollouts, crash loops, and node loss all show up here. <strong>99.5%</strong> leaves roughly <strong>3.6 hours</strong> of degradation per deployment per month before breach.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd80a6e2a042b37b6/6a8ea162cf026e84110c9d16/slo-detail-deployment-healthy.png" alt="Kubernetes observability with Elastic, grid of Deployment Replica Availability 99.5% Rolling 30 Days SLO cards at 100% for default and kube-system namespaces" /></p>
<p>Grouping by namespace and deployment name creates one SLO per workload. A cluster-wide average would let a healthy <code>frontend</code> mask a burning <code>checkout</code>. Linked dashboards (<strong>Overview</strong> and <strong>Workloads</strong>) connect the SLO view to investigation context in one click; from Workloads you drill into <strong>Deployment Detail</strong> for the failing deployment.</p>
<h3 id="statefulsetreplicaavailability995">StatefulSet replica availability (99.5%)</h3>
<pre><code>Metric:     sum(k8s.statefulset.ready_pods) / sum(k8s.statefulset.desired_pods) &gt;= 1
Target:     99.5% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.statefulset.name
</code></pre>
<p>When <code>ready_pods &lt; desired_pods</code>, the StatefulSet reports fewer Ready replicas than configured. Ordered rollouts, stuck pods, and node loss show up here too. Rollouts proceed in order, and each pod keeps its name and volume, so a missing replica can stay below desired longer than a stateless pod would.</p>
<p>Grouping by namespace and StatefulSet name avoids a healthy workload masking another that is burning the SLO budget. </p>
<h3 id="daemonsetschedulingavailability990">DaemonSet scheduling availability (99.0%)</h3>
<pre><code>Metric:     sum(k8s.daemonset.ready_nodes) / sum(k8s.daemonset.desired_scheduled_nodes) &gt;= 1
Target:     99.0% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.daemonset.name
</code></pre>
<p>DaemonSets run node-level infrastructure: log collectors, monitoring agents, security agents, and network plugins. When <code>ready_nodes &lt; desired_scheduled_nodes</code>, an eligible node lacks a Ready pod, which can leave that node without logs or metrics from that agent. Rolling updates and new nodes drive most gaps; pods that never become Ready show the same signal. Cordoned nodes often still run DaemonSet pods. 99.0% (~7.2 hours per month) reflects that churn. </p>
<p>Group by namespace and DaemonSet name so a healthy <code>fluentd</code> does not mask a broken <code>node-exporter</code> on the same SLO budget.</p>
<h3 id="jobcompletionsuccessrate990">Job completion success rate (99.0%)</h3>
<pre><code>Metric:     max(k8s.job.failed_pods) &lt;= 0
Target:     99.0% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.job.name
</code></pre>
<p>Jobs cover batch workloads: ETL pipelines, backups, database migrations, and scheduled reports. When <code>failed_pods &gt; 0</code>, at least one pod created by the Job reached the <strong>Failed</strong> phase. Application errors, timeouts, and missing dependencies drive many failures; when retries reach the configured <code>backoffLimit</code>, Kubernetes marks the Job as <strong>Failed</strong>. Missed runs often surface as stale or delayed data, not as a serving outage. 99.0% (~7.2 hours per month) reflects that occasional batch failure is less time-sensitive than a Deployment or StatefulSet breach. </p>
<h2 id="howdoslosandalertsworktogetherinkubernetesobservability">How do SLOs and alerts work together in Kubernetes observability?</h2>
<p>The SLO templates and the alert rules from <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> serve different people asking different questions at different times.</p>
<p>| <strong>SLO Template</strong>                  | <strong>Alert rule (Part 1)</strong>                                                 | <strong>Failure consequence</strong>                                          | <strong>Monthly budget (30d)</strong> |
| --------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------ |
| Deployment Replica Availability   | Deployment below the desired replicas                                   | Reduced throughput, degraded UX                                  | ~3.6 hours at 99.5%     |
| StatefulSet Replica Availability  | No dedicated rule. Covered by CrashLoopBackOff / OOMKilled at pod level | Split-brain risk, degraded durability                            | ~3.6 hours at 99.5%     |
| DaemonSet Scheduling Availability | Pod stuck in Pending / node disk pressure                               | Blind spots: unmonitored nodes and gaps in node-level coverage   | ~7.2 hours at 99.0%     |
| Job Completion Success Rate       | CrashLoopBackOff / OOMKilled                                            | Stale or incomplete data                                         | ~7.2 hours at 99.0%     |</p>
<p>Alert rules answer: <em>Is something broken right now?</em> They fire within minutes, page the on-call engineer, and expect immediate action.</p>
<p>SLO templates answer: <em>Are we meeting our reliability commitments over time?</em> They accumulate signal across weeks and turn prioritisation debates into a number tied to remaining budget.</p>
<h3 id="fromincidenttoerrorbudgetburnakuberneteswalkthrough">From incident to error budget burn: a Kubernetes walkthrough</h3>
<p>A deployment drops from <code>3/3</code> to <code>2/3</code> available replicas during a rolling update. The new pod fails its readiness probe. Here is what happened in our test cluster, from dashboard signal through alert, SLO impact, and root cause.</p>
<p><strong>Rollout begins.</strong> The Deployment dashboard shows <code>available: 2, desired: 3</code>. The Part 1 <strong>Deployment unavailable replicas</strong> rule has a <strong>5-minute</strong> grace period, so on-call is not paged yet during a short rollout gap. The Deployment Detail view for <code>web-frontend</code> shows available replicas dropping while desired stays at 3. The Deployment replicas over time chart marks where the rollout started to fail.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d6ad2f74e0a59bd/6a8ea166f65645658a54b8e7/k8s-workdload-replicaset-drop.png" alt="Kubernetes observability with Elastic, Workload resources view for web-frontend in blog-demo at 66.67% availability with available replicas at 2 of 3 desired and the replicas-over-time chart showing the drop" /></p>
<p><strong>Alert fires, then root cause.</strong> After the grace period, the alert rule triggers: <em>Deployment unavailable replicas</em>. The on-call engineer opens the Workloads dashboard, finds <code>web-frontend</code> at <code>available: 2, desired: 3</code>, and drills into Deployment Detail. The replicas-over-time chart confirms when availability dropped. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb63a9993f704b20/6a8ea169b4c43ed5190f14ae/k8s-replica-alert-trigger.png" alt="Kubernetes observability with Elastic, Deployment unavailable replicas alert rule showing active alerts after the replica drop" /></p>
<p>In <strong>Discover</strong>, filter Kubernetes events for that pod with <code>k8s.object.name: "web-frontend-796fcd55b9-jmlkh"</code>. The event stream shows <code>ImagePullBackOff</code> and <code>Back-off pulling image "nginx:nonexistent-tag-999"</code>. The rollout references an image tag that does not exist.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt154dbe907e7b8e68/6a8ea16c76e02a2ba5fd5485/k8s-discoverview-image-error.png" alt="Kubernetes observability with Elastic, Discover view showing ImagePullBackOff events for the web-frontend pod after a bad image tag" /></p>
<p><strong>Rollback and recovery.</strong> The engineer runs <code>kubectl rollout undo deployment/web-frontend</code>. Replicas return to <code>3/3</code>.</p>
<h3 id="howtworolloutsconsumed88ofa30dayerrorbudget">How two rollouts consumed 88% of a 30-day error budget</h3>
<p>The rollback fixed availability. The SLO still counted the day's failures.</p>
<p>Two rollout failures left <code>web-frontend</code> with <strong>39 failed timeslices</strong> where <code>available &lt; desired</code>. That consumed <strong>88.0%</strong> of the <strong>30-day error budget</strong>. The SLI still read <strong>99.56%</strong>, above the <strong>99.5%</strong> target, but only <strong>12%</strong> of the monthly allowance remained.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc7ae205fd2e6375/6a8ea16fbc5bb344a3f93c7e/k8s-slo-webserver-overview.png" alt="Kubernetes observability with Elastic, Deployment Replica Availability SLO for web-frontend showing SLI above target with most of the error budget already consumed" /></p>
<p>The burn rate alert fired next, even though replicas were healthy again. Over the past day the deployment consumed budget at <strong>26×</strong> the rate a <strong>99.5%</strong> SLO can sustain long term. Each failed 5-minute timeslice uses roughly <strong>2.3%</strong> of the monthly budget (about <strong>43</strong> failures allowed per 30 days). Thirty-nine failures across two rollouts is worth a reliability review, not a one-line postmortem. The burn rate alert often matters more than the raw SLI mid-month because it fires while you still have budget left to spend deliberately.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e36052030c8b486/6a8ea1723a56ed4c1938fc79/slo-burnrate-alert.png" alt="Kubernetes observability with Elastic, Alerts page showing an active critical burn rate alert for the web-frontend Deployment Replica Availability SLO" /></p>
<h2 id="tryityourselftriggeranerrorbudgetburnonatestdeployment">Try it yourself: trigger an error budget burn on a test Deployment</h2>
<p>If you already have <strong>Kubernetes OpenTelemetry Assets</strong> installed, the SLO templates live under <strong>Integrations → Kubernetes OpenTelemetry → Assets</strong>.</p>
<p>Create a <strong>Deployment replica availability</strong> SLO for the deployment you use below. Open the SLO and note the baseline: current SLI, remaining error budget, and existing timeslice history.</p>
<p>Create an isolated namespace and a small deployment so the exercise does not affect production workloads. Wait a few minutes for the OTel collector to scrape metrics before you create the SLO.</p>
<pre><code>kubectl create namespace blog-demo
kubectl create deployment web-frontend --namespace blog-demo --image=nginx:latest --replicas=3
</code></pre>
<p>Trigger a bad rollout with a non-existent image tag:</p>
<pre><code>kubectl get deployment web-frontend -n blog-demo
kubectl set image deployment/web-frontend nginx=nginx:nonexistent-tag-999 --namespace blog-demo
</code></pre>
<p>Within a few minutes a new pod enters <code>ImagePullBackOff</code>, available replicas drop below desired, and the SLO records failed timeslices. Roll back to recover:</p>
<pre><code>kubectl rollout undo deployment/web-frontend -n blog-demo
</code></pre>
<p>Refresh the SLO view. You should see new failed timeslices in the 30-day history and a reduction in remaining error budget.</p>
<p>One failed timeslice consumes about <strong>2.3%</strong> of the monthly error budget at <strong>99.5%</strong>. Repeat that across deployments in a week and the burn rate alert becomes the prioritisation signal.</p>
<p>When you are done, delete the test namespace with:</p>
<pre><code>kubectl delete namespace blog-demo
</code></pre>
<h2 id="whatsnextfromslomonitoringtoagenticremediation">What's next: from SLO monitoring to agentic remediation</h2>
<p>Alerts tell you replicas dropped. SLOs tell you how much monthly budget that cost. In the walkthrough above, the same ImagePullBackOff showed up in Deployment Detail, the unavailable-replicas alert, and failed timeslices on the replica-availability SLO, all from the OTel pipeline you installed in <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a>. The SLI still read <strong>99.56%</strong> while <strong>88%</strong> of the monthly error budget was gone.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> closed by previewing <strong>Agentic Investigations</strong>: investigation workflows that run when an alert fires, with skills, tools, and MCP views. This post adds the SLO layer on those same metrics so you can quantify reliability debt before automating runbooks. A follow-up post will cover that agentic workflow and propose remediations you review before applying.</p>
<p>Which remediations would you trust a workflow to suggest on a Kubernetes incident, and which would you keep manual? <a href="https://discuss.elastic.co/c/observability">Join the Elastic Community discussion</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kubernetes-observability-slo-error-budget-templates</link>
    <guid isPermaLink="false">kubernetes-observability-slo-error-budget-templates</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Agi K Thomas]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf5fe3ce01efa1f7/6a8ea17576e02a4eccfd5489/kubernetes-observability-slo-error-budget-templates.png" length="0" type="image/png"/>
    <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Correlate logs, metrics, and traces in one ES|QL query]]></title>
    <description><![CDATA[Walk through four investigations, from CPU saturation to pod memory pressure, each answered by a single query across signal types.]]></description>
    <content:encoded><![CDATA[<p>ES|QL can now filter one observability signal by the live result of a query against another.
<a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-in-subquery"><code>WHERE field IN (subquery)</code></a> is in technical preview in Elastic Stack 9.5 and on Elastic Cloud Serverless, and because these subqueries nest, one query can reach across logs, metrics, and traces at the same time.</p>
<p>The gain is in where the intermediate set lives.
When you ask what the saturated hosts logged, the list of saturated hosts is computed and consumed inside Elasticsearch.
Six host names or 500 trace IDs never land in a clipboard or in an AI agent's context window, and the set is recomputed from current data every time the query runs.</p>
<p>That changes the unit of investigation.
You go from "one slow request stalled on a lock" to "most of the slowest requests did," and only the second answer tells you which team to page.
It matters more the further apart your signals are: in many observability stacks, logs, metrics, and traces live in three separate systems, each with its own query language, its own time picker, and its own idea of what a host is, so the same question has to be asked two or three times and the answers joined by hand.</p>
<p>In this post we walk through four investigations, each of which is self-contained.
For every one of them, we set out the scenario that started it, the query that answers it, the table it returns, and a note on the difficulties you run into when you try to get the same answer any other way.</p>
<p>| Pattern | Starts from | Question it answers |
|---|---|---|
| Metrics to logs | CPU saturation | Which error patterns show up only on the saturated hosts? |
| Logs to metrics | Error logs | Do the erroring hosts look any different from the healthy ones? |
| Traces to logs | Slow spans | What did every service log during those specific requests? |
| All three signals | Pod memory pressure | Which log lines sit behind the requests that failed under that pressure? |</p>
<p>The data was collected with <a href="https://www.elastic.co/docs/reference/opentelemetry">OpenTelemetry</a> and lands in the <code>logs-*.otel-*</code>, <code>traces-*.otel-*</code>, and <code>metrics-*.otel-*</code> data streams, where fields keep their <a href="https://opentelemetry.io/docs/specs/semconv/">semantic convention</a> names rather than being rewritten into another schema.
The correlation pattern works just as well on Elastic Agent integrations, though the queries need translating rather than just renaming: ECS carries log severity as the text field <code>log.level</code> instead of a numeric <code>severity_number</code>, the System integration reports CPU as separate <code>system.cpu.*.pct</code> fields instead of one metric with a state dimension, and APM records durations in microseconds.
All of it lands in the same cluster either way, which is the part the subquery depends on.</p>
<p>In <a href="https://www.elastic.co/docs/explore-analyze/discover">Discover</a>, the time picker already applies the range, so the examples below omit an explicit <code>@timestamp</code> filter.
Outside Discover, add a filter by time yourself, either with literal timestamps in the query or with <code>?_tstart</code> and <code>?_tend</code> in the query and values in the <code>params</code> array of your <code>_query</code> request.
Every result below comes from a one hour window over a synthetic fleet of 300 hosts.</p>
<h2 id="metricstologswhatarethesaturatedhostscomplainingabout">Metrics to logs: what are the saturated hosts complaining about?</h2>
<p>An infrastructure alert tells you a handful of hosts in a fleet of a few hundred sat above 90% CPU over the last hour.
That tells you which hosts are hot and nothing about why.
The question worth answering is whether those hosts share a failure mode, or whether they are busy for unrelated reasons and the alert is a coincidence.</p>
<pre><code>FROM logs-*.otel-*
| WHERE severity_number &gt;= 17
  AND resource.attributes.host.name IN (
      TS metrics-hostmetrics.otel-*
      | WHERE attributes.state == "idle"
      | STATS idle = AVG(AVG_OVER_TIME(metrics.system.cpu.utilization))
          BY resource.attributes.host.name
      | WHERE idle &lt; 0.1
      | KEEP resource.attributes.host.name
    )
| STATS errors = COUNT(*), hosts = COUNT_DISTINCT(resource.attributes.host.name)
    BY pattern = CATEGORIZE(body.text), service = resource.attributes.service.name
| SORT errors DESC
</code></pre>
<p>The two halves of the query map onto the two halves of the question.
The subquery works out which hosts were saturated, averaging CPU utilization per host and keeping the ones that averaged under 10% idle, which is another way of saying above 90% busy for the window.
The outer query then works out what those hosts were complaining about, pulling their error logs and using <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions/categorize"><code>CATEGORIZE</code></a> to collapse thousands of individual lines into a handful of error classes.</p>
<p>Two choices in there are worth pausing on.</p>
<p>The subquery uses <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> rather than <code>FROM</code> because a host does not report one CPU number.
It reports a separate time series per CPU state, and per logical core as well if your collector is configured to break them out, so the reduction has to happen in two stages.
<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions/avg_over_time"><code>AVG_OVER_TIME</code></a> collapses each series to a single value first, and the outer <code>AVG</code> then combines those into one figure per host.</p>
<p>Naming that inner function matters more than it looks.
Write <code>AVG(metrics.system.cpu.utilization)</code> on its own and <code>TS</code> supplies <code>LAST_OVER_TIME</code> for you, averaging each series' final sample rather than its average over the window.
In this dataset that one substitution moves a host from 7% idle to 10% idle, which is the difference between appearing in the results and not.
<a href="https://www.elastic.co/observability-labs/blog/esql-ts-command-querying-metrics">Querying metrics with the TS command</a> goes into the two aggregation phases in more depth.</p>
<p>The log filter tests <code>severity_number</code> (17 is the ERROR floor on the OpenTelemetry scale) rather than the severity text, because the numeric scale is fixed by the spec while the text is whatever the emitting library decided to write.
That is not a hypothetical distinction here: the error logs in this cluster carry four different labels, including <code>SEVERE</code> from a Java service.
Matching on the text alone returns 2,754 of checkout's errors and misses the billing service entirely, while the numeric filter returns all 5,910 of them and keeps billing too.</p>
<p>When you run the query in Discover, the result is a short table of error classes, scoped to the saturated hosts:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a7ec0258240c9d3/6a85caf327c5cd90465f73f4/metrics-to-logs-result.jpg" alt="Discover ES|QL results showing error log patterns grouped by CATEGORIZE for hosts above 90% CPU" /></p>
<p>The subquery returned six saturated hosts, and on all six the same service is timing out against an upstream dependency and draining its connection pool.
The <code>hosts</code> column is what lets you set the other two rows aside without opening anything: the certificate errors reach only two of the six, and the gateway declines amount to five lines in an hour.
Neither tracks the cohort the way the checkout patterns do.</p>
<p>Having all three signals in one store already removed the exports from this investigation.
The subquery removes the step after that, and closing that last gap matters more than it sounds.
By the time you have read six host names off a chart and typed them into a log search, the set has moved: a host that crossed the threshold a minute ago is missing from your list, and one that has since recovered is still in it.
Here the host list is derived from current data on every run, so re-running the query during an incident gives you the current cohort.</p>
<p>The stale list is only half of it.
A correlation done by hand exists only in the head of the person who did it, so nobody else can check it, save it, or run it again tomorrow.</p>
<h2 id="logstometricsdotheerroringhostslookdifferentfromthehealthyones">Logs to metrics: do the erroring hosts look different from the healthy ones?</h2>
<p>Filtering metrics by a log-derived host set answers the opposite question.
The payments service is throwing errors on some hosts and not others, and you want to know whether resource pressure explains the split before you start reading deploy history.</p>
<p>That is a comparison, so the query needs both cohorts.
<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a> runs two branches over the same input, and <code>IN</code> and <code>NOT IN</code> against the same log-derived host set divide the fleet between them.</p>
<pre><code>TS metrics-hostmetrics.otel-*
| WHERE attributes.state == "idle"
| STATS idle = AVG(AVG_OVER_TIME(metrics.system.cpu.utilization))
    BY host = resource.attributes.host.name
| EVAL busy = 1 - idle
| FORK
    ( WHERE host IN (
        FROM logs-*.otel-*
        | WHERE severity_number &gt;= 17
          AND resource.attributes.service.name == "payments"
          AND resource.attributes.host.name IS NOT NULL
        | STATS errors = COUNT(*) BY resource.attributes.host.name
        | KEEP resource.attributes.host.name )
      | EVAL cohort = "logging errors" )
    ( WHERE host NOT IN (
        FROM logs-*.otel-*
        | WHERE severity_number &gt;= 17
          AND resource.attributes.service.name == "payments"
          AND resource.attributes.host.name IS NOT NULL
        | STATS errors = COUNT(*) BY resource.attributes.host.name
        | KEEP resource.attributes.host.name )
      | EVAL cohort = "no errors" )
| STATS hosts = COUNT(*), mean_busy = AVG(busy), busiest_host = MAX(busy)
    BY cohort
</code></pre>
<p>The query reads top to bottom as three stages.
The metrics query runs first and reduces the whole fleet to one busy figure per host.
<code>FORK</code> then splits that fleet in two using the same log query in both branches, separating the hosts that appear in it from the hosts that do not.
The final <code>STATS</code> summarizes each group, so both cohorts come back as two rows of one table, measured the same way over the same window.</p>
<p>This query is longer than the others, and three parts of it are less obvious than they look.</p>
<p>The natural way to label the two cohorts would be <code>EVAL cohort = CASE(host IN (...), "erroring", "healthy")</code>, and ES|QL rejects it.
In 9.5 an <code>IN</code> subquery has to be a top-level predicate in a <code>WHERE</code> condition rather than an argument to a scalar function, which is why the split happens at the command level with <code>FORK</code>.</p>
<p>The <code>STATS ... BY</code> inside each subquery looks redundant, since <code>KEEP</code> alone would return the same host names.
It is not: without it, the subquery returns one row per matching log document instead of one row per host, and those rows are all held in memory for the outer query to filter against.
Aggregating first turns millions of rows into a few hundred host names.</p>
<p>The <code>IS NOT NULL</code> filter guards the sharpest edge here, and this dataset is a live example rather than a hypothetical.
<code>NOT IN</code> follows SQL null semantics, so a single null in the subquery result makes the predicate match nothing at all.
Five of the payments error logs in this cluster came through a sidecar that dropped the host name.
Remove that one line from both branches and the query still succeeds, but it returns a single row: the nulls quietly delete the entire 286-host "no errors" cohort, and what is left looks like a perfectly plausible answer to a different question.</p>
<p>Run the query in Discover and you get two rows, one per cohort:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt867c2fae8e2b1116/6a85caf5abdc29f2f11224fc/cohort-comparison.jpg" alt="Discover ES|QL results comparing mean and peak CPU for hosts logging errors against hosts with no errors" /></p>
<p>CPU does not explain the split, and the numbers say so twice.
The 14 erroring hosts run slightly cooler on average than the 286 quiet ones, and the busiest machine among them averaged 50% over the hour while the quiet cohort contains a host that averaged 95%.
Whatever is failing on those 14, they had headroom the entire time, and deploy history is a better place to spend the next ten minutes.</p>
<p>A negative result like this is worth as much as a positive one, and it is usually the one people skip.
Getting it the long way means running the metrics query twice against two hand-built host lists and lining the numbers up afterwards, which is enough friction that the check often just does not happen.
Both cohorts here come from the same log query in the same execution, over identical time windows, so there is nothing to reconcile and no reason not to check.</p>
<h2 id="tracestologswhatdideveryservicelogduringtheslowrequests">Traces to logs: what did every service log during the slow requests?</h2>
<p>A <a href="https://www.elastic.co/observability-labs/blog/slo-burn-rate-analysis-trace-investigation">service level objective (SLO) burn alert</a> fires on checkout latency.
Tracing gives you the slow requests and their spans, and the next question is what the services involved were writing to their logs while those specific requests were in flight.</p>
<p>The trace ID is the join key, and there are far too many of them to move by hand.</p>
<pre><code>FROM logs-*.otel-*
| WHERE trace_id IN (
      FROM traces-*.otel-*
      | WHERE kind == "Server"
        AND resource.attributes.service.name == "checkout"
        AND name == "POST /api/orders"
        AND duration &gt; 2000000000
      | SORT duration DESC
      | LIMIT 500
      | KEEP trace_id
    )
| STATS lines = COUNT(*), traces = COUNT_DISTINCT(trace_id)
    BY pattern = CATEGORIZE(body.text),
       service = resource.attributes.service.name,
       severity_text
| SORT traces DESC
</code></pre>
<p>The subquery answers "which requests were slow."
It looks at the inbound request span for the checkout endpoint rather than the client and internal spans beneath it, then keeps the 500 slowest requests over two seconds.
Durations are recorded in nanoseconds, which is why the threshold has so many zeros.</p>
<p>The outer query answers "what got logged while they were running," gathering every log line that shares one of those trace IDs and grouping them into patterns.</p>
<p>Counting distinct traces per pattern is what makes the output readable.
A log pattern that appears 30,000 times across four traces is one chatty request, while a pattern that shows up in 470 of the 500 slowest traces is a property of being slow.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d7d39a09bb47dec/6a85caf8f9373d09d496f594/slow-trace-log-patterns.jpg" alt="Discover ES|QL results ranking log patterns by how many of the 500 slowest checkout requests they appear in" /></p>
<p>From the results above, the top row is there by construction and can be set aside: checkout writes one <code>order submitted</code> line per order, so it appears in all 500 traces and says nothing about why these particular 500 were slow.</p>
<p>From the results above, the third row is the answer, and it points at a service nobody was looking at.
A lock wait timeout in inventory, two hops downstream from where the alert fired, shows up in 470 of the 500 slowest requests, and the 947 lines behind those 470 traces mean a good share of them retried more than once.
The payment gateway declines are real failures, and at 19 traces out of 500 they are not what is burning the SLO.</p>
<p>Done by hand, this means opening slow traces one at a time and reading the correlated logs for each, which is tedious at ten traces and nobody's idea of a plan at 500.
When the spans and the logs are held in different systems, every trace you check is a copied ID and a context switch, and the sample size you can afford drops to about three.
Three traces is enough to form a theory and not enough to test one.
Treating the slow requests as a population is what turns "this trace had a lock wait" into "470 of the 500 slowest requests had a lock wait," and that difference decides whether you page the inventory team.</p>
<h2 id="allthreesignalsfrompodmemorypressuretotheloglinesbehindthefailures">All three signals: from pod memory pressure to the log lines behind the failures</h2>
<p><code>IN</code> subqueries nest, so the pattern extends to as many signal types as the question needs.</p>
<p>A node pool starts reporting memory pressure after a rollout.
You want the log lines from the requests that actually failed on the pods under pressure, which means going from metrics to traces to logs without stopping in between.</p>
<pre><code>FROM logs-*.otel-*
| WHERE trace_id IN (
      FROM traces-*.otel-*
      | WHERE kind == "Server"
        AND status.code == "Error"
        AND resource.attributes.k8s.pod.uid IN (
            TS metrics-kubeletstats.otel-*
            | STATS peak = MAX(MAX_OVER_TIME(metrics.k8s.pod.memory_limit_utilization))
                BY resource.attributes.k8s.pod.uid
            | WHERE peak &gt; 0.95
            | KEEP resource.attributes.k8s.pod.uid
          )
      | STATS failures = COUNT(*) BY trace_id
      | SORT failures DESC
      | LIMIT 1000
      | KEEP trace_id
    )
| STATS lines = COUNT(*), traces = COUNT_DISTINCT(trace_id)
    BY pattern = CATEGORIZE(body.text), service = resource.attributes.service.name
| SORT traces DESC
</code></pre>
<p>Reading the query inside out, you can see each layer answering one part of the question.
The innermost subquery identifies the pods whose memory peaked above 95% of their limit, using <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions/max_over_time"><code>MAX_OVER_TIME</code></a> to take each pod's peak rather than its average.
The middle one narrows to the requests that actually failed on those pods and reduces them to at most 1,000 trace IDs.
The outer query then collects the logs for those traces from every service that took part, including services running on pods that were entirely healthy, and that last part turns out to be where the answer is.</p>
<p>The pods are matched on their UID rather than their name, because names repeat across namespaces and restarts.
The query assumes Kubernetes metadata reaches your spans, which is what the collector's <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor"><code>k8sattributes</code> processor</a> is for; if it does not, the host or container ID works the same way.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3aa0104b29390bd2/6a85cafa07829040c5321766/three-signal-result.jpg" alt="Discover ES|QL results tracing Kubernetes pod memory pressure through failed spans to the log patterns behind them" /></p>
<p>From the results above, the first row sits at exactly 1,000 because that is the subquery's <code>LIMIT</code>, so it describes the size of the sample rather than the size of the incident.
Every trace in that sample carries the cart deadline line, which is the symptom you already knew about when you started.</p>
<p>Look at the second row instead.
<code>product-catalog</code> is rejecting oversized payloads across 946 of the same 1,000 traces, and it never appeared in the pod subquery at all: its pods peaked at 75% of their memory limit, well under the 95% threshold.
The rollout started sending larger payloads, which would account for both cart's memory climb and the failures.
Checkout's retry budget gives out in about half of them, which is how the failure became visible to users.</p>
<p>Filtering logs directly by the pressured pods would have shown you the cart line and hidden the product-catalog one, which is to say it would have confirmed the symptom and buried the cause.
Doing it without subqueries means three queries and two hand-built lists, and the second list is a thousand trace IDs.
That is usually the point at which people stop after the first hop and go with the cart theory.
The reason the second hop is cheap here is that all three signals sit in the same store behind the same query language, so widening from pods to traces to every service in the trace is a clause, not a project.</p>
<h2 id="whydoesqlsubqueriesmatterforaiagents">Why do ES|QL subqueries matter for AI agents?</h2>
<p>Keeping the intermediate set inside the cluster is convenient for a person and close to essential for an agent querying on your behalf.</p>
<p>Split across two tool calls, the intermediate result has to travel.
A list of 500 trace IDs comes back in a tool response and occupies the model's context, and the agent then has to rewrite every one of them into the next query.
That costs tokens on every hop, and it is where truncation and transcription errors come from.
With a subquery, the intermediate set stays inside Elasticsearch and the agent only ever sees the final table.</p>
<p>The problem compounds when the signals are spread across systems.
An agent then needs credentials, a client, and a working knowledge of the query language for each one, plus the judgment to join results that use different names for the same host.
One store and one query language reduce that to a single skill the agent has to be good at.</p>
<p>One ES|QL string is also a complete description of the correlation, which makes the investigation reproducible: an agent can put the query in its summary, and a human can paste it into Discover and get the same logic evaluated against current data.
A two-call sequence with a hardcoded host list in the middle gives you neither.
The one thing to watch is that an agent calling the <code>_query</code> API has to filter <code>@timestamp</code> itself, since nothing is binding a time picker.</p>
<h2 id="fourthingstoknowbeforewritingesqlinsubqueries">Four things to know before writing ES|QL IN subqueries</h2>
<p><code>IN</code> subqueries are in technical preview in Elastic Stack 9.5 and on Elastic Cloud Serverless, while <code>TS</code> and <code>FORK</code> have been generally available since 9.4.
<code>CATEGORIZE</code> has been generally available since 9.1 and requires a <a href="https://www.elastic.co/subscriptions">Platinum license</a>; every query above works without it if you group by an existing field instead.</p>
<p>Four things are worth knowing before you write your own:</p>
<ul>
<li>In 9.5 the subquery returns exactly one column, which is what the trailing <code>KEEP</code> does in each example.</li>
<li>Aggregate the subquery down to distinct values with <code>STATS ... BY</code> before returning them.
Its result is materialized for the outer query to filter against, so handing back a few hundred host names instead of a few million rows is both faster and safer.</li>
<li>Filter nulls out of any <code>NOT IN</code> subquery, because SQL null semantics mean one null makes the predicate match nothing.</li>
<li>Subqueries are non-correlated.
They run independently and cannot reference columns from the outer query, so this is a set filter rather than a row-by-row join.
Reach for <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a> when you need per-row enrichment, which we covered in <a href="https://www.elastic.co/observability-labs/blog/elastic-esql-join-observability">ES|QL joins for richer observability</a>.</li>
</ul>
<h2 id="tryesqlsignalcorrelationonyourowndata">Try ES|QL signal correlation on your own data</h2>
<p>The pattern under all four examples is the same.
You start with a set you can describe in one signal and a question you can only answer in another, and the subquery carries that set across the boundary for you.</p>
<p>The syntax is the smaller part of what makes that work.
It works because logs, metrics, and traces sit in <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">one store behind one query engine</a>, under field names they kept on the way in, so crossing from one signal to another is a clause in a query rather than an integration to build and maintain.
Where that is not true, the same four investigations turn into a sequence of exports, translations, and manual joins, and that costs more than slower answers.
It quietly shrinks the number of questions anyone is willing to ask, and the negative results are the first to go.</p>
<p>Each pattern here replaces two or three queries with one, and no host list or trace ID list has to move between them.
Fewer steps mean fewer places to be wrong, and a correlation you can save as a single string and hand to someone else.</p>
<p>To try it:</p>
<ol>
<li>Open an <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Observability project on Elastic Cloud Serverless</a>, or upgrade to Elastic Stack 9.5.</li>
<li>Send data with the <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry</a>, or point an existing collector at Elasticsearch.</li>
<li>In <strong>Discover</strong>, switch to ES|QL and start from the metrics to logs query above, swapping in your own data streams and thresholds.</li>
<li>Read the <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-in-subquery"><code>IN</code> subquery reference</a> for the full set of commands you can use inside a subquery.</li>
</ol>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-subqueries-correlate-logs-metrics-traces</link>
    <guid isPermaLink="false">esql-subqueries-correlate-logs-metrics-traces</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar,Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt746d25b6fd650a97/6a85cafe4710c625d2d3cb3d/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 14 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[One edit, every dashboard updated: managing Kibana observability at scale with Terraform]]></title>
    <description><![CDATA[Define your golden-signals panels once in a shared HCL library and use for_each to generate every team's dashboard, with drift detection and git rollback built in.]]></description>
    <content:encoded><![CDATA[<p>Elastic ships a Kibana Dashboards API and a native Terraform resource for managing dashboards as code. This capability was introduced as a technical preview in Elastic 9.4 and was made generally available in Elastic 9.5. You define a golden signals panel library once in HCL, and <code>for_each</code> generates a dashboard for every team from it. When you need to change an error threshold, a panel layout or a query, one pull request updates every team at once. If something drifts or breaks, you roll back with git.</p>
<h2 id="whymanagingobservabilitydashboardsbyhandbreaksdownatscale">Why managing observability dashboards by hand breaks down at scale</h2>
<p>Large organizations often end up with hundreds of dashboards. Teams build similar panels and maintain them using the Kibana UI.</p>
<p>When a small change comes in (a panel rename, a field fix, a new error threshold), there is no easy way to apply it across all of them. You either open each dashboard and edit it in the UI one by one, or you export the NDJSON, run a string replace, and re-import it.</p>
<h2 id="dashboardsarecodenow">Dashboards are code now</h2>
<p>Elastic ships a <a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">typed Kibana Dashboards API</a> and a native <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard"><code>elasticstack_kibana_dashboard</code></a> Terraform resource. You define a dashboard in an HCL file and then manage versions and changes as if it was regular code.</p>
<h2 id="goldensignalsdashboardonedefinitionforeveryteam">Golden signals dashboard: one definition for every team</h2>
<p>The platform team owns a standard dashboard built on the four <a href="https://sre.google/sre-book/monitoring-distributed-systems/#xref_monitoring_golden-signals">golden signals</a>: latency, traffic, errors, and saturation. Every team should get that standard, and some teams add a panel or two of their own.</p>
<p>We want one definition of the standard, each team's dashboard generated from it, and a single change that reaches every team.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud deployment or self-managed cluster running <strong>Elastic 9.4</strong> or newer, or an <strong>Elastic Cloud Serverless</strong> project</li>
<li><strong>Terraform</strong> installed</li>
<li>An Elasticsearch API key</li>
</ul>
<p>The full Terraform configuration, the seed script, and the captured <code>terraform plan</code> outputs used in this article are available in the <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform">companion repo</a>.</p>
<h2 id="configuretheelasticterraformprovider">Configure the Elastic Terraform provider</h2>
<p>Create a <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/provider.tf"><code>provider.tf</code></a> next to the rest of your Terraform files:</p>
<pre><code>terraform {
  required_providers {
    elasticstack = {
      source  = "elastic/elasticstack"
      version = "~&gt; 0.11"
    }
  }
}

variable "elasticsearch_endpoint" {
  type = string
}

variable "elasticsearch_api_key" {
  type      = string
  sensitive = true
}

variable "kibana_endpoint" {
  type = string
}

variable "kibana_api_key" {
  type      = string
  sensitive = true
}

provider "elasticstack" {
  elasticsearch {
    endpoints = [var.elasticsearch_endpoint]
    api_key   = var.elasticsearch_api_key
  }
  kibana {
    endpoints = [var.kibana_endpoint]
    api_key   = var.kibana_api_key
  }
}
</code></pre>
<p>Provide your credentials through a local <code>terraform.tfvars</code> file (and add it to <code>.gitignore</code> so the keys never reach the repo):</p>
<pre><code>elasticsearch_endpoint = "https://...es.region.cloud.es.io"
elasticsearch_api_key  = "..."
kibana_endpoint        = "https://...kb.region.cloud.es.io"
kibana_api_key         = "..."
</code></pre>
<p>You can use the same API key for both <code>elasticsearch_api_key</code> and <code>kibana_api_key</code> as long as it has dashboard write privileges in the target space.</p>
<p>Then initialize the working directory:</p>
<pre><code>terraform init
</code></pre>
<h2 id="defineasingleteamkibanadashboardinhcl">Define a single-team Kibana dashboard in HCL</h2>
<p>Start with a baseline dashboard for a single team. Panels sit on a 48-column grid, and each one is a <a href="https://www.elastic.co/docs/explore-analyze/visualize/lens">Lens</a> visualization configured inline. Use <code>config_json</code> for KPI tiles (it exposes secondary metrics and value coloring) and <code>xy_chart_config</code> for time-series charts.</p>
<p>Add the baseline resource to a new <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a>:</p>
<pre><code>resource "elasticstack_kibana_dashboard" "golden_signals" {
  title            = "Golden Signals - payments"
  description      = "Latency, traffic, errors"
  query            = { language = "kql", text = "" }
  refresh_interval = { pause = false, value = 60000 }
  time_range       = { from = "now-15m", to = "now" }

  panels = [
    {
      type = "vis"
      grid = { x = 0, y = 0, w = 12, h = 5 }
      config_json = jsonencode({
        type        = "metric"
        data_source = {
          type  = "esql"
          query = "FROM logs-payments-* | STATS `5xx errors` = COUNT(CASE(status &gt;= 500, 1, null))"
        }
        metrics = [{ type = "primary", column = "5xx errors" }]
      })
    },
    # More panels follow the same shape: other metric tiles, xy_chart_config line charts, and a breakdown datatable. See the companion repo for the full file.
  ]
}
</code></pre>
<p>Each panel sets a <code>type</code> and <code>grid</code> position, then picks one chart kind. KPI tiles serialize the whole Lens config into <code>config_json</code>; the <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> query lives under <code>data_source</code> and the metric column is referenced by name in <code>metrics[*].column</code>. The dashboard time picker already scopes ES|QL panels, so the query needs no explicit <code>@timestamp</code> range filter.</p>
<h3 id="previewkibanadashboardchangeswithterraformplan">Preview Kibana dashboard changes with terraform plan</h3>
<p>Run <code>terraform plan</code> to see what Terraform will create:</p>
<pre><code>terraform plan
</code></pre>
<p>The plan output lists the new <code>elasticstack_kibana_dashboard.golden_signals</code> resource and every attribute it will set: the top-level dashboard fields and one entry per panel with its grid position, chart kind, and data source.</p>
<pre><code>Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # elasticstack_kibana_dashboard.golden_signals will be created
  + resource "elasticstack_kibana_dashboard" "golden_signals" {
      + description      = "Latency, traffic, errors"
      + title            = "Golden Signals - payments"
      + query            = { language = "kql", text = "" }
      + refresh_interval = { pause = false, value = 60000 }
      + time_range       = { from = "now-15m", to = "now" }
      + panels           = [
          # Every panel described in full: KPI tiles (config_json),
          # line charts (xy_chart_config), and the breakdown datatable.
        ]
    }

Plan: 1 to add, 0 to change, 0 to destroy.
</code></pre>
<p>Reviewing the plan is your last check before anything ships to Kibana.</p>
<p>Don't apply yet. The next section extends the file with per-team dashboards, and then a single <code>terraform apply</code> ships everything.</p>
<h2 id="generateperteamobservabilitydashboardsfromasharedpanellibrary">Generate per-team observability dashboards from a shared panel library</h2>
<p>On top of the baseline, each team gets the standard set of panels, with the option to add a few of their own. Hardcoding one resource per team does not scale. Instead, define a panel library and a teams map as <code>locals</code>, then build the dashboards with <code>for_each</code>. Each library entry describes a chart kind, a title, and the data it needs; the resource emits the right Lens block (<code>config_json</code> for metric tiles, <code>xy_chart_config</code> for line charts) based on <code>chart_type</code>.</p>
<p>Replace the contents of <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a> with:</p>
<pre><code>locals {
  panel_library = {
    errors = {
      chart_type     = "metric"
      title          = "Error rate"
      esql_query_tpl = "FROM {idx} | STATS `5xx errors` = COUNT(CASE(status &gt;= 500, 1, null))"
      esql_column    = "5xx errors"
    }
    saturation = {
      chart_type     = "metric"
      title          = "Saturation (CPU)"
      # Saturation reads from the metrics TSDB, so this query is not parameterized by {idx}.
      esql_query_tpl = "TS metrics-payments-* | STATS avg_cpu = AVG(cpu.pct)"
      esql_column    = "avg_cpu"
    }
    latency = {
      chart_type = "xy"
      title      = "Latency p95"
      x_json     = jsonencode({
        operation          = "date_histogram"
        field              = "@timestamp"
        suggested_interval = "auto"
      })
      y_json = jsonencode({
        operation  = "percentile"
        field      = "duration_ms"
        percentile = 95
      })
    }
    # ... more entries (traffic, cart_value) in the companion repo.
  }

  teams = {
    payments = {
      index  = "logs-payments-*"
      panels = ["errors", "saturation", "latency", "traffic"]
    }
    checkout = {
      index  = "logs-checkout-*"
      panels = ["errors", "cart_value", "latency", "traffic"]
    }
  }
}

resource "elasticstack_kibana_dashboard" "golden_signals" {
  for_each         = local.teams
  title            = "Golden Signals - ${each.key}"
  description      = "Latency, traffic, and errors for the ${each.key} service"
  query            = { language = "kql", text = "" }
  refresh_interval = { pause = false, value = 60000 }
  time_range       = { from = "now-15m", to = "now" }

  sections = [
    {
      title     = "KPIs"
      grid      = { y = 0 }
      collapsed = false
      panels = [
        for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "metric"] : {
          type        = "vis"
          grid        = { x = (i % 4) * 12, y = 0, w = 12, h = 5 }
          config_json = jsonencode({ ... }) # one metric tile per panel; see the companion repo for the full config
        }
      ]
    },
    {
      title     = "Trends"
      grid      = { y = 1 }
      collapsed = false
      panels = [
        for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "xy"] : {
          type       = "vis"
          grid       = { x = (i % 3) * 16, y = 0, w = 16, h = 10 }
          vis_config = { by_value = { xy_chart_config = { ... } } }
        }
      ]
    },
    # A third "Breakdown" section holds the request-by-status datatable. See the companion repo.
  ]
}
</code></pre>
<p>Adding a team is one entry in <code>teams</code>. Adding a panel to every team is one entry in <code>panel_library</code> and one reference per team. The full config (data source ES|QL queries, metrics, layers, axis defaults, and legend placement) lives in <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a>.</p>
<p>The saturation panel queries the metrics data stream with the ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> command, which is designed for TSDB. For the query to work, data streams matching <code>metrics-payments-*</code> must use <code>time_series</code> mode, so the configuration also ships an index template (<a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/metrics_tsdb.tf"><code>metrics_tsdb.tf</code></a>) that enables that.</p>
<h3 id="applydashboardsascodetokibanawithterraformapply">Apply dashboards as code to Kibana with terraform apply</h3>
<p>Run <code>terraform plan</code> to confirm both team dashboards (payments and checkout) will be created then apply:</p>
<pre><code>terraform apply
</code></pre>
<p>Open Kibana and you'll see one <strong>Golden Signals</strong> dashboard per team, each backed by its own index pattern.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3d461fb8f841d32/6a85cdb4331d7a7965c3180b/image2.jpg" alt="" /></p>
<h2 id="dashboardsascodeinthegitopsloopreviewchangesinpullrequests">Dashboards as code in the GitOps loop: review changes in pull requests</h2>
<p>Dashboards are now an artifact in version control, like the rest of your infrastructure.</p>
<p>You edit the library or a team's selection, open a pull request, your reviewer reads the <code>terraform plan</code> diff and sees which dashboards change.</p>
<p>For example, say you tighten the "critical error" threshold from <code>status &gt;= 500</code> to <code>status &gt;= 503</code> in <code>panel_library.errors.esql_query_tpl</code>. Running <code>terraform plan</code> shows the change reaching both teams at once:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a891e92c1b74fba/6a85cdb79a32f15bbda7e038/image3.jpg" alt="" /></p>
<p><em>Note: Full output in <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/outputs/terraform-plan-update.txt"><code>terraform-plan-update.txt</code></a>.</em></p>
<p>A single edit to <code>panel_library.errors</code> propagates to every team that references it. After the PR merges, it's time to run <code>terraform apply</code>.</p>
<p>After the apply finishes, refresh the dashboards in Kibana and the new threshold is in effect:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a1490023fd81294/6a85cdba0782902a5a3217c4/image4.jpg" alt="" /></p>
<h2 id="detectdashboarddriftandrollbackwithgit">Detect dashboard drift and roll back with git</h2>
<p>If someone edits a dashboard using the UI, the next <code>terraform plan</code> shows the difference, because the code and the live state no longer match.</p>
<p>To see this in action, open <code>Golden Signals - payments</code> in Kibana, rename the <strong>Latency p95</strong> panel to <code>Latency p95 (EDITED)</code>, and save the dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf100d616be5f89de/6a85cdbc9bf99401610a05c1/image5-small.jpg" alt="" /></p>
<p>Then run <code>terraform plan</code>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55cc1662818757b8/6a85cdbfeaf245b4d9a49fa5/image6-small.jpg" alt="" /></p>
<p>Terraform reads the panel title from the live dashboard, compares it against the code, and proposes reverting the UI rename. You decide whether to keep the change (update the code to match) or revert it by running <code>terraform apply</code>.</p>
<p>You can commit the new version, or rollback one or many versions using git.</p>
<p>Replaying the earlier example: if you reopen the PR that changed <code>panel_library.errors</code> to broaden the error threshold and add a clearer title, <code>git diff dashboards.tf</code> shows the entire intent in two lines:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c138d8078b2634c/6a85cdc2bc5bb33835f81b45/image7.jpg" alt="" /></p>
<p>Every team that references <code>errors</code> picks up the new threshold on the next <code>terraform apply</code>, and reverting that commit rolls the change back across all of them at once.</p>
<h2 id="wrapup">Wrap up</h2>
<p>Managing Kibana observability dashboards by hand does not scale past a few teams. With the Kibana Dashboards API and Terraform, you define a standard once, compose each team's dashboard from a shared library, and review every change in a pull request. One edit reaches every team, and you can roll back by reverting a commit.</p>
<p>The proposed file structure is only one of many ways you can organize your dashboards depending on how much information they share.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li><a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">Kibana Dashboards as code with Terraform</a></li>
<li><a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard"><code>elasticstack_kibana_dashboard</code> resource reference</a></li>
<li><a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs">Elastic Stack Terraform provider documentation</a></li>
<li><a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-dashboards">Kibana Dashboards API documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL reference</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kibana-observability-dashboards-terraform</link>
    <guid isPermaLink="false">kibana-observability-dashboards-terraform</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbc974846f9b410e/6a85cdc4342d69301d21b147/image1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 03 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[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[Elastic z/OS ingest: five architectures for mainframe data]]></title>
    <description><![CDATA[This field guide walks through the ingest architectures I've seen work in production, the data quality checks that decide whether your dashboards actually work, and the ECS mapping that makes mainframe data usable to the platform.]]></description>
    <content:encoded><![CDATA[<p>Mainframe teams want what every other observability team already has: anomaly detection, machine learning (ML) on the batch windows, and alerts that fire when something's actually wrong. Most of them have the data for it. What they don't have is data that the platform can recognize as unified, connected, and operationally meaningful.</p>
<p>A customer described it to me this way: A single transaction passes through three products on its way through the mainframe, and each one names the same field differently (system name, program name, user). Getting the data into Elastic isn't the hard part; getting it to correlate across products, so that Elastic's dashboards and ML jobs recognize it as the same data, is where most projects fall short. </p>
<p>Done right, Elastic becomes the speed layer that mainframe environments have never had: a near–real-time view across operational, transactional, and security data, while the authoritative systems of record stay exactly where they are.</p>
<p>This is the onboarding process I use with mainframe customers, built from architectures running in production at large financial institutions. It covers the ingest patterns that actually work, how Elastic Common Schema (ECS) alignment makes the data usable, and whether the data quality holds up or fails quietly.</p>
<h2 id="validateyourmainframesourcedatabeforeyouwriteapipeline">Validate your mainframe source data before you write a pipeline</h2>
<p>The most expensive failures I've seen in mainframe ingest projects are the ones that don't fail loudly. Pipelines run, data lands, dashboards render, and weeks later, someone notices that half the events from one logical partition (LPAR) never parsed or a quiet typing change has been silently corrupting a field.</p>
<p>Two mistakes come up frequently:</p>
<p><strong>1. Format inconsistency across LPARs and time windows:</strong> Log formats vary across LPARs, between batch and online windows, and across shift changes. A format that parses cleanly in a dev LPAR may not match what production emits during peak batch. This is the single most common cause of partial parse failures I run into.</p>
<p><strong>2. Sample configurations treated as production configurations:</strong> A common cause of "it broke overnight" incidents: The upstream collector configuration was based on a sample structure shipped by the vendor and then never replaced with a deliberate production configuration. When the vendor pushed an update, naming and typing changed (fields renamed, types shifted) and the downstream pipeline started rejecting records mid-flight. Treat sample configurations as exactly that, and replace them with a deliberate production configuration that doesn't move under you.</p>
<p>Before any pipeline development begins, walk through 24–48 hours of raw samples from each source with the mainframe team. This review should be treated as a recurring requirement rather than a one-off event, because the conditions that produce format drift (vendor updates, configuration changes, new message types) keep happening after the project goes live.</p>
<p><strong>Worth knowing first:</strong> For mainframe environments, the <a href="https://www.elastic.co/integrations/data-integrations?search=ibm">Elastic integrations catalog</a> is short. The <a href="https://www.elastic.co/docs/reference/integrations/ibmmq">IBM MQ integration</a> is the most complete option (Queue Manager error logs and performance metrics, ECS-aligned, with out-of-the-box dashboards), though the metrics data stream requires the containerized MQ distribution rather than native z/OS MQ. If your architecture includes Customer Information Control System (CICS) workloads or you need end-to-end distributed tracing, assess <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">IBM Z Observability Connect</a> before building custom pipelines: It's the <em>native OpenTelemetry (OTel) path</em> and covers more ground than the architectures below. For everything else, read on.</p>
<h2 id="ecsalignmentfromdatainelastictodataelasticcanuse">ECS alignment: from data in Elastic to data Elastic can use</h2>
<p>Before choosing an ingest strategy, it's worth understanding why ECS alignment comes first in practice, even if the pipeline gets built later. It's the decision that determines whether everything else pays off.</p>
<p>A mainframe team's core mission: Trace a single transaction from a REST call into z/OS Connect, through to an Information Management System (IMS) application, and back. That flow touches three products, each emitting telemetry with its own field names for the same concepts (system name, program name, user, transaction ID). Without normalization, correlating that transaction means writing queries that explicitly union three different field names per concept. That’s expensive to write and fragile when any product changes its schema.</p>
<p>ECS solves this. It defines a consistent target schema (<code>host.name</code>, <code>process.name</code>, <code>user.name</code>, <code>event.code</code>) that every source maps into. Once z/OS Connect, IMS Connect, and IMS data all land in the same ECS fields for the same logical concepts, that cross-product transaction trace becomes a single query.</p>
<p>There's a second reason this matters. Elastic's OOTB dashboards, alerting rules, anomaly detection, and ML jobs are all built against ECS field paths. A <code>job_name</code> field that Logstash extracted from a JES log is invisible to them. A <code>process.name</code> field carrying the same value is immediately recognized and processed. ECS alignment is what makes the platform's built-in capabilities recognize your data.</p>
<p>Skipping this step is the most common reason that ingest projects fall short, despite the data being technically present.</p>
<h3 id="mapwhatfitstocoreecs">Map what fits to core ECS</h3>
<p>The mapping below is a starting point drawn from what I've seen work across customer environments. Field names in your source data will vary, but the ECS targets are stable:</p>
<p>| z/OS concept | ECS field | Notes |
| :---- | :---- | :---- |
| Job name | <code>process.name</code> |  |
| Return code | <code>process.exit_code</code> | Ensure integer type; hex strings are a common mapping mistake |
| Program name | <code>process.executable</code> |  |
| Elapsed time | <code>event.duration</code> | Nanoseconds in ECS; z/OS typically reports in hundredths of a second or milliseconds, so convert at the pipeline stage; unit mismatches silently break ML anomaly detection on latency |
| Message ID | <code>event.code</code> |  |
| Timestamp | <code>@timestamp</code> | Normalize from z/OS format to ISO 8601 in the pipeline |
| LPAR name | <code>host.name</code> |  |
| System ID (SMFID) | <code>host.hostname</code> |  |
| User ID | <code>user.name</code> |  |</p>
<p>Reference: <a href="https://www.elastic.co/docs/reference/ecs/ecs-process">ECS process fields</a> and <a href="https://www.elastic.co/docs/reference/ecs/ecs-event">ECS event fields</a>.</p>
<h3 id="extendstrategicallywithcustomecsfields">Extend strategically with custom ECS fields</h3>
<p>Mainframe-specific concepts have no ECS equivalent: job class, ASID, SMF record type and subtype, sysplex name, WTO routing codes, CICS transaction ID. Flattening these into <code>labels.*</code> as untyped strings destroys type information and makes them effectively unusable for queries and aggregations.</p>
<p>Define a <code>zos.*</code> custom namespace using ECS's documented extension mechanism. It keeps your core telemetry ECS-compliant while retaining the operational context your mainframe team needs for incident response.</p>
<h3 id="useecsmappingstostaycurrent">Use ecs@mappings to stay current</h3>
<p>Include <code>ecs@mappings</code> as a component template in your index template (available from Elasticsearch 8.9 for custom index templates and from 8.13 for Elastic Agent integration templates). It provides Elastic-maintained ECS field definitions automatically and keeps them current with each Elasticsearch release. For custom pipelines, this is what keeps your ECS alignment from drifting over time without manual upkeep.  </p>
<p>One important caveat from the field: <code>ecs@mappings</code> provides the field definitions but doesn't enforce types at ingest. A return code arriving as a string is accepted and mapped as a string. Monitoring these discrepancies is critical, and they can be identified using the Data Quality dashboard. And because Elastic <a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-faq">donated ECS to OpenTelemetry</a>, the <code>zos.*</code> mappings you define here remain valid as OTel semantic conventions and ECS converge. The alignment work is the same whether data arrives via Logstash or OpenTelemetry Protocol (OTLP).</p>
<h2 id="choosetherightarchitectureforthesource">Choose the right architecture for the source</h2>
<p>Most environments I work with run more than one of these ingest architectures, and different data sources have different latency, throughput, and licensing characteristics. A single architecture rarely covers everything. The table below maps common z/OS data sources to the architectures that work well for them.</p>
<p>Kafka is commonly added when there's a network resilience requirement between the mainframe and the Elastic cluster. If Kafka isn't already in your estate, the operational overhead of running Kafka should be weighed against the resilience benefits. From IBM MQ 9.4.3, Kafka Connect can run natively in z/OS UNIX System Services for MQ connector use cases, reducing the need for an off-platform Kafka Connect cluster. </p>
<p>When Kafka is used, Logstash is the recommended downstream consumer for the ingest paths in this guide. The Confluent Elasticsearch sink connector is an alternative; the self-managed <a href="https://docs.confluent.io/kafka-connectors/elasticsearch/current/overview.html">v1 connector</a> supports Elasticsearch 7.x and 8.x but is deprecated with end of life (EOL) in April 2027; and the <a href="https://docs.confluent.io/cloud/current/connectors/cc-elasticsearch-sink-v2/cc-elasticsearch-sink-v2.html#features">v2 connector</a> is Confluent Cloud only, making it unsuitable for on-premises and air-gapped environments.</p>
<p>Reference architecture: <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/agent-kafka-es">Kafka as middleware</a>.</p>
<p>| Data source | Collector | Notes |
| :---- | :---- | :---- |
| SMF type 30 job accounting | IBM Z Common Data Provider (CDP) | Binary SMF records need preprocessing before ingestion |
| z/OS SYSLOG | IBM Z CDP |  |
| JES job logs | IBM Z CDP | Batch export is an alternative for historical / proof of concept (PoC) work |
| Resource Access Control Facility (RACF) audit events | IBM Z CDP | ECS-aligned RACF data works with Elastic SIEM out of the box |
| RMF performance data | IBM Z CDP | Consider time series data stream (TSDS) for the index template |
| IMS statistical records | IBM Z CDP |  |
| OMEGAMON agent metrics (CICS, IMS, Db2, z/OS, network, storage) | IBM OMEGAMON Data Provider (ODP) | Outputs JSON natively; no binary preprocessing needed |
| IMS transaction data | IMS Connect Extension (Rocket Software) | JSON output bypasses SMF binary parsing; requires Rocket Software licensing |
| CICS transaction traces | IBM Z Observability Connect | Native OTel; covered in detail in the <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">End-to-End Observability from Cloud Native to Mainframe</a> deep -dive |
| Linux on IBM Z (zLinux) | Standard Elastic Agent | Full integration catalog available; different problem from z/OS onboarding |
| Historical analysis / PoC | Batch export (CSV / FTP) | Not suitable as a long-term operational solution |</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62803cc80ddb77ae/6a7f0d2b448e4eb6415c072b/image1.png" alt="Flow diagram of the ingest paths from z/OS to Elastic" />
<em>Flow diagram of the ingest paths from z/OS to Elastic.</em></p>
<h3 id="ibmzcdptheworkhorseforzosoperationaldata">IBM Z CDP: The workhorse for z/OS operational data</h3>
<p>IBM Z CDP is the most widely deployed first-mile collector for z/OS operational data. It reads from SMF datasets in near-real time and forwards off-platform, handling the genuinely difficult part of getting data off z/OS without burdening performance-critical paths. In the environments I work with, it's the standard path for SMF type 30 job accounting, IMS statistical records, and z/OS SYSLOG.</p>
<p>CDP forwards to Logstash, which handles parsing, field extraction, and routing into Elasticsearch. Kafka is an optional middleware message queue:</p>
<ul>
<li><strong>CDP → (Kafka →) Logstash → Elasticsearch</strong></li>
</ul>
<p>The trade-offs: CDP is a separately licensed IBM product, binary SMF records need preprocessing before Logstash can parse them, and the architecture isn't suited to sub-minute latency requirements.</p>
<p>Worth noting alongside CDP:</p>
<ul>
<li>IBM ODP plays the same collector role for performance and availability metrics from whichever OMEGAMON monitors are in your stack: CICS, IMS, Db2, z/OS, network, and storage. Unlike CDP's binary SMF output, ODP converts to JSON natively, so there's no preprocessing step. ODP consists of two components: OMEGAMON Data Broker (a Zowe cross-memory server plugin running on z/OS that collects attributes from OMEGAMON monitoring agents and forwards them to Data Connect); and OMEGAMON Data Connect (a Java application running on or off z/OS that receives data from Data Broker and forwards it to destinations including Elasticsearch; the destination settings are configured here). If OMEGAMON is already in your monitoring stack, ODP is the natural path for getting that telemetry into Elastic.</li>
<li>IBM Z Operational Log and Data Analytics (IZLDA) packages CDP's data streaming capabilities alongside analytics and dashboarding into a single licensed product — I haven't encountered it in production yet, but it's the direction IBM is heading. If your organization is evaluating or has recently licensed IZLDA, the CDP ingest path described above remains the same — IZLDA uses CDP as its underlying collection engine, with Elastic Stack as one of its supported destinations. <a href="https://www.ibm.com/case-studies/bcc-iccrea-group">Gruppo BCC ICCREA's deployment</a> is a published example of IZLDA feeding an Elasticsearch-based monitoring stack.</li>
</ul>
<p>Reference architecture: <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/ls-for-input">Logstash to Elasticsearch</a>.</p>
<h3 id="imsconnectextensionforimsworkloadsthatcanbypasscdp">IMS Connect Extension: For IMS workloads that can bypass CDP</h3>
<p>Rocket Software's IMS Connect Extension journals IMS transaction activity directly as JSON, bypassing the SMF layer entirely. Events publish to Kafka, and Logstash consumes and indexes. Some organizations standardize all log streams through Kafka (rsyslog → Kafka → Logstash) as an optional resilience pattern.</p>
<ul>
<li><strong>IMS Connect Extension → (Kafka →) Logstash → Elasticsearch</strong></li>
</ul>
<p>This works well for IMS transaction performance data and application-level event streams. JSON output removes the binary parsing problem. Kafka gives you decoupling, replay, and a buffer for downstream maintenance.</p>
<p>The trade-offs: IMS Connect Extension licensing, Kafka infrastructure to operate, and IMS-specific coverage that doesn't help with z/OS SYSLOG or other SMF types.  </p>
<p>One thing I always validate before committing to this pattern is Kafka topic naming. Banks and regulated environments typically have strict topic naming policies, and IMS Connect Extension's default behavior of creating topics itself can clash with those policies. It’s cheaper to discover this before architecture commitment than after.</p>
<h3 id="batchexportforhistoricalanalysisandpoc">Batch export: For historical analysis and PoC</h3>
<p>Export from IMS Problem Investigator or similar tooling to CSV, transfer off-platform, and ingest via Logstash or Elastic Agent file input. This approach has no real-time capability, and it doesn’t require any new z/OS software.</p>
<ul>
<li><strong>Batch export → CSV/FTP → Logstash/Elastic Agent → Elasticsearch</strong></li>
</ul>
<p>This works well for historical analysis, initial PoC work, and demonstrating value before committing to a real-time pipeline. I also use this to get ECS mapping right before the production architecture is in place. It isn’t suitable as a long-term operational observability solution.</p>
<h3 id="linuxonibmzaseparateandeasierpath">Linux on IBM Z: A separate and easier path</h3>
<p>This path is often overlooked. Linux on IBM Z workloads can run standard Elastic Agent (Elastic Agent doesn’t run on native z/OS), no z/OS-specific tooling, no custom pipeline and the full Elastic integration catalog is available.</p>
<p>If you have Linux on IBM Z workloads in your estate, treat them as a separate (and considerably easier) onboarding path.</p>
<p>For all options, please see the <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures">reference architectures with Elastic Agent</a>.</p>
<h3 id="throughputandairgappedthetwoquestionseverymainframeteamasks">Throughput and air-gapped: The two questions every mainframe team asks</h3>
<p><strong>Throughput impact:</strong> Anything that touches z/OS performance-critical paths is a nonstarter for mainframe teams running thousands of transactions per second. All four architectures above use off-platform collection deliberately: CDP, ODP, Kafka/Logstash, batch export, or standard Linux agent. This is the right design for the environment, not a workaround.</p>
<p><strong>Air-gapped environments:</strong> Most mainframe estates I work with are network-restricted to some degree. Elastic's <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/airgapped-env">air-gapped reference architecture</a> is a documented, supported deployment path.</p>
<h2 id="buildthepipelineanddontstartfromscratch">Build the pipeline, and don't start from scratch</h2>
<p>For the CDP and IMS Connect Extension architectures, log data lands in Elastic reflecting the limited structure of its source. Mainframe log formats are installation-specific and partially structured at best; no off-the-shelf parser covers them, and writing a pipeline from scratch has historically been the largest time sink in any onboarding project. AI has changed that. For mainframe estates that can't call out to a hosted model (which is most of them), both tools below work with self-managed local large language models (LLMs), so the capability is available in air-gapped and network-restricted environments. See the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/local-llms-overview">local LLMs overview</a> for supported options.</p>
<p><strong>Streams: For data already landing in Elastic (available from 9.2).</strong><br />
Open the <strong>Processing</strong> tab for a stream in Kibana, and click <strong>Suggest pipeline</strong>. Within seconds, you're looking at a complete, validated pipeline (Grok or Dissect pattern, date normalization, type conversions, field cleanup) with a live preview of how your actual documents parse through it. Nothing writes to the stream until you confirm. Under the hood, generation runs in two stages: First, deterministic fingerprinting groups your log formats and picks the best parsing approach; second, a reasoning agent iterates to add normalization and cleanup, validating against hard thresholds before handing control to you. The result is a working pipeline you refine, not a starting point you rewrite. The technical detail is in <a href="https://www.elastic.co/observability-labs/blog/elastic-streams-ai-pipeline-generation">How Streams Generates a Log Pipeline in Seconds</a>.</p>
<p><strong>Automatic Import: For building a new custom integration from the ground up (available from 8.18/9.0).</strong> <a href="https://www.elastic.co/docs/explore-analyze/ai-features/automatic-import">Automatic Import</a> takes a different path. You upload sample data, and it generates a complete, deployable Elastic Agent integration package (ingest pipeline, ECS field mappings, event categorization, and related.* field population), which you review and approve before it installs. Where the Streams Suggest Pipeline structures data already arriving in a stream, Automatic Import builds the entire collection path from scratch. Supported input formats include JSON, NDJSON, CSV, and syslog, which covers z/OS SYSLOG directly. Supported collection methods include Kafka, File Stream, TCP, and HTTP Endpoint, making it a natural fit for shops already routing data through Kafka or receiving ODP output over TCP. For mainframe shops adopting Elastic Agent, this removes what was previously weeks of custom integration work.</p>
<p>A less obvious benefit that applies to both tools is continuity. The engineer who wrote your custom GROK pattern eventually moves to another team. A tool that can regenerate a pipeline or integration from sample data is operational resilience.</p>
<p>In terms of scope, Streams works on text. Binary SMF records need to be converted to text or JSON upstream (via CDP or IBM-supplied utilities) before either tool can do anything with them. That conversion happens before Elastic is involved.</p>
<h2 id="configurethedeadletterqueuefromdayone">Configure the dead-letter queue from day one</h2>
<p>Mainframe teams know the <em>dead-letter queue pattern</em> from MQ: When a message can't be delivered or processed, it goes to a holding queue rather than being silently dropped. Elasticsearch has the same concept for ingest pipelines, called the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">failure store</a>. Configure it from day one, not after your first production incident.</p>
<p>Format drift is a recurring failure mode: vendor updates, sample-config-as-production, new message types appearing in batch windows. The failure store is how you find out about it before your dashboards lie to you. When a log line arrives in an unexpected format and the pipeline can't parse it, the failure store captures the original document with metadata about why it failed. You can query it, alert on its growth rate, and use the captured documents to fix the pipeline.</p>
<p>Without it, parse failures either fall to default handling (records indexed with raw <code>message</code> fields, expected query fields simply absent) or get dropped entirely. Either way, you don't know it's happening.</p>
<p>Configure retention based on how long it takes your team to triage drift, typically days to a couple of weeks. Pair it with an alert on document count or growth rate so the queue itself is the early warning, not something someone has to remember to check.</p>
<h2 id="verifymainframedataqualitybeforeyoubuildonit">Verify mainframe data quality before you build on it</h2>
<p>Don't build dashboards or alerting rules on data you haven't verified. The Data Quality dashboard tells you whether your ECS alignment is real or aspirational.  </p>
<p>For mainframe data, silent type mismatches are common: return codes in hex mapped as keywords, elapsed times stored as strings, timestamps that never coerced to <code>@timestamp</code>. None of these fail at ingest. All of them silently break queries and alerting conditions.</p>
<p>Run the checker against real production data, not synthetic samples. z/OS log variation across batch windows and message types means edge cases only surface under real conditions. Expect to iterate: Find the mismatch, fix the pipeline, and run again. Two or three passes is normal for a new mainframe data stream.</p>
<p>Source quality validation, the failure store, and the Data Quality dashboard are three points on the same loop. Together they give you confidence that the dashboards reflect what's actually happening on the mainframe, not what you hoped your pipeline was producing.</p>
<h2 id="gettingstartedwithmainframedataonboarding">Getting started with mainframe data onboarding</h2>
<p>The mainframe is a first-class observability target, and the path there is more concrete than it was a few years ago. Managed integrations cover IBM MQ. CDP and Kafka-based architectures have well-understood deployment patterns. Streams and Automatic Import remove the blank-page problem for custom pipelines, including in restricted environments through local LLMs. IBM Z Observability Connect is there when the OTel path is in reach.</p>
<p>Recommended order of operations:</p>
<ol>
<li>Validate the source data.  </li>
<li>Use OOTB integrations where they exist.  </li>
<li>Align to ECS early.  </li>
<li>Choose architectures source by source.  </li>
<li>Generate pipelines rather than write them from scratch.  </li>
<li>Configure the failure store from day one.  </li>
<li>Verify before building anything on top.  </li>
</ol>
<p>If your organization is working through this and you'd like to compare notes, or if you're hitting a specific blocker, reach out to your Elastic account team. For the OTel-native path, the <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">End-to-End Observability from Cloud Native to Mainframe</a> deep dive is the next read. To try the building blocks in your own environment, <a href="http://cloud.elastic.co/registration">start a free Elastic Cloud trial</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/mainframe-data-ingestion</link>
    <guid isPermaLink="false">mainframe-data-ingestion</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Anna Maria Modée]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2cddefcfe7021906/6a7f0d2ee88c65adaf00b6d0/image2.png" length="0" type="image/png"/>
    <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic now alerts at 80% OpenAI rate limit usage, before your app gets throttled]]></title>
    <description><![CDATA[OpenAI rate limit monitoring in Elastic maps headroom across every project and model. Compare configured RPM, TPM and IPM limits against real usage and plan capacity before a throttling alert fires.]]></description>
    <content:encoded><![CDATA[<p>Elastic's <a href="https://www.elastic.co/docs/reference/integrations/openai">OpenAI integration</a> now polls rate limits every five minutes and checks them against real usage across every project and model. You can see RPM, TPM and IPM headroom before OpenAI hits you with an HTTP 429. A prebuilt alert fires when peak one-minute utilization crosses 80% of your configured limit for three checks in a row, grouped by project and model, so one team's spike doesn't get lost in an org-wide average. OpenAI configures these limits per project, capped at or below your organization's overall ceiling. That means a single noisy project can burn through its own allocation while the rest of the org still has room, and until now, that headroom stayed invisible until it ran out.</p>
<p>The first time most teams learn that their OpenAI project is close to a rate limit is when production traffic starts getting throttled with HTTP 429 responses. OpenAI enforces rate limits at the project level, not at the organization level, so a single noisy workload in one project can saturate that project's RPM or TPM ceiling while the rest of the organization still has plenty of room. Without OpenAI rate limit monitoring that compares configured limits against actual consumption, headroom is invisible until it runs out.</p>
<h2 id="openaiapimonitoringinelasticwhatsnew">OpenAI API monitoring in Elastic: what's new</h2>
<p>We're pleased to announce updates to the <a href="https://www.elastic.co/docs/reference/integrations/openai">Elastic OpenAI integration</a>. On top of the existing token usage and audit log coverage, the integration now polls OpenAI's <a href="https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/projects/subresources/rate_limits/methods/list_rate_limits">List project rate limits Admin API</a> per project and rolls the results up into both per-project and org-wide views. A new <code>openai.rate_limits</code> dataset feeds two new dashboard panels and a prebuilt threshold alert rule, so teams can see how close each project and model is to being throttled before users experience production impact.</p>
<h2 id="whattheintegrationpollsusageauditlogsandratelimitsapis">What the integration polls: Usage, Audit Logs and Rate Limits APIs</h2>
<p>The Elastic OpenAI integration is built for teams running applications on the OpenAI API platform. The people accountable for it are the developers shipping those services, the platform and SRE teams keeping them running, and the finance and FinOps owners answering "how much is our software consuming, and are we within our capacity envelopes?"</p>
<p>The integration collects from three OpenAI Admin API surfaces:</p>
<ul>
<li><strong>Usage API</strong> for usage counts across tokens, characters, seconds, sessions, bytes, and images, with project, model, user, and API key attribution where that Usage API surface provides it.</li>
<li><strong>Audit Logs API</strong> for organization audit events such as API key creation, project changes, and user activity.</li>
<li><strong>Rate Limits API</strong> for configured RPM, TPM, and IPM ceilings per project and per model, plus other limit dimensions where available; the new headroom views compare the per-minute request, token, and image limits against actual consumption.</li>
</ul>
<p>Because everything is pulled from the Admin API at the organization level, platform teams get a unified view across every project, model and API key, alongside the rest of the telemetry they already monitor in Elastic, without touching application code or installing SDKs in every service.</p>
<h2 id="whatteamsneedtomonitorwhenrunningontheopenaiapi">What teams need to monitor when running on the OpenAI API</h2>
<p>Four operational needs come up over and over for teams running production workloads on the OpenAI API.</p>
<h3 id="tokenusageattribution">Token usage attribution</h3>
<p>A single OpenAI organization usually serves many internal teams and products, each with its own project, its own mix of models (GPT-5.5 Pro for the hardest reasoning tasks, GPT-5.4 for everyday traffic, GPT-5.4 nano for high-volume low-cost requests, and specialized models for images, audio and embeddings) and its own user and API key footprint. When usage patterns shift, the platform team needs to know which project, model and key is driving the change so they can attribute consumption back to the right team and decide which workloads should move to a cheaper model.</p>
<h3 id="ratelimitheadroom">Rate limit headroom</h3>
<p>OpenAI enforces per-model rate limits on requests per minute (RPM) and tokens per minute (TPM) at the project level, not at the organization level. The first time a team learns they're close to the ceiling is usually when production traffic starts being throttled. Surfacing configured limits alongside actual consumption, per project and per model, lets platform teams see headroom in advance, plan capacity, and request limit increases before users feel the impact.</p>
<h3 id="auditvisibility">Audit visibility</h3>
<p>Security and compliance teams need to know who created API keys, who changed project settings, who invited or removed users, and when. The integration ingests OpenAI's organization audit log so those events land in the same Elastic deployment as the usage data, ready for correlation, alerting and long-term retention. Audit log ingestion has two prerequisites: audit logging must be enabled in your OpenAI organization, and the Admin API key used by the integration must belong to an <strong>Organization Owner</strong>, because OpenAI restricts audit-log access to that role. Without both, the <code>openai.audit</code> dataset stays empty.</p>
<h3 id="granularityforeveryaudience">Granularity for every audience</h3>
<p>The same data needs to serve different cadences. SREs want one-minute resolution to catch spikes and trigger throttling alerts. Platform engineers want hourly views for capacity planning. Finance and FinOps owners want daily totals that roll up cleanly for reporting. A single integration that exposes all three granularities removes the need to maintain separate pipelines for each audience.</p>
<h2 id="howdoeselasticpolltheopenaiadminapi">How does Elastic poll the OpenAI Admin API?</h2>
<p>The integration runs on Elastic Agent and uses the CEL input to poll OpenAI's Admin API on a schedule. Authentication uses a single Admin API key, stored as an encrypted Fleet secret and redacted from agent logs. From a single configuration, the integration ingests datasets from three Admin API sources:</p>
<p><strong>Usage API datasets</strong> (per project, model, user and API key, with each dataset tracking the unit OpenAI exposes for that workload):</p>
<ul>
<li><code>openai.completions</code> for chat and completion token counts (input, output, cached, audio input/output).</li>
<li><code>openai.embeddings</code> for embedding token counts.</li>
<li><code>openai.moderations</code> for moderation token counts.</li>
<li><code>openai.images</code> for image counts and size dimensions.</li>
<li><code>openai.audio_speeches</code> for text-to-speech character counts.</li>
<li><code>openai.audio_transcriptions</code> for speech-to-text duration in seconds.</li>
<li><code>openai.code_interpreter_sessions</code> for code interpreter session counts.</li>
<li><code>openai.vector_stores</code> for vector store byte counts.</li>
</ul>
<p><strong>Audit Logs API dataset:</strong></p>
<ul>
<li><code>openai.audit</code> for organization audit events such as API key creation, project changes and user activity.</li>
</ul>
<p><strong>Rate Limits API dataset:</strong></p>
<ul>
<li><code>openai.rate_limits</code> <em>(new)</em> for snapshots of configured rate limits per project and per model, including RPM, TPM, IPM, and other limit fields where OpenAI returns them, paged across all active projects on each poll.</li>
</ul>
<p>Ingest pipelines handle parsing and field mapping so the data lands queryable, dashboard-ready, and aligned with the rest of Elastic Observability. Because the data is pulled from the Admin API at the organization level, you get this visibility without any application-side instrumentation or SDK changes.</p>
<h2 id="whatyouneedtosetupopenaimonitoringinelastic">What you need to set up OpenAI monitoring in Elastic</h2>
<p>To get started with the Elastic OpenAI integration, you will need:</p>
<ul>
<li>An Elastic deployment:</li>
<li><strong>Elastic Cloud Hosted (ECH)</strong> running a recent supported version, or</li>
<li><strong>Elastic Cloud Serverless</strong>, no version requirement, works out of the box.</li>
<li>An OpenAI organization with <strong>Admin API</strong> access.</li>
<li>An <strong>Admin API key</strong> provisioned by an <strong>Organization Owner</strong> from the <a href="https://platform.openai.com/settings/organization/admin-keys">OpenAI platform settings</a> under <strong>Settings → Admin keys</strong>. Owner-level keys are required if you want the <code>openai.audit</code> dataset to populate.</li>
<li>Audit logging enabled in your OpenAI organization, if you want audit data.</li>
<li>Elastic Agent installed on a host with outbound HTTPS access to <code>api.openai.com</code>, or the agentless deployment option.</li>
</ul>
<h2 id="howtosetuptheopenaiintegration">How to set up the OpenAI integration</h2>
<ol>
<li>Generate an Admin API key in the <a href="https://platform.openai.com/settings/organization/admin-keys">OpenAI platform settings</a>.</li>
<li>In Kibana, go to <strong>Management → Integrations</strong>, search for <strong>OpenAI</strong> and click <strong>Add</strong>.</li>
<li>Choose your deployment mode: <strong>agentless</strong> for a zero-install experience, or <strong>Elastic Agent</strong> on your own host.</li>
<li>Tune the defaults if you need to. Each dataset has sensible defaults:</li>
</ol>
<ul>
<li><strong>Usage datasets</strong> poll every 5 minutes with 1-minute buckets. Each dataset exposes a <code>finalization_grace</code> setting that controls when a per-minute usage bucket is considered final. The default <code>0s</code> favors freshness: a bucket is ingested as soon as its minute closes. The observed behavior (which OpenAI does not document, but the integration team measured against the live API) is that bucket counts can keep rising for some time after that point, so per-minute totals at <code>0s</code> can undercount during heavy bursts. Setting <code>finalization_grace</code> to <code>15m</code>, the recommended value for accurate counts, holds a bucket back until the grace window has elapsed and brings counts much closer to the Usage API, though a small residual undercount can remain during very high-volume bursts because OpenAI's per-minute counts can be revised upward beyond any fixed grace window. The cost is delaying dashboards and the rate limit headroom alert by the grace period.</li>
<li><strong>Rate limits</strong> polls every 5 minutes. Each poll captures the full set of configured RPM, TPM and IPM limits per project and per model.</li>
<li><strong>Audit logs</strong> polls on a separate cadence and ingests all org-level audit events. Remember the prerequisites: audit logging enabled in your OpenAI organization and an Organization-Owner Admin API key.</li>
</ul>
<ol>
<li>Open the integration assets in Kibana. Within minutes, usage, audit, and rate-limit data starts flowing, and the prebuilt dashboards and alert rule are ready to use.</li>
</ol>
<p>For the full configuration reference, see the <a href="https://www.elastic.co/docs/reference/integrations/openai">OpenAI integration documentation</a>.</p>
<h2 id="whatdotheopenairatelimitdashboardsshow">What do the OpenAI rate limit dashboards show?</h2>
<p>The integration ships with a pre-built Kibana dashboard that gives you an immediate, queryable view of your organization's OpenAI API consumption. The overview pulls headline numbers (total tokens, total invocations, top models, top projects, top users and top API keys) into one place for a quick read on the state of your OpenAI usage. The screenshot below shows the OpenAI usage overview dashboard:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55959947293efa82/6a7f0f1805b7b5417b18ba40/openai-overview.png" alt="Pre-built OpenAI usage overview dashboard in Elastic showing total tokens, top models, top projects, top users and top API keys" /></p>
<p>From the overview, you can drill into the views that answer the operational needs introduced earlier.</p>
<h3 id="tokenusagebymodelprojectanduser">Token usage by model, project and user</h3>
<p>The token metrics panels break down token consumption (input, output, cached input, audio input/output) for the token-based datasets (<code>openai.completions</code>, <code>openai.embeddings</code>, <code>openai.moderations</code>) by model and over time. This is the view that tells you where your token budget is actually going, which workloads are getting the most out of prompt caching, and which projects, users or API keys are driving the bulk of your token consumption. Filter by project or model to scope the view to a single team or product. Image, audio and vector-store consumption (measured in images, characters, seconds, sessions and bytes rather than tokens) is reported in dedicated sections of the same dashboard. The token metrics panels look like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb92bc42bd94d5c6d/6a7f0f1b63e959626e73dec2/openai-token.png" alt="OpenAI token usage metrics by model and project in Elastic, showing input, output, cached and audio token consumption over time" /></p>
<h3 id="ratelimitheadroomperprojectandmodelnew">Rate limit headroom: per project and model <em>(new)</em></h3>
<p>The new rate limit headroom panel joins the configured limits from <code>openai.rate_limits</code> against actual consumption from the usage datasets, per <code>project_id</code> and <code>model</code>. For each row it reports peak one-minute used, the configured limit, and utilization percentage for requests (RPM), tokens (TPM), and images (IPM). Rows are sorted by highest TPM utilization first, with RPM and IPM utilization as tie-breakers, so the highest token-pressure rows appear at the top of the list. Utilization is computed against the peak one-minute bucket in the lookback window, never a 5- or 15-minute sum against a one-minute ceiling, so the panel reflects peak-minute pressure against the one-minute ceiling instead of averaging it away. The per-project rate limit headroom panel is shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd81c080dff528457/6a7f0f1e2f00b2a2f9efec60/openai-rate_limit.png" alt="OpenAI rate limit headroom dashboard panel in Elastic, showing RPM, TPM and IPM utilization per project and model with the closest-to-throttling row at the top" /></p>
<h3 id="ratelimitheadroomorgwiderollupbymodelnew">Rate limit headroom: org-wide rollup by model <em>(new)</em></h3>
<p>Because OpenAI enforces limits per project, a single per-project view doesn't answer "how much total capacity do we have for <code>gpt-image-2</code> across the organization?" The new org-wide rollup panel reports the same RPM, TPM and IPM metrics summed across all active projects for each model. Both the limit and the usage figures are indicative upper bounds rather than exact org-wide numbers (the limit is a sum of per-project ceilings; the usage is a sum of each project's peak minute, which may fall in different minutes across projects), but together they give platform teams a single number to plan against when they're sizing a new workload or deciding which project should absorb a new use case. The org-wide rollup panel is shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt783a4f0120092992/6a7f0f21c2cc09a3a8249686/openai-rate_limit_org_wide.png" alt="OpenAI rate limit headroom org-wide rollup in Elastic, summing RPM, TPM and IPM metrics by model across all active projects" /></p>
<p>Behind the scenes, version <code>2.3.0</code> also normalizes request and token counts into shared <code>openai.base.usage_tokens</code> and <code>openai.base.usage_images</code> fields across the usage datasets, so the headroom panels render correctly even when only a subset of usage datasets is enabled.</p>
<h3 id="openaiauditlogactivityinelastic">OpenAI audit log activity in Elastic</h3>
<p>The audit panels surface organization audit events (API key creations, project changes, user invitations and login activity) so security and compliance teams can review who did what, when, alongside the usage data. The audit dashboard is shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788a856cc65581ab/6a7f0f2496b5a6dae787b541/openai-audit.png" alt="OpenAI audit log dashboard in Elastic, showing API key creation, project changes, user invitations and login activity events" /></p>
<h2 id="outoftheboxalertforratelimitheadroomnew">Out-of-the-box alert for rate limit headroom <em>(new)</em></h2>
<p>The integration ships with a pre-built threshold alert rule template, <code>[OpenAI] Rate limit headroom low</code>, ready to install in one click from the integration's Assets tab.</p>
<p>The default behavior is tuned to be useful out of the box:</p>
<ul>
<li>Runs every 5 minutes.</li>
<li>Looks back over the last 15 minutes.</li>
<li>Fires after 3 consecutive matches.</li>
<li>Triggers when peak one-minute TPM utilization reaches or exceeds 80% of the configured project/model limit.</li>
<li>Groups alerts by <code>project_id::model</code>, so an incident in one project on one model doesn't get lost in an org-wide aggregate.</li>
</ul>
<p>The 80% threshold and other parameters are editable in Kibana after you install the rule, so each team can tune the alert to its own risk tolerance.</p>
<h2 id="customopenaialertsandslosinelasticobservability">Custom OpenAI alerts and SLOs in Elastic Observability</h2>
<p>As with every other Elastic integration, all the OpenAI metrics and audit data is fully available to leverage in every capability in <a href="https://www.elastic.co/observability">Elastic Observability</a>, including <a href="https://www.elastic.co/guide/en/observability/current/slo.html">SLOs</a>, <a href="https://www.elastic.co/guide/en/observability/current/create-alerts.html">alerting</a>, custom <a href="https://www.elastic.co/guide/en/kibana/current/dashboard.html">dashboards</a> and in-depth <a href="https://www.elastic.co/guide/en/observability/current/monitor-logs.html">logs exploration</a>.</p>
<p>For example, to keep token consumption under control across a single project, create a custom threshold rule that sums tokens from the relevant usage dataset and fires when the daily or hourly total crosses your budget. To track model mix, define an SLO in Elastic Observability that treats OpenAI requests on your approved lower-cost model families as the "good events" (the ones that count as meeting the target) and all OpenAI requests as the "total events", grouped by <code>openai.base.project_id</code> and <code>openai.base.user_id</code>. The ratio becomes your SLI; a 7-day rolling 80% target quickly surfaces projects and users overusing more expensive models.</p>
<h2 id="choosingopenaiusagedatagranularity">Choosing OpenAI usage data granularity</h2>
<p>OpenAI usage data collected by the integration powers different cadences, with a fidelity-versus-freshness tradeoff to be aware of. One-minute usage buckets feed the rate limit headroom alert and near-real-time throttling notifications when a project approaches its ceiling: with <code>finalization_grace</code> set to <code>0s</code> (the default), per-minute counts arrive within minutes but can undercount during heavy bursts; raising <code>finalization_grace</code> to <code>15m</code> brings counts much closer to reconciled at the cost of delaying the dashboards and alert by the grace period; a small residual undercount can still remain for the busiest buckets. Hourly views support operational monitoring and capacity planning across projects and models. Daily aggregates roll up cleanly for FinOps reporting and reconciliation. An out-of-the-box alert ships for rate limit headroom (<code>[OpenAI] Rate limit headroom low</code>), and the same data can be reused for custom usage and budget thresholds without building anything from scratch.</p>
<h2 id="getstartedwithopenaimonitoringinelastic">Get started with OpenAI monitoring in Elastic</h2>
<p>The <a href="https://www.elastic.co/docs/reference/integrations/openai">Elastic OpenAI integration</a> is available today in Elastic Cloud, including Elastic Cloud Hosted and Elastic Cloud Serverless. To get started, sign up for a <a href="https://cloud.elastic.co/registration">free Elastic Cloud trial</a>, provision an Admin API key in the <a href="https://platform.openai.com/settings/organization/admin-keys">OpenAI platform settings</a>, and add the OpenAI integration from Kibana under <strong>Management → Integrations</strong>.</p>
<p>Within minutes you'll have token usage, audit activity, and rate limit headroom data flowing into Elasticsearch, with the prebuilt dashboards and the new throttling alert ready to use.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openai-rate-limit-monitoring</link>
    <guid isPermaLink="false">openai-rate-limit-monitoring</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Daniela Tzvetkova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb99f918480cb486e/6a7f0f273cab1c5c100e493a/title_openai_rate_limit.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch: best-in-class for logs, now best-in-class for metrics]]></title>
    <description><![CDATA[Elasticsearch is now best-in-class for metrics: 30× faster than Prometheus, up to 2.5× more storage-efficient, 50% less than Datadog. Learn about all the capabilities we’ve added.]]></description>
    <content:encoded><![CDATA[<p>Over the past few months, Elastic has shipped a columnar storage engine in Elasticsearch purpose-built for time series data, native Prometheus ingest and storage, PromQL support and we’ve delivered a new metrics exploration experience, pre-built infrastructure dashboards, agentic investigation, and a migration path from Datadog and Grafana. Capabilities now include:</p>
<ul>
<li><p>Elasticsearch is a Prometheus-compatible metrics backend — <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 now works natively in Kibana</a>, no translation layer required.</p></li>
<li><p>Metrics land in <a href="https://www.elastic.co/search-labs/blog/elasticsearch-metrics-columnar-engine">Elasticsearch's columnar TSDS architecture</a> storing data up to <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">2.5× more efficient than Prometheus</a> and 2× more efficient than ClickHouse.</p></li>
<li><p>ES|QL time series queries run <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">up to 30× faster than Prometheus</a> on gauge averages and counter rates, including high-cardinality workloads.</p></li>
<li><p><a href="https://www.elastic.co/blog/metrics-pricing">Elastic costs approximately 50% less than Datadog</a>, with no custom metric classification and no cardinality-based billing.</p></li>
<li><p>Grafana can query Elasticsearch directly through the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api">native Prometheus API</a>, keeping your visualization layer while replacing the backend.</p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting">Kubernetes</a> and AWS monitoring ship with pre-built dashboards, alert templates, ML anomaly jobs, and agentic investigation content ready at ingest. Additionally <a href="https://github.com/elastic/agent-skills/tree/main/plugins/observability">skills</a> and <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">MCP apps</a> are available.</p></li>
<li><p>Unified backend for Metrics, logs, and traces enabling <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">agentic investigations</a> without stitching context across tools.</p></li>
<li><p>Metrics exploration in Discover lets anyone start querying and analyzing metrics immediately, no query language expertise required.</p></li>
<li><p>Custom dashboarding is fast and flexible — dashboards-as-code, AI-assisted dashboard creation, variable controls, and collapsible panels mean less time building and more time investigating.</p></li>
<li><p>Migration tooling to help easily migrate dashboards and alerting rules / monitors from Datadog and Grafana.</p></li>
</ul>
<p>Elasticsearch metrics now competes on every dimension that matters to SREs: you can afford to keep every metric at full resolution, query it up to 30x faster than Prometheus, pay 50% less than Datadog, migrate dashboards and alerting rules from Grafana or Datadog easily, and go from alert to root cause without stitching context across disconnected tools. The rest of this post walks through each of these in detail.</p>
<h2 id="elasticsearchmetricsperformance30fasterthanprometheusandmimir">Elasticsearch metrics performance: 30× faster than Prometheus and Mimir</h2>
<p>Datadog and Prometheus force the same tradeoff: drop high-cardinality data or watch costs spiral. SREs managing Kubernetes, AWS, or any high-cardinality infrastructure know the specific shape of this problem. The Kubernetes labels, ephemeral pod data, and fine-grained OTel dimensions that matter most during an incident are the first to go when budgets tighten.</p>
<p>Elastic rebuilt the time series data store and ES|QL compute engine into a fully columnar metrics engine. Adding a new Kubernetes label, a new AWS instance tag, or a new application dimension doesn't strain the system; it adds far less cost than systems that index every label. OTel, Prometheus, and application-defined metrics all land in the same columnar backend at full resolution, with logs, traces, and metrics in a single store. No data dropped, no retention shortened.</p>
<p>Elasticsearch stores metrics up to 2.5× more efficiently than Prometheus (results may differ due to factors like compaction), and 2× more efficiently than ClickHouse. Query performance via ES|QL runs <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">up to 30× faster than Prometheus</a> on gauge averages and counter rates, including high-cardinality workloads where competitors stall. The <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">architecture post</a> covers how TSDS is organized and why the columnar layout produces these results.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1fc77441a43b2a65/6a7f19c1e88c65894500baf4/promql.png" alt="PromQL" /></p>
<p>|                            |                    |                  |                    |
| :------------------------: | :----------------: | :--------------: | :----------------: |
|        <strong>Dimension</strong>       | <strong>vs. Prometheus</strong> |   <strong>vs. Mimir</strong>  | <strong>vs. ClickHouse</strong> |
| Query performance (ES|QL) |  Up to 30× faster  | Up to 30× faster |   Up to 8× faster  |
|     Storage efficiency     |  Up to 2.5× better |      On par      |      2× better     |</p>
<p>The key architectural difference is that Elasticsearch metrics does not maintain a per-series in-memory state that scales with cardinality, so adding thousands of new Kubernetes pod labels or OTel dimensions doesn't drive up memory pressure.</p>
<p>OTel, Prometheus-native, and application-defined metrics are all stored the same way at full resolution, queried fast, at half the cost of Datadog.</p>
<h2 id="elasticobservabilitymetricspricingwithoutthedatadogcustommetricpenalties">Elastic Observability metrics pricing without the Datadog custom metric penalties</h2>
<p>Observability cost is the #1 reason teams switch platforms. For Datadog customers, the pain comes down to one pricing mechanic: custom metrics. Any user-defined value outside of Datadog's built-in integrations is classified as a custom metric and billed at a premium rate. That includes the high-cardinality data that Kubernetes, OpenTelemetry, and cloud-native workloads generate by default. The more granular your instrumentation, the faster the bill compounds. Teams running modern infrastructure hit this ceiling quickly, and the response is predictable: drop data, shorten retention, lose the context that matters most when an incident happens.</p>
<p>Elasticsearch metrics removes that classification. Every metric is priced the same, with no per-metric penalties, no cardinality-based billing, and no forced rollups. You keep every metric at full resolution without a surprise invoice at the end of the month. And because Elastic is 50% the cost of Datadog, the conversation with finance changes: not what data you had to drop to stay on budget, but what you found because you kept everything. It's also why the AI investigation works. Unlike Grafana's fragmented LGTM stack, the context is already unified when the alert fires, not assembled by hand across disconnected tools.</p>
<h2 id="nativeprometheusandpromqlsupportinelasticsearch">Native Prometheus and PromQL support in Elasticsearch</h2>
<p>Most SRE teams aren't running a clean, single-format telemetry pipeline. Prometheus is deeply embedded in applications, services, platforms, and automations. Migrating metrics backends historically meant rewriting queries, rebuilding dashboards, and retraining engineers — enough friction that teams stay on platforms they've outgrown rather than go through it.</p>
<p>Elasticsearch metrics has removed most of that friction. Prometheus metrics arrive via <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> and land in the same columnar store without semantic changes, preserving full metric fidelity end to end. Point them at Elasticsearch instead of Mimir and the data flows. No translation layer, no changes to existing scrape configs.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL now works natively in Kibana</a>, so engineers who live in PromQL don't have to change how they work. Existing PromQL queries, dashboards, and alert rules migrate into Kibana directly. </p>
<p><strong>PromQL queries work unchanged on Elasticsearch</strong></p>
<p>If your team already writes PromQL, nothing needs to change. These queries run as-is against Elasticsearch as your backend — copy, paste, and go.</p>
<p><strong>CPU usage rate (container-level)</strong> The per-second CPU rate across containers, grouped by pod. Useful for spotting which pods are burning CPU during an incident.</p>
<pre><code>PROMQL sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
</code></pre>
<p><strong>Memory working set (container-level)</strong> Current memory in active use per container — the number that matters for OOM risk, not total allocated memory.</p>
<pre><code>PROMQL sum by (container) (avg_over_time(container_memory_working_set_bytes[5m]))
</code></pre>
<p><strong>HTTP request rate (application-level)</strong> Per-second request throughput grouped by instance. A standard first signal when investigating latency or error spikes.</p>
<pre><code>PROMQL sum by (instance) (rate(http_requests_total[5m]))
</code></pre>
<p>All three follow standard PromQL syntax. If you use Elasticsearch as your backend, they run without modification. For the full syntax reference and what's covered, see the<a href="https://www.elastic.co/docs/reference/query-languages/promql"> PromQL support documentation</a>.</p>
<p>The <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api">native Prometheus API</a> makes Elasticsearch a fully Prometheus-compatible backend. Any Prometheus-compatible frontend (Grafana included) can query Elasticsearch directly, so teams that want to keep Grafana as their visualization layer while consolidating onto Elasticsearch can do exactly that without modifying existing dashboards or alert rules.</p>
<p>When SREs need to go deeper than PromQL allows, <a href="https://www.elastic.co/observability-labs/blog/esql-ts-command-querying-metrics">ES|QL</a> works across metrics, logs, and traces in a single interface. The <code>TS</code> command handles the time series specifics: counter rates, gauge averages, window functions, and multilevel aggregations across high-cardinality dimensions. The same query that pulls a CPU counter rate can join against logs from the same host and surface the deployment event that preceded the spike. No tool switching, no new query language. The query language, the dashboards, the alert rules, the visualization layer — all of it carries over. The only thing that changes is that Elasticsearch is the single backend powering everything.</p>
<h2 id="elasticobservabilityoutoftheboxdashboardsalertsandinfrastructurecontent">Elastic Observability: out-of-the-box dashboards, alerts, and infrastructure content</h2>
<p>Most Observability vendors require you to build everything from scratch. Elastic Observability has reduced this need across three areas:</p>
<p><strong>Metrics exploration in Discover.</strong> The <a href="https://www.elastic.co/observability-labs/blog/exploring-metrics-new-data-source-discover">new Elasticsearch metrics exploration experience</a> lets SREs explore metrics in the same interface used for logs — no tab switching, no duplicate queries. Connect an OTel pipeline or Prometheus scrape config, open Streams, and every metric in the data stream renders as a time series chart immediately. No dashboard to build, no query to write. This is where teams can validate data, spot patterns, and start building alerts and SLOs from a live view of what's flowing and cross correlate with logs, traces and other indexed data in Elasticsearch.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt82233276f588b597/6a7f19c5bd21984d9475849b/ts-metrics.png" alt="Metrics Exploration" /></p>
<p><strong>Dashboards.</strong> Kibana dashboards have gained collapsible panels with lazy loading, so panels that aren't immediately visible don't generate queries until they're needed and ES|QL control variables that let SREs manipulate visualizations through dropdowns without writing new queries. Dashboards-as-code is also shipping, enabling version-controlled dashboard definitions that can be templated, shared, and deployed programmatically across environments.</p>
<p><strong>Out-of-the-box infrastructure content.</strong>  Elastic is shipping with two new infrastructure OOTB experiences:</p>
<ul>
<li>The <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">new Kubernetes integration</a> ships with hierarchical dashboards, alert rule templates, ML anomaly detection jobs, and the context and prompts needed for AI-assisted root cause analysis — all pre-configured and ready the moment data starts flowing. </li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27a0aea38d8a3800/6a7f19c8c2e91457c0016fe0/k8s-dashboard.png" alt="Kubernetes Integration" /></p>
<ul>
<li>AWS infrastructure monitoring follows the same pattern: OOTB content for core AWS services activates at ingest, so teams aren't starting from scratch every time a new service or account comes online. The same approach extends to databases and other core infrastructure — the platform arrives opinionated, not blank.</li>
</ul>
<h2 id="agenticinvestigationsacrossyourinfrastructurewithelasticobservability">Agentic investigations across your infrastructure with Elastic Observability</h2>
<p>Elasticsearch correlates metrics, logs, and traces in a single backend, so the investigation context is assembled before an engineer is paged.</p>
<p>The hard part is 2am. An RDS instance hitting connection limits, starving services upstream. An Auto Scaling group failing health checks for a reason buried in application logs. A pod restart cascading across a namespace.</p>
<p>In a Grafana LGTM stack, you're opening three tabs before you have enough context to form a hypothesis.</p>
<p>In Datadog, the context is unified but the AI is a black box: no BYO-LLM, no data residency options.</p>
<p>In Elastic, metrics, logs, and traces share a single backend and a common schema, so the investigation context is already assembled when the alert fires — no manual correlation across tools, no context lost in translation between query languages. ML anomaly detection runs automatically against infrastructure metrics (Kubernetes, AWS, databases), so the investigation starts from a scored anomaly with context about what's typical, what changed, and how severe the deviation is, not just a raw threshold breach.</p>
<p>When an alert fires, Elastic's investigation workflow correlates signals, assembles root cause context, and surfaces recommended next steps before anyone is paged. The <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">agentic Kubernetes observability post</a> walks through a complete example end to end. The <a href="https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting">EKS troubleshooting walkthrough</a> shows how Agent Builder and MCP work together for a full root cause loop across EC2, EKS, and related AWS services.</p>
<p>In addition to investigating issues in Elastic Observability, you can use Claude, Cursor, VS Code, or your favorite tool to analyze issues using MCP Apps and agent skills from Elastic. The Observability MCP App extends the analysis to wherever your team already works. If your team investigates in Claude, Cursor, or VS Code, the same investigation capabilities (infrastructure health rollup, service dependency graph, anomaly detail, blast radius analysis) render as interactive views directly in the conversation. Neither Grafana nor Datadog offer this.</p>
<ul>
<li><strong>Observability MCP App</strong> — Connects Claude, Cursor, VS Code, or any MCP-compatible tool directly to your Elasticsearch data, so infrastructure health, service dependencies, and anomaly context surface as interactive views inside the conversation without leaving your tool of choice.<a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp"> See how it works with Kubernetes.</a></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd67fe754bc52576b/6a7f19cb3ce8e2b5e5cf5799/mcp-app.png" alt="Observability MCP App" /></p>
<ul>
<li><strong>Agent Skills</strong> — Pre-built skills for Kubernetes, AWS, and other core infrastructure let any agent — in Elastic or your own — run structured investigations against your observability data without custom prompt engineering. Drop them into Claude, Cursor, or your own agent pipeline and they work out of the box.<a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows"> Explore the observability skills</a> or<a href="https://github.com/elastic/agent-skills/tree/main/plugins/observability"> browse the skills library on GitHub.</a></li>
</ul>
<h2 id="migratingfromdatadogorgrafanatoelasticobservability">Migrating from Datadog or Grafana to Elastic Observability</h2>
<p>The most common reason SRE teams don't switch observability platforms is migration. Moving years of alert rules, hundreds of dashboards, and runbook-embedded PromQL queries is a daunting operational task, and the cost of maintaining parallel stacks while doing it compounds every day.</p>
<p>The <a href="https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana">Observability Migration Platform</a> handles the translation automatically. Point the CLI or Claude/Cursor (with Elastic’s agent skills) at your Datadog org or Grafana instance and it converts supported dashboards, alert rules, and PromQL queries into Kibana-native outputs. The tool allows you to see what was fully migrated, what needed tweaks and what is needed from you to migrate everything. You move what you've already built.</p>
<p>On the ingest side, <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> means the pipeline requires no changes. Scrape configs point to Elasticsearch instead of another Prometheus-compatible backend and the data lands in the same columnar store. Workflows, queries, and alert configurations carry over without change. For teams that want to keep Grafana as a visualization layer during or after migration, the native Prometheus API and PromQL support in Kibana mean the transition can be phased rather than cut over all at once.</p>
<p><strong>Elasticsearch as a backend for Grafana</strong></p>
<p>For teams not ready to leave Grafana, replacing the backend is a migration path in its own right, and there are two ways to do it depending on your workflow.</p>
<p>If your team runs Prometheus today, the lowest-friction path is Grafana's <strong>Prometheus data source</strong>. Elasticsearch now exposes a native Prometheus-compatible API, so you can <a href="https://www.elastic.co/observability-labs/blog/query-prometheus-metrics-grafana-elasticsearch">point Grafana's existing Prometheus plugin directly at Elasticsearch</a>. No sidecars, no adapters, no pipeline changes required. Existing PromQL dashboards, alert rules, and variable dropdowns work without modification, including Grafana's Metrics Drilldown explorer. Add Elasticsearch as a <code>remote_write</code> target in your Prometheus config and swap the data source URL. That's the full migration for most teams.<a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api"> See the end-to-end setup guide.</a></p>
<p>For teams that want to go further and query logs, metrics, and traces together from a single Grafana query editor, the <strong>official Grafana Elasticsearch plugin</strong> now ships with ES|QL support. This unlocks cross-signal correlation directly in Grafana, with Elasticsearch handling all three data types in a unified columnar backend.<a href="https://www.elastic.co/observability-labs/blog/esql-grafana-elasticsearch-plugin"> See how to set it up.</a></p>
<p>Either way, keep Grafana, replace Mimir and Loki, and gain the full benefit of Elasticsearch's columnar storage and query performance underneath. Years of operational work, preserved. The migration that teams have been putting off becomes a backend swap.</p>
<h2 id="whatsgaandwhatsintechpreview">What's GA and what's in tech preview</h2>
<p>| Capability                                | Status       |
| ----------------------------------------- | ------------ |
| Columnar metrics engine (TSDS)            | GA           |
| ES|QL time series support                | GA           |
| PromQL support in Kibana                  | GA           |
| Prometheus Remote Write ingest            | GA           |
| Kubernetes infrastructure OOTB experience | GA           |
| AWS infrastructure OOTB experience        | Tech Preview |
| Observability MCP App                     | Tech Preview |
| Agent skills                              | Tech Preview |
| Observability Migration Platform          | Tech Preview |</p>
<p>The individual posts linked throughout cover GA versus preview specifics and known limitations.</p>
<p>All of this  (the columnar metrics engine, native PromQL, agentic investigations, and migration tooling) runs across Elastic's three deployment modes: serverless, Elastic Cloud, and self-managed. Datadog has no on-prem option; Grafana Cloud limits its highest-value features to hosted deployments. With Elastic, you choose where your data lives.</p>
<h2 id="elasticobservabilitylowercostwithoutdroppingdata">Elastic Observability: lower cost without dropping data</h2>
<p>Modern cloud infrastructure broke the observability model built around separate tools for separate signals. The cost is real: duplicate tooling bills, manual correlation during incidents, and data dropped just to stay on budget.</p>
<p>A single backend that stores every signal efficiently means you keep what you need without the bill that usually comes with it. That's a different kind of conversation to have with finance: not "we had to drop data to stay on budget," but "here's what we found." The AI gets the full picture because there's only one picture, and the platform arrives with enough pre-built content to be useful on day one, not after weeks of dashboard toil.</p>
<p>That's possible because of how Elasticsearch is built differently from the platforms you're likely replacing:</p>
<ul>
<li><p><strong>Columnar metrics storage</strong> stores stores metrics data highly efficiently in TSDS index mode. </p></li>
<li><p><strong>Native Prometheus compatibility</strong> means existing scrape configs, PromQL queries, and dashboards work without rewriting.</p></li>
<li><p><strong>Unified metrics, logs, and traces</strong> in a single backend means investigation context is assembled at query time, not manually across tabs.</p></li>
<li><p><strong>Search and analytics in the same engine</strong> — an inverted index for logs, a columnar index for metrics, queried together with ES|QL.</p></li>
<li><p><strong>Agentic investigations</strong> that correlate signals, surface anomalies, and suggest remediation before anyone is paged.</p></li>
<li><p><strong>Serverless, Elastic Cloud, or self-managed</strong> — you choose where your data lives, which Datadog cannot offer.</p></li>
</ul>
<p>The cost conversation with finance becomes about what you found, not what you spent.</p>
<p><strong>Get started</strong></p>
<ul>
<li><p><a href="https://cloud.elastic.co/registration">Start a free trial</a></p></li>
<li><p><a href="https://www.elastic.co/docs/solutions/observability">Elastic Observability documentation</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs">Elastic Observability Labs</a></p></li>
</ul>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>Is Elasticsearch now a production-ready metrics platform?</strong></p>
<p>Yes. As of June 2026, Elasticsearch ships a rebuilt columnar storage engine purpose-built for time series data, native Prometheus Remote Write ingest, PromQL support in Kibana, ES|QL time series querying, and out-of-the-box infrastructure dashboards for Kubernetes and AWS. The columnar metrics engine, ES|QL time series support, PromQL, and Prometheus ingest are all generally available in Elastic Serverless and soon GA in Elastic Cloud Hosted.</p>
<p><strong>How does Elasticsearch compare to Datadog for metrics cost?</strong></p>
<p>In comparable metrics workloads, Elastic Observability Serverless costs significantly less than Datadog — in illustrative examples based on published list pricing, more than 50% less, and often closer to two-thirds less. The gap is structural: Datadog bills primarily per host, then adds charges for custom metrics and containers as instrumentation grows. The cost difference is largest for exactly the workloads where Datadog bills most: high-cardinality, densely instrumented environments like Kubernetes and OTel.</p>
<p><strong>How does Elasticsearch metrics performance compare to Prometheus and Grafana Mimir?</strong></p>
<p>ES|QL queries on Elasticsearch run up to 30× faster than Prometheus and Mimir on gauge averages and counter rates, including high-cardinality workloads. Elasticsearch stores OTel metrics at 3.75 bytes per data point; up to 2.5× more efficiently than Prometheus and 2× more efficiently than ClickHouse.</p>
<p><strong>Can teams migrate from Datadog or Grafana to Elasticsearch without rebuilding everything?</strong></p>
<p>Yes. Elastic's Observability Migration Platform converts Datadog and Grafana dashboards, alert rules, and migrates PromQL queries into Kibana as-is. Teams can also keep Grafana as a visualization layer while replacing the backend with Elasticsearch, using the native Prometheus API and PromQL support in Kibana.</p>
<p><strong>What makes Elasticsearch different from Grafana for metrics observability?</strong></p>
<p>Elasticsearch stores metrics, logs, and traces in a single unified backend with one query language (ES|QL), while Grafana's LGTM stack splits metrics (Mimir/Prometheus) and logs (Loki) across separate backends requiring separate query languages. Elasticsearch also ships agentic investigation capabilities, which includes AI Agent, Workflows, MCP App, and Agent skills, a more comprehensive set of capabilities than Grafana. </p>
<p><strong>Does Elasticsearch support Prometheus and PromQL natively?</strong></p>
<p>Yes, in two distinct ways. First, Elasticsearch accepts Prometheus metrics via Prometheus Remote Write and exposes a native Prometheus-compatible API, so it can serve as a backend for any Prometheus-compatible frontend, including Grafana. Second, Kibana supports PromQL natively, meaning existing queries, dashboards, and alert rules run directly in Kibana without a translation layer or modification.</p>
<p><strong>What infrastructure monitoring content ships out of the box with Elastic Observability?</strong></p>
<p>Elastic ships pre-built dashboards, alert templates, and ML anomaly detection jobs across hundreds of infrastructure integrations covering hosts, containers, cloud services, databases, network devices, and more. For Kubernetes and AWS specifically, the platform also includes agentic investigation content such as agent skills and an Observability MCP App that lets teams run investigations directly from Claude, Cursor, or VS Code. All of this is available at ingest with no configuration required.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-faster-cheaper-datadog</link>
    <guid isPermaLink="false">prometheus-metrics-elasticsearch-faster-cheaper-datadog</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Bahubali Shetti,Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab11d1e390d9cfcc/6a7f19cede23150cc4fd808b/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Use Elasticsearch as a Drop-In Prometheus Backend for Grafana]]></title>
    <description><![CDATA[Use Elasticsearch as a Prometheus backend for Grafana dashboards, autocomplete, Metrics Drilldown, and alerting without changing PromQL workflows.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch is already one of the most popular plugins in the Grafana ecosystem, and we have now made it much more powerful for metrics usage.
If you run Prometheus today and use Grafana to visualize your metrics, you can now point Grafana's Prometheus data source directly at Elasticsearch.
No sidecars, no adapters, no pipeline changes required.</p>
<p>Elasticsearch now implements a native Prometheus-compatible API layer, which covers <a href="https://www.elastic.co/blog/prometheus-remote-write-elasticsearch">ingestion via Remote Write</a> and <a href="https://www.elastic.co/blog/elasticsearch-supports-promql">querying via PromQL</a>.
This post shows the Grafana setup end to end.
Companion posts also cover <a href="https://www.elastic.co/blog/promql-queries-run-in-kibana">PromQL in Kibana</a> and the <a href="https://www.elastic.co/blog/prometheus-remote-write-elasticsearch-architecture">Remote Write architecture</a>.</p>
<h2 id="whyuseelasticsearchasaprometheusbackend">Why use Elasticsearch as a Prometheus backend?</h2>
<p>Over the last year, Elasticsearch has become a state-of-the-art metrics store: <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">time series data streams</a>, ES|QL's <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> command</a>, and storage and query optimizations that deliver strong <a href="https://www.elastic.co/search-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">metrics performance for Prometheus-style workloads</a>.
The Prometheus-compatible API layer makes that engine reachable through the tools your team already uses.</p>
<p>Many teams have invested heavily in Prometheus-based tooling: dashboards, runbooks that reference PromQL queries, on-call workflows built around Grafana panels.
Elasticsearch's Prometheus-compatible endpoints let you move metrics storage while keeping those Grafana workflows.</p>
<p>This is particularly relevant if you already use Elasticsearch for logs or traces and want to consolidate your observability data into a single platform, while keeping your Grafana-based workflows intact.</p>
<h2 id="whattheelasticsearchprometheusapiincludes">What the Elasticsearch Prometheus API includes</h2>
<p>The Elasticsearch Prometheus API exposes three endpoint groups.</p>
<h3 id="queryapis">Query APIs</h3>
<p>The core query endpoints allow Grafana to evaluate PromQL expressions against data stored in Elasticsearch:</p>
<ul>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/query_range</code> evaluate a PromQL expression over a time window and return matrix results.
This is what powers most Grafana dashboard panels.</li>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/query</code> evaluate a PromQL expression at a single point in time and return vector results.</li>
</ul>
<p>Both endpoints implement the standard Prometheus response envelope, including result types (vector, matrix, scalar, string), status codes, and error handling.
For <code>POST</code>, send parameters in an <code>application/x-www-form-urlencoded</code> body, matching Prometheus client behavior.</p>
<h3 id="metadataapis">Metadata APIs</h3>
<p>Grafana's metric explorer, autocomplete, and variable dropdowns rely on metadata endpoints to discover what's available.
Elasticsearch supports:</p>
<ul>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/series</code> return time series matching label selectors.</li>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/labels</code> return all available label names.</li>
<li><code>GET /_prometheus/api/v1/label/{name}/values</code> returns all values for a given label.</li>
<li><code>GET /_prometheus/api/v1/metadata</code> returns type and help text for each metric name.</li>
</ul>
<p>These endpoints power autocomplete and the metric browser in Grafana.
The <code>/metadata</code> endpoint additionally enables Grafana's <a href="https://grafana.com/docs/grafana/latest/explore/explore-metrics/">Metrics Drilldown</a>: an interactive metric explorer that displays all available metrics as a grid of live sparklines and lets you drill into any metric without writing a PromQL query.</p>
<h3 id="indexprefiltering">Index pre-filtering</h3>
<p>All query and metadata endpoints accept an optional <code>{index}</code> path segment immediately after <code>/_prometheus/</code>, for example:</p>
<pre><code>GET /_prometheus/metrics-prod-*/api/v1/query_range
</code></pre>
<p>This pre-filters the Elasticsearch indices that the PromQL query runs against before any expression evaluation happens.
Scoping queries to the relevant data can reduce query work for dashboards that span large volumes of metrics across different data streams.</p>
<p>You can configure a separate Grafana data source per index pattern to give teams scoped access to their own metrics.</p>
<h3 id="remotewriteingestion">Remote Write ingestion</h3>
<p>Elasticsearch also implements the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-prometheus-remote-write">Prometheus Remote Write protocol</a>, which lets you ship metrics from Prometheus to Elasticsearch using the standard <code>remote_write</code> configuration.
Adding Elasticsearch as a remote write destination requires a single block in your existing Prometheus config:</p>
<pre><code>remote_write:
  - url: "&lt;es_endpoint&gt;/_prometheus/api/v1/write"
    authorization:
      type: ApiKey
      credentials: &lt;api_key&gt;
</code></pre>
<p>Metrics are stored in the <code>metrics-generic.prometheus-default</code> data stream by default.
You can route metrics from different Prometheus instances or environments into separate data streams using the dataset and namespace path segments:</p>
<ul>
<li><code>POST /_prometheus/metrics/{dataset}/api/v1/write</code> stores metrics in <code>metrics-{dataset}.prometheus-default</code></li>
<li><code>POST /_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code> stores metrics in <code>metrics-{dataset}.prometheus-{namespace}</code></li>
</ul>
<h2 id="howtoconnectgrafanatoelasticsearch">How to connect Grafana to Elasticsearch</h2>
<h3 id="step1createaserverlessproject">Step 1: Create a serverless project</h3>
<p>Sign in to <a href="https://cloud.elastic.co">cloud.elastic.co</a> and create a new <strong>Observability</strong> serverless project.
Once the project is ready, you will land directly in Kibana.
To find the Elasticsearch endpoint, go back to the Elastic Cloud console, open <strong>Manage &gt; Application endpoints, cluster and component IDs</strong>, and click the copy icon next to <strong>Elasticsearch</strong>.
The endpoint looks like:</p>
<pre><code>https://&lt;project-id&gt;.es.&lt;region&gt;.&lt;provider&gt;.elastic.cloud
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37ae7707a46a689f/6a7f1a1c42a11755a895c2f5/elasticsearch-endpoint.png" alt="Elastic Cloud console showing the Application endpoints panel with the Elasticsearch endpoint and copy button" /></p>
<h3 id="step2createapikeys">Step 2: Create API keys</h3>
<p>Create two API keys with scoped privileges: one for ingestion, one for querying.
Using separate keys means a leaked Grafana key cannot be used to write data, and a leaked ingest key cannot be used to read it.</p>
<p>In your project, open <strong>Admin and settings</strong> (the ⚙️ icon at the bottom left of the side nav), go to <strong>API keys</strong>, and create the first key.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c7a05f18adefd4d/6a7f1a1f448e4e7e355c0b5a/create-api-key.png" alt="Create API key dialog showing the name, type, and role descriptor for the ingest key" /></p>
<p><strong>Ingest key</strong> (<code>prometheus-remote-write</code>): restricts access to writing metrics data streams only.
In the <strong>Control security privileges</strong> section, paste the following role descriptor:</p>
<pre><code>{
  "ingest": {
    "indices": [
      {
        "names": ["metrics-*"],
        "privileges": ["auto_configure", "create_doc"]
      }
    ]
  }
}
</code></pre>
<p>Create a second key for Grafana in the same section.</p>
<p><strong>Query key</strong> (<code>prometheus-grafana</code>): restricts access to reading metrics data streams only.</p>
<pre><code>{
  "query": {
    "indices": [
      {
        "names": ["metrics-*"],
        "privileges": ["read", "view_index_metadata"]
      }
    ]
  }
}
</code></pre>
<p>Copy both key values before closing. You will not be able to retrieve them again.</p>
<h3 id="step3runprometheusandgrafana">Step 3: Run Prometheus and Grafana</h3>
<p>Create a <code>prometheus.yml</code> that scrapes Prometheus itself and forwards those metrics to Elasticsearch.
Replace <code>&lt;es_endpoint&gt;</code> with the endpoint from Step 1 and <code>&lt;ingest_api_key&gt;</code> with the ingest key from Step 2:</p>
<pre><code>global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

remote_write:
  - url: "&lt;es_endpoint&gt;/_prometheus/api/v1/write"
    authorization:
      type: ApiKey
      credentials: &lt;ingest_api_key&gt;
</code></pre>
<p>Next, create the Grafana provisioning directories:</p>
<pre><code>mkdir -p grafana/provisioning/datasources grafana/provisioning/dashboards
</code></pre>
<p>Then create a Grafana data source configuration that points at the Elasticsearch Prometheus API.
Create <code>grafana/provisioning/datasources/datasource.yml</code>, replacing <code>&lt;es_endpoint&gt;</code> and <code>&lt;query_api_key&gt;</code> with the values from Steps 1 and 2:</p>
<pre><code>apiVersion: 1

datasources:
  - name: Elasticsearch
    type: prometheus
    access: proxy
    url: "&lt;es_endpoint&gt;/_prometheus"
    uid: elasticsearch-prometheus
    isDefault: true
    jsonData:
      httpHeaderName1: Authorization
    secureJsonData:
      httpHeaderValue1: "ApiKey &lt;query_api_key&gt;"
</code></pre>
<p>This configures a Prometheus-type data source backed by Elasticsearch.
Grafana sends Prometheus queries with <code>POST</code> by default, which Elasticsearch accepts on authenticated HTTPS endpoints such as Serverless.</p>
<p>Create <code>grafana/provisioning/dashboards/dashboards.yml</code> to tell Grafana where to find provisioned dashboards:</p>
<pre><code>apiVersion: 1

providers:
  - name: default
    type: file
    options:
      path: /var/lib/grafana/dashboards
</code></pre>
<p>Finally, create a <code>docker-compose.yml</code> to start everything:</p>
<pre><code>services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro

  download-dashboard:
    image: curlimages/curl:latest
    user: root
    volumes:
      - dashboards:/dashboards
    command: &gt;
      sh -c 'curl -fsSL https://grafana.com/api/dashboards/3662/revisions/2/download
      | sed "s/\$${DS_THEMIS}/elasticsearch-prometheus/g"
      &gt; /dashboards/prometheus-overview.json'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      download-dashboard:
        condition: service_completed_successfully
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=grafana
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - dashboards:/var/lib/grafana/dashboards:ro

volumes:
  dashboards:
</code></pre>
<p>The <code>download-dashboard</code> service fetches the <a href="https://grafana.com/grafana/dashboards/3662-prometheus-2-0-overview/">Prometheus 2.0 Overview</a> dashboard from the Grafana marketplace and patches it to use the Elasticsearch data source.
The <code>sed</code> replaces the dashboard's <code>${DS_THEMIS}</code> data source placeholder with our data source UID.
This is needed because Grafana's provisioning does not resolve these placeholders on its own (<a href="https://github.com/grafana/grafana/issues/10786">grafana#10786</a>).
Grafana waits for the download to finish before starting.</p>
<p>Start both with:</p>
<pre><code>docker compose up -d
</code></pre>
<p>Prometheus will start scraping its own metrics and shipping them to Elasticsearch every 15 seconds.
Give it one or two scrape intervals before opening the dashboard.</p>
<h3 id="step4openthedashboard">Step 4: Open the dashboard</h3>
<p>Open Grafana at <code>http://localhost:3000</code> and log in with <code>admin</code> / <code>grafana</code>.
Go to <strong>Dashboards</strong> and open <strong>Prometheus 2.0 Overview</strong>.</p>
<p>The dashboard shows your Prometheus self-monitoring metrics, pulled from Elasticsearch via PromQL queries.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3f55bf517eea926/6a7f1a22bd21988de17584a9/grafana-dashboard.png" alt="Grafana dashboard with Elasticsearch as the Prometheus data source, showing Prometheus self-monitoring metrics rendered by PromQL queries" /></p>
<h3 id="step5exploremetricswithgrafanasmetricsdrilldown">Step 5: Explore metrics with Grafana's Metrics Drilldown</h3>
<p>Because Elasticsearch implements the Prometheus metadata and discovery endpoints, Grafana's <a href="https://grafana.com/docs/grafana/latest/explore/explore-metrics/">Metrics Drilldown</a> works out of the box.</p>
<p>In Grafana, go to <strong>Drilldown &gt; Metrics</strong> in the left-hand navigation and select <strong>Elasticsearch</strong> as the data source.
Grafana loads all available metrics from Elasticsearch and displays them as a grid of live sparklines.
From there you can filter by label, search by name, and drill into any metric without writing PromQL.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e51ae030a51c63e/6a7f1a26eab5be665d20aaf0/grafana-drilldown.png" alt="Grafana Metrics Drilldown showing all Prometheus metrics from Elasticsearch as a grid of sparklines" /></p>
<h2 id="currentlimitationsandwhatsnext">Current limitations and what's next</h2>
<p>This is the first implementation and updates should be expected.
All of the following are actively being worked on:</p>
<h3 id="promqlcoverageisnotyetcomplete">PromQL coverage is not yet complete</h3>
<p>Queries using group modifiers (for example, <code>on(instance, job)</code>), set operators (<code>or</code>, <code>and</code>, <code>unless</code>), and certain functions like <code>topk</code> are not yet supported.</p>
<h3 id="formencodedposthasdeploymentrequirements">Form-encoded POST has deployment requirements</h3>
<p><code>POST</code> requests with <code>application/x-www-form-urlencoded</code> bodies require security enabled, TLS on the Elasticsearch HTTP interface, and an authenticated request.
Serverless meets these requirements out of the box.
If TLS terminates before Elasticsearch and the node sees plain HTTP, use <code>GET</code> with query-string parameters instead.</p>
<h3 id="onlyremotewritev1issupported">Only Remote Write v1 is supported</h3>
<p>Remote Write v2 support is planned.</p>
<h3 id="instantqueriesarenotpointintimeyet">Instant queries are not point-in-time yet</h3>
<p>The instant query endpoint currently runs a short range query under the hood and returns the last sample.
It will be replaced with a proper point-in-time evaluation.</p>
<p>Coming next: broader PromQL function and operator coverage, Remote Write v2, and exemplar endpoints.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>Can Grafana query Prometheus metrics stored in Elasticsearch?</strong>
Yes.
Grafana can use Elasticsearch as a Prometheus data source when the URL points to <code>/_prometheus</code>.
Queries use PromQL and return the standard Prometheus response format for Grafana dashboards, variables, Metrics Drilldown, and alerting.</p>
<p><strong>Do I need to change Prometheus or Grafana dashboards to use Elasticsearch?</strong>
You do not need to rewrite PromQL queries or dashboard panels for common Grafana use cases.
Configure Prometheus Remote Write to send metrics to Elasticsearch, then point Grafana's Prometheus data source at the Elasticsearch <code>/_prometheus</code> endpoint.</p>
<p><strong>Why use Elasticsearch instead of a separate Prometheus long term storage backend?</strong>
Using Elasticsearch as a Prometheus backend lets you store metrics with logs and traces under the same access controls and retention model.
Recent work on the Elasticsearch metrics engine also delivers strong performance for Prometheus-style workloads.
For the benchmark details, see the <a href="https://www.elastic.co/search-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">Elasticsearch metrics performance post</a>.</p>
<p><strong>What PromQL features are supported in Elasticsearch today?</strong>
Elasticsearch supports common PromQL query patterns used by Grafana dashboards.
Advanced group modifiers, set operators, and <code>topk</code> are not yet supported.</p>
<p><strong>Can I limit Grafana queries to specific Elasticsearch indices?</strong>
Yes.
Add an index pattern after <code>/_prometheus/</code>, such as <code>/_prometheus/metrics-prod-*/api/v1/query_range</code>.
This pre-filters the Elasticsearch indices before PromQL evaluation and can reduce query work for large metrics deployments.</p>
<h2 id="prometheusapiavailability">Prometheus API availability</h2>
<p>The Prometheus-compatible API is available now on <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elasticsearch Serverless</a> with no additional configuration.</p>
<p>If you run into issues or have feedback, open an issue on the <a href="https://github.com/elastic/elasticsearch">Elasticsearch repository</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/query-prometheus-metrics-grafana-elasticsearch</link>
    <guid isPermaLink="false">query-prometheus-metrics-grafana-elasticsearch</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5c8286ee6ba2a92/6a7f1a28ead8ec2d18baac4c/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[SNMP Topology Data in Kibana: Collection to Canvas]]></title>
    <description><![CDATA[The Network Topology plugin for Kibana provides a ready-to-deploy Logstash pipeline, a structured schema, and a topology view that shows what's connected to what.]]></description>
    <content:encoded><![CDATA[<h2 id="snmpcollectionshouldntrequireasidequest">SNMP collection shouldn't require a side quest.</h2>
<p>Getting SNMP data into Elasticsearch unlocks rich visibility into your network — interface utilization, routing health, L2 forwarding, and more. The path there involves a few familiar steps: choosing which MIBs to walk, mapping OIDs to human-readable field names, configuring SNMP v2c or v3 authentication, accommodating vendor-specific MIB extensions, and tuning the pipeline to gracefully handle device timeouts across large inventories. With a solid template in place, what used to be a bespoke Logstash project becomes a repeatable, shareable setup that any engineer on the team can pick up and extend.</p>
<p><a href="https://github.com/elastic/kibana-network-topology-plugin">The plugin</a> includes a Logstash pipeline <a href="https://github.com/elastic/kibana-network-topology-plugin/blob/main/docs/collectors/logstash.conf">template</a> that handles the common cases out of the box. It walks IF-MIB (interface counters and status), IP-MIB (ARP tables and IP address assignments), BRIDGE-MIB (MAC address forwarding tables), BGP4-MIB (BGP peer sessions), and OSPF-MIB (OSPF neighbor adjacencies) per target device on a configurable poll interval. You add your device list and authentication details, start Logstash, and data begins flowing into Elasticsearch.</p>
<p>The template also handles the operational annoyances that trip people up: poll timeouts, missing OID branches on devices that don't support a given MIB, and batching walks across large device inventories.</p>
<h2 id="structuringsnmpdatainelasticsearchschemadesign">Structuring SNMP data in Elasticsearch: schema design</h2>
<p>Once SNMP data lands in Elasticsearch, the next problem is structure. Interface counters like <code>ifInOctets</code> and <code>ifOperStatus</code> map to ECS concepts reasonably well. They're host-level metrics with direct equivalents in <code>host.network.*</code> fields. But the data network engineers actually need for troubleshooting is relational, and this plugin offers a way to view those relationships.</p>
<p>A BGP peer session has a state, a remote AS number, an uptime, and an update count. An OSPF adjacency has a neighbor router ID, an area, a priority, and a state machine position. A MAC table entry records which physical switch port a given MAC address was learned on. None of these have ECS field definitions, and stuffing them into generic <code>event.*</code> or <code>observer.*</code> fields loses the semantic meaning that makes the data useful.</p>
<p>The plugin takes an opinionated approach: use ECS where it fits, extend with clear namespaces where it doesn't. Interface data maps to ECS-aligned fields. Routing protocol and L2 forwarding data goes into purpose-built namespaces (<code>bgp_peer.*</code>, <code>ospf_neighbor.*</code>, <code>arp.*</code>, <code>mac_table.*</code>) with field names that match the concepts operators already think in. If you know what <code>bgpPeerState</code> means on a router CLI, <code>bgp_peer.state</code> in Elasticsearch is immediately familiar. If you already collect SNMP data in a homegrown schema, the plugin's templates and ingest pipeline will complement it rather than replace it. The new fields are additive, so you can adopt them at your own pace!</p>
<p>| Data Type | Key Fields | ECS Namespace |
| --- | --- | --- |
| BGP Peer Session | State, Remote AS, Uptime, Update Count | <code>bgp_peer.*</code> |
| OSPF Adjacency | Neighbor Router ID, Area, Priority, State | <code>ospf_neighbor.*</code> |
| MAC Table Entry | Switch Port, Learned MAC Address | <code>mac_table.*</code> |
| ARP Entry | IP-to-MAC Mapping | <code>arp_table.*</code> |</p>
<p>An ingest pipeline (<code>snmp-device-enrichment</code>) handles classification at index time. It parses each device's <code>sysDescr</code> string to assign a normalized <code>device.type</code> (router, switch, firewall, access point) and <code>device.vendor</code>, so every downstream consumer (dashboards, ES|QL queries, alerting rules, the topology view) gets consistent device metadata without custom parsing. The pipeline recognizes common vendors out of the box and is extensible for environments with less common hardware.</p>
<p>The result is SNMP data you can query like any other structured data in Elasticsearch. "Show me every BGP session not in Established state" is a filter, not a regex exercise. "Which Cisco switches have interfaces that are admin-up but oper-down" is a KQL query, not a script.</p>
<h2 id="visualisingsnmpnetworktopologyinkibana">Visualising SNMP network topology in Kibana</h2>
<p>Dashboards excel at answering "what are the numbers?" A topological view answers a complementary question: "what's connected to what?" Network engineers think in topology: upstream and downstream relationships, path diversity, and blast radius of a link failure. A spatial, graph-based view brings that mental model directly into Kibana, sitting alongside the charts and data tables operators already rely on.</p>
<p>The plugin adds an interactive topology graph to Kibana's Observability navigation. It reads the structured SNMP data from Elasticsearch, builds an adjacency graph from ARP, MAC table, BGP, and OSPF relationships, and renders it as a force-directed layout you can zoom, pan, and rearrange. Nodes are devices, edges are discovered relationships, and clicking any device opens a flyout with its interface table, ARP neighbors, and routing protocol sessions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80b0174a4703b359/6a7f1afd63e959967973e279/topo-diagram.png" alt="Network Topology Diagram" /></p>
<h2 id="howdoyousetupsnmpnetworktopologymonitoringinkibana">How do you set up SNMP network topology monitoring in Kibana?</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte26fc1b65d134bd5/6a7f1b006693f80125664391/setup-tab.png" alt="Setup Overview" /></p>
<p>The plugin is nearly ready to go out of the box, <a href="https://www.elastic.co/docs/solutions/observability/infra-and-hosts/network-topology/monitor-network-devices">only a few assets need installation</a>. Here's what the setup looks like:</p>
<ol>
<li><p><strong>Install the plugin zip</strong> on a self-managed Kibana instance (<code>bin/kibana-plugin install file:///path/to/networkTopology-&lt;version&gt;.zip</code>).</p></li>
<li><p><strong>Apply the index templates and ingest pipeline</strong>. Click through the template installation in the plugin's Setup tab. A few button clicks and the schema is in place.</p></li>
<li><p><strong>Deploy the Logstash pipeline.</strong> Add your device targets, authentication details, or other configuration to the included template and start it. If you're using <a href="https://www.elastic.co/docs/reference/logstash/logstash-centralized-pipeline-management">Logstash Centralized Pipeline Management</a>, push it from Kibana, no SSH required.</p></li>
</ol>
<p>Data hits Elasticsearch on the next poll cycle, the ingest pipeline classifies and enriches them, and the topology view populates. Start to finish, you're looking at minutes, not hours or days of trial and error.</p>
<p>A <a href="https://github.com/elastic/kibana-network-topology-plugin/blob/main/scripts/generate_sample_data.mjs">sample data generator</a> is included for teams that want to evaluate the plugin before connecting to live infrastructure; spin up a <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/install-elasticsearch-docker-basic">Docker development environment</a> and explore the full feature set with a realistic multi-site network.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/snmp-topology-data-kibana-collection-canvas</link>
    <guid isPermaLink="false">snmp-topology-data-kibana-collection-canvas</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[C. Pierce]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2ce47ec9d31d82c/6a7f1b0342a117161f95c31f/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Self-Driving Observability: From Stacktraces to Profiling-Derived Metrics]]></title>
    <description><![CDATA[Profiling-derived metrics turn raw stacktraces into time-series KPIs, unlock continuous profiling for every user and lay the foundation for an observability system that detects, investigates, and acts on its own.]]></description>
    <content:encoded><![CDATA[<p>Continuous profiling has come a long way. With the <a href="https://opentelemetry.io/blog/2026/profiles-alpha/">OpenTelemetry Profiles signal entering Alpha</a> and the <a href="https://github.com/open-telemetry/opentelemetry-ebpf-profiler">OpenTelemetry eBPF profiler</a> — donated by Elastic — now operating as a first-class OpenTelemetry Collector receiver, low-overhead, whole-system profiling on Linux is finally available to every OpenTelemetry user. No instrumentation, no recompilation, no service restarts. Just deploy the profiler and get visibility from the kernel, through native code, all the way up into HotSpot, Python, V8, .NET, Go, PHP, Perl, BEAM Erlang and Ruby runtimes.</p>
<p>The processing pipeline is straightforward: The profiler samples every CPU core on the system at a fixed rate
(19Hz by default), unwinds execution stacks, symbolizes the resulting stacktraces and ships the profiles to
a backend like Elasticsearch.</p>
<p>And then… the user has to figure out what to do with them.</p>
<p>That last step is where continuous profiling has historically faced adoption challenges, as
the path from "profiling is on" to "profiling is useful" is steeper than it should be.</p>
<h2 id="fourbarrierstoadoption">Four barriers to adoption</h2>
<ul>
<li><p><strong>Storage cost:</strong> Full stacktraces, even after deduplication and clever storage schemas, are expensive to store at fleet scale. That cost makes continuous profiling an opt-in feature in practice: a lot of potential users never enable it, and the ones who do, tend to enable it only on a subset of hosts.</p></li>
<li><p><strong>Query friction:</strong> A normalized stacktrace schema is optimized for ingestion and storage but complicates ad-hoc questions. "How much CPU time does my service spend in TLS?" is a simple question that may require intricate ES|QL or custom code in order to be answered.</p></li>
<li><p><strong>AI-hostile data:</strong> Normalized stacktrace data (typically involving multiple levels of indirection) resists straightforward algorithmic analysis. LLMs in particular struggle with it and necessitate further data transformations into representations more amenable to LLM processing.</p></li>
<li><p><strong>UX barrier:</strong> Flamegraphs are extremely useful when you know how to read them but intimidating when you don't.</p></li>
</ul>
<p>These four barriers compound: storage cost limits coverage, the UX barrier limits who benefits from coverage, query friction limits what questions users can ask and the AI-hostile data representation limits what the system can do when users don't know what questions to ask.</p>
<h2 id="howprofilingderivedmetricsworkclassifyattheedge">How profiling-derived metrics work: classify at the edge</h2>
<p>The core idea is simple: instead of sending full stacktraces all the way to a backend and asking the user to make sense of them there, we classify and count at the edge, inside an OpenTelemetry Collector pipeline, and emit ordinary OpenTelemetry time-series counters. The profiling logic itself doesn't change; it's still the OpenTelemetry eBPF profiler running inside the OpenTelemetry Collector. All the new work happens in a stateless connector inside the Collector: the connector inspects each stacktrace produced by the profiler, classifies its frames into one or more categories and increments counters.</p>
<p>We've released <a href="https://github.com/elastic/opentelemetry-collector-components/tree/main/connector/profilingmetricsconnector"><code>profilingmetricsconnector</code></a> as part of Elastic's <code>opentelemetry-collector-components</code> repository. It sits between the OpenTelemetry eBPF profiler receiver and any metrics exporter, and turns symbolized stacktraces into named, aggregated counters with attributes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5bb4283e563187e2/6a7f197c33fa8a9fe8202b6c/profilingmetricsconnector-pipeline.svg" alt="profilingmetricsconnector pipeline" /></p>
<p>Because the profilingmetricsconnector lives inside the standard OpenTelemetry Collector pipeline, every metric it produces flows through the same processors as the rest of your telemetry. In the following example, the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/resourcedetectionprocessor/README.md"><code>resourcedetectionprocessor</code></a> enriches each counter with host-derived attributes.</p>
<pre><code>connectors:
  profilingmetrics:
    flush_interval: 30s

receivers:
  profiling: {}

exporters:
  elasticsearch:
    endpoints:
      - # ENDPOINT
    api_key: # API_KEY
    mapping:
      mode: otel

processors:
  resourcedetection:
    detectors: ["system"]
    system:
      hostname_sources: ["os"]
      resource_attributes:
        host.name:
          enabled: true
        host.id:
          enabled: false
        host.arch:
          enabled: true
        os.description:
          enabled: true
        os.type:
          enabled: true

service:
  pipelines:
    profiles:
      receivers: [ profiling ]
      exporters: [ profilingmetrics ]
    metrics:
      receivers: [ profilingmetrics ]
      processors: [resourcedetection]
      exporters: [ elasticsearch ]
</code></pre>
<h2 id="profilingderivedcpumetricswhatgetsemitted">Profiling-derived CPU metrics: what gets emitted</h2>
<p>The connector ships with a set of pre-baked counters built from useful classification rules. Each metric is a count of stacktrace samples whose leaf frame matched a particular category, with the frequency value standing in for CPU consumption.</p>
<p>| Metric | Classifies | Attached metadata |
|---|---|---|
| <code>kernel.count</code> | Kernel leaf frames | <code>syscall</code>, <code>category</code> (<code>disk/rw</code>, <code>ipc/rw</code>, <code>network/{tcp,udp,other}/rw</code>, <code>memory</code>, <code>synchronization</code>, …) |
| <code>native.count</code> | Native C/C++/Rust leaf frames | shared library name (<code>libcrypto</code>, <code>libclrjit</code>, <code>libsystemd</code>, …) |
| <code>hotspot.count</code>, <code>go.count</code>, <code>python.count</code>, … | Runtime-specific leaf frames | runtime-specific attributes |</p>
<p>The kernel categorization is worth a closer look as a modern Linux kernel has more than 400 system calls. However, most of what shows up in CPU stacktraces falls into a handful of subsystems: filesystem read/write, network read/write, memory management, scheduling, synchronization. Some syscalls (e.g. <code>read</code>, <code>write</code>) are ambiguous on their own and only become specific when one examines more frames down the stack: <code>ext4_file_read_iter</code> points to filesystem, <code>tcp_v4_rcv</code> to network. The connector handles this disambiguation as part of frame iteration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt282d1eb8637b61fc/6a7f197fb4377095a24d70e6/kibana-kernel-cpu-by-category.png" alt="Kernel CPU breakdown by category in Kibana" /></p>
<p>Native frames typically lack symbolic information beyond shared library names, but those names are still informative: <code>libssl</code> and <code>libcrypto</code> mean cryptographic work as part of OpenSSL or one of its variants; <code>libz</code> means compression; <code>libclrjit</code> means the .NET JIT is busy. We don't need to enumerate libraries statically as the connector dynamically generates <code>shlib_name</code> attribute values using the trimmed library name (e.g. <code>libssl</code> not <code>libssl.so.3</code>) for clean cardinality.</p>
<p>Currently, for each stacktrace, the connector computes a <strong>Self CPU</strong> count (the leaf frame matched the category) corresponding to exclusive CPU usage. A complication exists for fine-grained kernel categories like <code>network/tcp/write</code> where the actual leaf frame is usually a device-driver call that we can't meaningfully match. We deal with that by trying to match frames further up the stack (e.g. <code>tcp_sendmsg</code> is enough to correctly classify the sample).</p>
<p>Users can also add their own categories by specifying a frame pattern (e.g. a function or package) and the connector will generate counters for them.</p>
<h2 id="benefitsofprofilingderivedmetricsforobservability">Benefits of profiling-derived metrics for observability</h2>
<p>This shift looks small from the outside — "we're emitting counters" — but it changes almost everything about how profiling fits into an observability stack.</p>
<ul>
<li><p><strong>Orders of magnitude less storage:</strong> A counter aggregated over a 5-second (or 30-second or one-minute) window is dramatically cheaper than the full stacktraces it distills. The pre-aggregation interval is configurable with the trade-off being time resolution rather than categorization fidelity. For most "where is my CPU being spent?" questions, 30 seconds is plenty.</p></li>
<li><p><strong>On by default:</strong> Because the storage cost is now in line with regular metrics, profiling-derived metrics can be on for everyone, on every host, from the moment the profiler is deployed. Users get a CPU breakdown by runtime, syscall, kernel category and shared library on day one.</p></li>
<li><p><strong>Standard dashboards:</strong> These are ordinary OpenTelemetry time-series counters and can be visualized ad-hoc using stacked bar graphs, pie charts, top-N panels or any other visualization Kibana supports out of the box. The same Lens and TSDB-backed views for application metrics work here.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte715bd8eb945ab75/6a7f198296b5a6391f87b86b/kibana-user-cpu-over-time.png" alt="User CPU by frame type over time in Kibana" /></p>
<ul>
<li><p><strong>AI and query-friendly:</strong> Standard time-series data is trivially consumable by ES|QL, ML jobs, anomaly detectors and by LLMs. "Show me the top services by <code>network/udp/write</code> time, filtered to the payments namespace, over the last six hours" is one query that is not only simple for the system to answer but also simple for an LLM to generate.</p></li>
<li><p><strong>Cross-signal correlation:</strong> Because the metrics flow through the standard OpenTelemetry Collector pipeline, they pick up the same resource attributes (e.g. <code>service.name</code>, <code>k8s.pod.name</code>, <code>host.name</code>, <code>deployment.environment</code>) that logs, other metrics and traces already carry.</p></li>
<li><p><strong>Instant value, with a path to more detail:</strong> A user who just wants to know "what's burning my CPU?" gets a meaningful answer without ever opening a flamegraph. A user who wants to dig deeper still has the full eBPF profiler underneath, ready to hand back complete stacktraces when they're warranted.</p></li>
</ul>
<h2 id="userprogrammableprofilingandadaptivesampling">User-programmable profiling and adaptive sampling</h2>
<p>The longer-term direction is for the profiler to stop being something users <em>consume</em> and start being something they <em>program</em>. User-defined metrics are the first step in this direction, complemented by on-demand (full) profiling and adaptive sampling.</p>
<p>Profiling-derived metrics or other signals can act as a trigger for on-demand profiling where the system enables full profiling on a specific host or service to capture complete stacktraces. In that way, the full profiling processing and storage cost is paid only when it matters.</p>
<p>We can apply the same idea to the sampling rate. 19Hz is a sensible baseline for steady state but when the metrics signal an interesting event or an anomaly, the system can automatically ramp to 100Hz or higher to capture high-fidelity data for the time window during which it's relevant. It can then ramp down to baseline.</p>
<h2 id="howprofilingderivedmetricsenableselfdrivingobservability">How profiling-derived metrics enable self-driving observability</h2>
<p>Most observability stacks today use an open-loop model: the profiler emits data with a fixed configuration. Then a human looks at flamegraphs and dashboards, potentially correlates with logs, other metrics and traces, forms a hypothesis and triggers a deeper investigation. Every link in this chain requires a human decision. Nothing feeds back into the profiler at speed and the system cannot act on its own observations.</p>
<p>Profiling-derived metrics close that loop.</p>
<ol>
<li><p>A "significant host events" metric, an anomaly on <code>network/udp/write</code> or a spike in <code>native.count/libz</code>: something crosses a threshold.</p></li>
<li><p>The profiler adjusts in response: sampling rate increases, full profiling turns on for the affected hosts.</p></li>
<li><p>The richer data is correlated against logs, traces, and other metrics by an LLM, by a human or both. The same resource attributes that make cross-signal correlation easy for the user make it easy for the system.</p></li>
<li><p>A root cause is identified. A remediation is suggested or applied. The metric returns to baseline and the loop continues.</p></li>
</ol>
<p>This is what we mean when we talk about <em>self-driving observability</em>. The profiler is no longer just an instrument that someone wields. It is the sensory organ of an autonomous feedback loop: a system that observes itself, decides what to look at more closely and adjusts its own configuration in response to what it sees.</p>
<h2 id="whatsnextinclusivecpuoffcpumetricsandruntimespecificprofiling">What's next: inclusive CPU, off-CPU metrics, and runtime-specific profiling</h2>
<p>Any piece of data visible in a stacktrace can be a metric source and several extensions are already on the roadmap.</p>
<ul>
<li><p><strong>Inclusive-CPU metrics:</strong> Today's pre-baked counters attribute CPU at the leaf frame (exclusive-CPU). Inclusive-CPU metrics will attribute the entire call chain which is useful when you care about the total cost of a function call — the function plus everything it transitively calls — not just the work done directly in its own body.</p></li>
<li><p><strong>Runtime-specific metrics:</strong> GC time per runtime, JSON/Protobuf serialization, RPC frameworks, FFI boundaries. The kinds of questions every team eventually asks about their language runtime, answered by default.</p></li>
<li><p><strong>Off-CPU metrics:</strong> On-CPU profiling tells you where you're spending CPU but Off-CPU profiling tells you where you're <em>not</em> (e.g. blocked on I/O, locks). The same classification logic applies, with the only change being the source signal.</p></li>
</ul>
<p>Profiling-derived metrics are an active area of work within Elastic and the <a href="https://github.com/elastic/opentelemetry-collector-components/tree/main/connector/profilingmetricsconnector">profilingmetricsconnector</a> is the place to start if you want to play with this today. A ready-made <a href="https://www.elastic.co/docs/reference/integrations/profilingmetrics_otel">Kibana integration</a> ships dashboards for all the metrics described above.</p>
<p>If you're already using Elastic's continuous profiling, expect these metrics to show up as first-class citizens in the Elastic stack. If you're not, this is a very low-friction way in as no flamegraph expertise is required and storage
cost is minimal.</p>
<p>The flamegraph isn't going anywhere, but for the first time, it isn't the <em>only</em> way profiling yields results.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-profiling-metrics</link>
    <guid isPermaLink="false">otel-profiling-metrics</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Christos Kalkanis,Roger Coll]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt418826f669e25898/6a7f19859090b02bc984ee13/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[30x faster than Prometheus: how we rebuilt Elasticsearch as a leading columnar metrics datastore]]></title>
    <description><![CDATA[Elasticsearch now stores OTel metrics at 3.75 bytes per data point and queries them up to 30x faster than Prometheus. Here's how we rebuilt TSDS and ES|QL.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch now stores OTel metrics at <strong>3.75 bytes per data point</strong> — down from 25 bytes a year ago — and queries them up to <strong>30x</strong> faster and with up to <strong>2.5x</strong> better storage efficiency, compared to <strong>Prometheus</strong>, <strong>Mimir</strong> and <strong>ClickHouse</strong>. These gains came from rebuilding <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">TSDS</a> storage and the ES|QL compute engine into a <strong>fully columnar metrics engine</strong>, with native OTel ingestion added as part of the effort — all while keeping Elasticsearch's ability to store and query logs, traces, and any other data alongside metrics.</p>
<p>Elasticsearch has supported storing metrics in time-series data streams (<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">TSDS</a>) since <strong>version 8.7</strong>. This offering mainly focused on storage gains as explained in an earlier <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins">blog post</a>. Still, performance was not on par with specialized systems for storing and querying metrics, in terms of storage efficiency, indexing throughput and query latency.</p>
<p>In the past year, we revisited the storage layer, optimized ingestion for OTel metrics and extended the ES|QL compute engine with vectorized processing for time series data. These efforts led to substantial performance wins across the board, compared to earlier versions of TSDS:</p>
<ol>
<li>Up to <strong>6.6x</strong> improvement in storage efficiency, reaching 3.75 bytes per data point in OTel metrics</li>
<li>Up to <strong>50%</strong> improvement in indexing throughput for OTel data</li>
<li>Up to <strong>160x</strong> improvement in query latency, including blazing fast counter rate evaluation and window support in time series aggregations</li>
</ol>
<p>Elasticsearch has thus become a <strong>leading columnar metrics engine</strong>, matching or exceeding the competition (like <strong>Prometheus</strong>, <strong>Mimir</strong>, and <strong>ClickHouse</strong>) in indexing throughput and exceeding it by up to <strong>2.5x</strong> in storage efficiency and <strong>30x</strong> in query performance. All while maintaining the ability to store logs and other data and fully use the rich querying capabilities of ES|QL (e.g. <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">inline stats</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">lookup join</a>) — which other PromQL-based systems lack. Elasticsearch can thus serve as a unified storage and query engine for all user data, with no compromises for metrics and observability applications.</p>
<h2 id="howtsdsisorganized">How TSDS is organized</h2>
<p>TSDS has the following properties that help improve the performance of time-series codecs and produce correct results when aggregating data points per time series:</p>
<ul>
<li>The <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">metric</a> name and the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-dimension">dimension</a> names and values are used to calculate the <code>_tsid</code>, a unique identifier per time series.</li>
<li>TSDS get sorted by <code>[_tsid ascending, timestamp descending]</code> order. Each time series is thus stored in sequence on disk, with newer data points appearing first. Since the <code>_tsid</code> is calculated over dimension values, the latter are also clustered on disk.</li>
<li>Shard routing is based on <code>_tsid</code>, with each <code>_tsid</code> value appearing in one shard only.</li>
<li>Backing indices are <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-bound-tsds">time-bound</a>, with no overlap over time between them.</li>
</ul>
<p>The rest of this post explains how we use these properties to improve storage, indexing, and query performance.</p>
<h2 id="storageoptimizations">Storage optimizations</h2>
<p>TSDS <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins">already</a> achieved a very competitive storage footprint, reaching <strong>0.9 bytes per data point</strong>, when it is possible to combine many metrics in a single doc, sharing the same dimension values. However, when most data points have a unique set of dimensions (which is typical for OTel or Prometheus metrics), docs end up containing a single data point. In this setup, storage required 25 bytes per data point, with dedicated metrics stores requiring less than 10 bytes per data point.</p>
<p>To further reduce the storage footprint, we applied a series of optimizations over the past year:</p>
<h3 id="replaceinvertedindicesandbkdtreeswithdocvalueskippers">Replace inverted indices and BKD trees with doc value skippers</h3>
<p>Elasticsearch creates inverted indices (for text values) or BKD trees (for numeric values) by default for all non-metric fields, i.e. for <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams#backing-indices">@timestamp</a> and dimensions. These indices improve performance for queries including filters on these fields, but have significant impact to storage — effectively doubling the footprint for each field. More so, they are also processed during <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">segment merging</a>, increasing the cpu, memory and storage overhead and slowing down the system — especially in high ingest throughput scenarios, as is often the case with metrics.</p>
<p>Lucene has been extended with <a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/index/DocValuesSkipper.html">doc value skippers</a>, a form of hierarchical sparse indices that store the minimum and maximum value of blocks of documents. Range queries can check these min and max values and ignore blocks that don't fall into the requested range. Skippers work particularly well on sorted fields. Since TSDS are sorted by <code>[_tsid, timestamp desc]</code>, dimension values get also clustered on disk. It's therefore possible to replace indices on <code>@timestamp</code> and dimension fields with doc value skippers that <strong>amplify the columnar layout</strong> — each field stored in its own files, with no duplicate tracking of each doc for indexing purposes.</p>
<p>Doc value skippers have negligible storage overhead — replacing indices with them led to a reduction of <strong>10 bytes</strong> out of the initial 25 bytes per data point in OTel. Moreover, they work very well in practice when queries include filters on time ranges or dimension values (including prefixes and regex) — there was no noticeable regression in query performance in our benchmarks when they replaced separate indices. Doc value skippers are enabled for TSDS by default since <strong>version 9.3</strong>.</p>
<h3 id="enablesyntheticids">Enable synthetic ids</h3>
<p>The <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-id-field"><code>_id</code></a> metadata field was another big contributor to the storage footprint. TSDS has already been extended to trim the doc values once they were no longer needed for replication, but the inverted index was kept around to efficiently support the id-based APIs (<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-get">Get</a>, <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-delete">Delete</a>, <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update">Update</a>).</p>
<p>The id value for TSDS is synthesized by combining the <code>_tsid</code> and <code>@timestamp</code> values that uniquely identify each data point. Since these fields are configured with doc value skippers, it's possible to replace the inverted index on <code>_id</code> with (a) retrieval of the <code>_tsid</code> and <code>@timestamp</code> value from the <code>_id</code> value, and (b) checks for matches using doc value skippers respectively. Care has to be taken to avoid expensive checks for duplicate ids during metric ingestion, with segment-level bloom-filters keeping the overhead at bay.</p>
<p>Supporting synthetic ids in metrics is a first for Elasticsearch. It led to a reduction of <strong>5 bytes</strong> out of the initial 25 bytes per data point for OTel metrics, with no loss of functionality. Synthetic ids are enabled for TSDS by default in <strong>version 9.4</strong>. We plan to extend their uses in logs and other applications after further evaluation.</p>
<h3 id="trimsequencenumbers">Trim sequence numbers</h3>
<p>Sequence numbers are used as part of replication, but also to provide strong consistency semantics on doc modification operations through <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/optimistic-concurrency-control">Optimistic Concurrency Control</a> (OCC). While such semantics are applicable to certain scenarios, they don't fit in metrics where concurrent updates are very rare, with no practical need for guarding against concurrent operations on data points with matching ids. We therefore decided to <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-disable-sequence-numbers">disable the use of sequence numbers</a> in all APIs, along with OCC support, for TSDS, in <strong>version 9.4</strong>. This leads to a substantial storage reduction of <strong>4 bytes</strong> out of the initial 25 bytes per data point for OTel data, as there's no inverted index and sequence numbers get trimmed once no longer needed for replication. <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-by-query-api">Update</a> and <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-delete-by-query">delete</a> by query operations are still supported, albeit with weaker consistency semantics.</p>
<p>If OCC is still deemed important for a particular metrics application, the old behavior can be restored by setting <code>index.disable_sequence_numbers: false</code> in the index template of the involved TSDS.</p>
<h3 id="uselargenumericcodecblocks">Use large numeric codec blocks</h3>
<p>TSDS already uses an advanced codec, as explained in an earlier <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins#specialized-codecs">article</a>. The codec works very well in most cases, but has poor performance in case of repeated sequences of keywords and numbers, leading to an inflated storage footprint for dimensions containing IP and MAC addresses. We identified that the existing logic for identifying repeated sequences requires larger codec blocks to work well, especially as the sequence length increases. After experimentation, the numeric block size was increased from 128 to 512 elements in <strong>version 9.3</strong>, leading to a reduction of <strong>2 bytes</strong> out of the initial 25 bytes per data point for an OTel dataset containing IP and MAC addresses as dimensions. We're also working on a more configurable codec layout that will allow more flexibility with block sizes and other parameters, based on field type and cardinality.</p>
<h2 id="indexingthroughput">Indexing throughput</h2>
<p>Elasticsearch has support for bulk ingestion of documents. This entrypoint has long been optimized for leniency, ensuring that all docs get accepted. This flexibility, however, incurs additional processing cost during indexing. Metric applications proved good candidates for using different approaches to reduce this overhead, as explained below.</p>
<h3 id="introduceotlpprotobufentrypoint">Introduce OTLP protobuf entrypoint</h3>
<p>OTel metrics and Prometheus have established protocols for metrics ingestion, using protocol buffers. In the past, a translation step was required to convert collected protobuf messages to bulk requests that Elasticsearch can consume.</p>
<p>Elasticsearch was recently extended with endpoints accepting messages from OTel metrics collectors and over Prometheus remote write. Parsing and processing these (binary) messages is cheaper, compared to json parsing, while hash operation over dimensions for <code>_tsid</code> calculations get reused and amortized across more data points within a single protobuf message. Furthermore, <code>_tsid</code>s get evaluated once per doc in the coordinator nodes and propagated to data nodes for indexing, thus deduplicating an expensive step per indexed doc. These improvements led to up to a 20% speedup in indexing throughput for OTel metrics. The OTLP entrypoint was added in version 9.2 (tech preview) and reached GA in <strong>version 9.3</strong>. We've added similar entrypoints for <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus remote write</a> in <strong>version 9.4</strong> (tech preview) and are actively working to cover OTel Logs and Traces.</p>
<h3 id="reduceindexingcpuwithdocvalueskippers">Reduce indexing CPU with doc value skippers</h3>
<p>In addition to a substantial storage footprint, inverted indices require a lot of cpu to build and reconstruct during segment merging. The use of doc value skippers in their place helps also reduce cpu load at ingestion and thus improves indexing throughput by 10%, a welcome bonus on top of the aforementioned storage wins.</p>
<h3 id="syntheticrecoverysource">Synthetic recovery source</h3>
<p>The original <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field">source</a> of a document, as provided at index time, is never stored for metrics. Still, Elasticsearch needed to temporarily store it for replication purposes. That changed in <strong>version 9.1</strong>, where the source gets synthesized on demand for replication purposes. This is known as synthetic recovery source and reduces disk I/O by 50%, with a significant impact to metrics indexing performance. Check out this <a href="https://www.elastic.co/search-labs/blog/elastic-logsdb-tsds-enhancements">article</a> for more details.</p>
<h2 id="queryexecution">Query execution</h2>
<p>Replacing inverted indices with doc value skippers leads to a pure columnar storage layout for TSDS, with metric and dimension fields stored as Lucene doc values, each field encoded and compressed in their own file. Combined with the introduction of the <a href="https://www.elastic.co/blog/elasticsearch-query-language-esql#dedicated-query-engine">ES|QL compute engine</a> that uses vectorized execution internally, it became possible to introduce a fully columnar storage and query processing engine for metrics in Elasticsearch. We pushed this idea to the extreme and implemented a <strong>columnar metrics processing engine</strong> that comfortably outperforms dedicated metrics engines and other columnar stores in query performance.</p>
<h3 id="timeseriesintegrationincomputeengine">Time series integration in compute engine</h3>
<p>Time series processing is largely based on applying aggregation functions per time series (or <code>_tsid</code>), such as a <a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge">gauge</a> average or a <a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums">counter</a> rate. These partial results are then reduced by a secondary function to produce results for the grouping dimensions, e.g. per host and process. Observability dashboards are built on top of this execution model, providing summary views of how metrics evolve over time and allowing for quick deep-dives by filtering on dimension values and time ranges.</p>
<p>To support this execution model, we introduced the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts#description">TS source command</a>, providing a simple yet powerful syntax for executing such queries that combine an inner aggregation function per time series with an outer aggregation over the partial results of the former. For instance, the following query calculates the hourly sum of rate of search requests per host over the last day:</p>
<pre><code>TS metrics
  | WHERE TRANGE(1d)
  | STATS SUM(RATE(search_requests)) BY TBUCKET(1h), host
</code></pre>
<p>To execute this query, the compute engine is aware of how data is stored and applies the inner aggregation function per <code>_tsid</code> value. Since data are sorted by <code>_tsid</code>, time series aggregation functions process metric values as they get fetched, until the <code>_tsid</code> changes or the timestamp belongs to the next time bucket. This leads to vectorized execution of these functions over the fetched columns of metric values, while dimension values are only fetched (once) when the <code>_tsid</code> changes. The evaluation of the secondary aggregation function is also efficient, with partial aggregates stored in arrays of primitive values that get populated when <code>_tsid</code> values change.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf3aa04dd3516c3fb/6a859a248c294428fdb88511/image12.png" alt="Vectorized time series aggregation execution" /></p>
<p>The compute engine has inherent support for parallel query evaluation, taking full advantage of the available processing cores. Time series aggregations fully use this feature and process data points in parallel as applicable, reducing response times through improved cpu utilization.</p>
<p>Time series processing in ES|QL was introduced in version 9.2 as tech preview and reaches GA in <strong>version 9.4</strong>. We expect all metrics applications to adopt it and benefit from the much improved query performance wins.</p>
<h3 id="zerocopydatadecodingandloading">Zero-copy data decoding and loading</h3>
<p>Vectorized processing of time series data delivered immediate performance wins (<strong>8x</strong> for some queries), compared to aggregations through the <code>/_search</code> API, but the performance was still inferior when compared to competitive metrics stores. Benchmarking and profiling showed that there were too many array copies within the compute engine, between data decoding and evaluation of aggregation functions. To that end, the following optimizations were introduced:</p>
<ul>
<li>The codec for TSDS was extended to decode on-disk data directly into primitive arrays inside blocks that the compute engine uses to evaluate time series aggregations. No additional copies required, as the compute engine can bulk-read these blocks and process their arrays, one column at a time.</li>
<li>Blocks containing a single value N times are represented as constant blocks with these 2 values, as opposed to an array with length N, a form of in-memory run-length encoding. Filtering and aggregation operations were extended to efficiently consume these blocks. This reduced memory pressure and cpu overhead for the <code>_tsid</code> and dimension fields, as their values get clustered due to index sorting.</li>
<li>Documents with null values for the aggregated metric fields are filtered out at the Lucene level, before they get decoded and copied into blocks.</li>
<li>All filters and regular expressions on the timestamp and dimension fields get pushed down to Lucene that makes use of doc value skippers to efficiently filter out non-matching docs.</li>
</ul>
<p>Combined, these optimizations led to query execution speedups exceeding <strong>10x</strong> (totaling 80x when combined with the 8x speedup from vectorized execution). They were included since the introduction of the TS source command in <strong>version 9.2</strong>, and fine-tuned ever since.</p>
<h3 id="optimizedcounterrateevaluation">Optimized counter rate evaluation</h3>
<p>While most time series aggregations can be trivially parallelized and evaluated, rate evaluation of cumulative counters is tricky as it requires processing all data points in order to detect counter resets (e.g. when a host restarts). To address this, the compute engine uses the <code>_tsid</code> prefix to shard time series across threads. Care has been taken to assign in-order ranges of <code>_tsid</code> values to each thread, as opposed to hash-partitioning <code>_tsid</code>s, so that each thread can scan on-disk data in order, still making use of efficient decoding and zero-copying into blocks. The performance wins are impressive, with rate evaluation performance far exceeding dedicated metrics stores as we shall see in the next section.</p>
<p>Another interesting problem for cumulative counters is how to properly calculate counter increases for the entire time bucket when there are no data points at the bucket boundary timestamps. Metrics systems often use extrapolation, extending the first and last data points of each time bucket to the boundaries, or calculate the delta between the last data point of adjacent buckets. We posit we can do better, by interpolating between the last data point of each bucket and the first of the next, to get an estimate of the value on each boundary. The delta is then calculated over the interpolated values of the lower and upper boundary of each time bucket.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f30a4e56924facc/6a859a28e2447a3c268b085b/image10.png" alt="Counter rate interpolation across time bucket boundaries" /></p>
<h3 id="slidingwindowsupport">Sliding window support</h3>
<p>Elasticsearch has long supported aggregations bucketed by time, but it was not possible to extend the window of processed data beyond each time bucket. Using windows larger than the time bucket, e.g. a window of 5 minutes for per-minute bucketing, helps smoothen out spikes and observe the underlying trend per time series with reduced noise:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta471cf615145ac8d/6a859a2bf61d6e6e8e9c2037/image3.png" alt="Sliding window smoothing example" /></p>
<p>All <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">time-series aggregation functions</a> have been extended with window support, as an optional argument. In case the window is a multiple of the time bucket (e.g. 1h window with <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions/tbucket"><code>TBUCKET</code></a><code>(5m)</code>), the compute engine first aggregates data points over intervals matching the time bucket span, and then combines these partial results per window span. This 2-phase approach eliminates repeated scans of data points and makes optimal reuse of intermediate results, improving response times. Window support was introduced as tech preview in version 9.3 and reaches GA in <strong>version 9.4</strong>.</p>
<h3 id="efficientdatetimerounding">Efficient datetime rounding</h3>
<p>Queries on time-series data commonly include time bucketing. While data points can be trivially assigned to sub-hour time buckets, larger buckets start interfering with issues like time zones, daylight savings, variable days per month etc. Elasticsearch has elaborate logic for datetime rounding that takes these peculiarities into account, but that has relatively high cpu cost when processing time series data.</p>
<p>To mitigate this, the compute engine has been extended to identify cases where simpler logic can be employed to assign data points to time buckets. For instance, it can identify when the buckets are sub-hour or when timezones and daylight savings don't affect a particular query, and switches to simple modulo operations for datetime rounding. This led to a further <strong>30%</strong> improvement in response times for certain queries. This change is introduced in <strong>version 9.4</strong>.</p>
<h2 id="performanceevaluation">Performance evaluation</h2>
<p>To evaluate the performance of our offering and track how it evolves and improves over time, we focused on OTel metrics since (a) Open Telemetry is the industry standard for collecting metrics, with universal adoption by all cloud providers and (b) they lead to a storage layout with 1 metric per doc, a setup that traditionally hurt performance for Elasticsearch.</p>
<p>We rely on <a href="https://github.com/elastic/metricsgenreceiver">Metricsgenreceiver</a> to generate datasets. This tool is inspired by <a href="https://github.com/timescale/TSBS">TSBS</a>, producing data simulating the data points collected by the OTel <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/hostmetricsreceiver#readme">hostmetricreceiver</a>. We used two datasets:</p>
<ol>
<li>A low-cardinality setup, with 100 hosts sending metrics every 1s, containing 14k time series in total</li>
<li>A high-cardinality setup, with 10k hosts sending metrics every 10s, containing 1.4M time series in total</li>
</ol>
<p>We benchmarked on single-node deployments on EC2, using <a href="https://aws.amazon.com/ec2/instance-types/c6i/">c6i.4xlarge</a> and <a href="https://aws.amazon.com/ec2/instance-types/c8g/">c8g.8xlarge</a> machines for the low- and high-cardinality datasets respectively.</p>
<p>For competitive comparison, we used Prometheus (v.3.11.1), Mimir (v.3.0.6.) and ClickHouse (v26.3.9.8-lts). Prometheus and Mimir have proper time series processing, e.g. for counter rate, whereas ClickHouse <a href="https://clickhouse.com/docs/use-cases/time-series/analysis-functions">lacks such support</a> and only provides approximate values at best (for instance, it can't track counter resets consistently). We still report response times for ClickHouse to showcase that, once we optimize Elasticsearch for columnar query processing, it can exceed competing columnar engines even when they don't process the data per time series as expected.</p>
<p>We strived to use the default configuration for every system (including Elasticsearch), without tweaking them to optimize performance for the particular workload. This helps understand the user experience when systems are deployed by novice users, without much experience (or time) to tweak before receiving metrics traffic and setting up dashboards. We focused on single-node runs to keep noise low and accommodate all systems (Prometheus doesn't offer a multi-node setup out of the box). Elasticsearch performance provably scales well with the number of nodes; we plan to share scalability results in a future post.</p>
<h3 id="storageefficiencyandindexingthroughput">Storage efficiency and indexing throughput</h3>
<p>Our efforts to improve storage efficiency paid big dividends. Performance on OTel metrics dropped <strong>from 25 to 3.75</strong> bytes per data point, in a year. Such an improvement, on top of an offering already optimized for time series, is really impressive and very rare in the industry:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f9e6b91c5dcdaa1/6a859a2e98292617ab582db0/image1.png" alt="Storage efficiency improvements over time" /></p>
<p>The competitive picture looks favorable at this point, with Elasticsearch:</p>
<ul>
<li>Slightly outperforming Mimir in storage efficiency and indexing throughput</li>
<li>Outperforming Prometheus by 2.5x in storage efficiency and by a small margin in indexing throughput</li>
<li>Outperforming ClickHouse by 2x in storage efficiency and by 40% in indexing throughput</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ccb2947bcbce791/6a859a31f9373d7ace96eb5b/image7.png" alt="Storage efficiency comparison across systems" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96ed82b099550f20/6a859a344710c6021ad3c055/image11.png" alt="Indexing throughput comparison across systems" /></p>
<h3 id="queryperformance">Query performance</h3>
<p>The novel columnar engine for metrics processing proves very efficient in practice. We used a mix of queries based on gauge averages and counter rates, the most common operations that require different optimization approaches. The queried interval was 4 hours of data, covering all time series per metric.</p>
<p>ClickHouse doesn't support time series aggregations, so the results have limited value and are not directly comparable to Prometheus or Mimir that natively support time series processing. We used the published <a href="https://clickhouse.com/docs/use-cases/time-series/analysis-functions">guidelines</a> to adjust each query to get similar results to the extent possible. The point is to show how our columnar engine compares to generic columnar stores.</p>
<p>Here is a summary of the results:</p>
<p>| Query type | vs Mimir | vs Prometheus | vs ClickHouse †   |
|---|---|-------------------|-------------------|
| Gauge average | up to 30x faster | up to 30x faster  | up to 8x faster   |
| Counter rate | up to 30x faster | up to 30x faster  | up to 3.5x faster |
| Prefix filter on host name | up to 5x faster | up to 5x faster | up to 3x faster   |
| Gauge average with window | up to 25x faster | up to 25x faster | up to 4x faster   |</p>
<p>†ClickHouse lacks native support for time series aggregations and counter reset detection.</p>
<h4 id="gaugeaverage">Gauge average</h4>
<p>We compared performance of evaluating the per-host hourly average of average memory utilization per time series, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (avg_over_time(system.memory.utilization[1h]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(AVG_OVER_TIME(system.memory.utilization)) BY host.name, TBUCKET(1h)
</code></pre>
<p>Elasticsearch comfortably outperforms the other systems by up to 30x, in both low and high cardinality datasets:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf153d92f6c1f2452/6a859a378c2944279ab8851b/image14.png" alt="Gauge average query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta53ce295d2c310ee/6a859a39ba7acca4239916b7/image2.png" alt="Gauge average query performance — high cardinality" /></p>
<h4 id="counterrate">Counter rate</h4>
<p>We next compared performance of evaluating the per-host hourly average of cpu rate, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (rate(system.cpu.time[1h]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(RATE(system.cpu.time)) BY host.name, TBUCKET(1h)
</code></pre>
<p>Despite processing data points per time series in order, counter rate performance matches calculating gauge average (the involved time series have 6.6x more docs than the query above). Elasticsearch maintains its wide advantage compared to the other systems and outperforms Mimir and Prometheus by 30x in the low cardinality dataset and by 16x in the high cardinality one:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03149d9c0cc613b5/6a859a3cf5f1a0151a2ebf26/image4.png" alt="Counter rate query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a36f24c1eb3f55d/6a859a3ef61d6e4afb9c203f/image9.png" alt="Counter rate query performance — high cardinality" /></p>
<p>It's really impressive that, for the high cardinality dataset, Elasticsearch is able to process 4 hours of data for half a million time series in less than 2 seconds, while the other systems take more than 30 seconds, leading to unresponsive dashboards for such queries. ClickHouse is also slower, despite having no logic to detect counter resets and extrapolate/interpolate deltas across buckets.</p>
<h4 id="prefixfilteronhostname">Prefix filter on host name</h4>
<p>We next compared performance of filtering on host names based on their prefix, using the following queries:</p>
<pre><code># PromQL
avg by (host_name)
  (avg_over_time(system_cpu_load_average_1m{host_name=~"host-.*"}[5m]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| WHERE host.name LIKE "host-*"
| STATS AVG(AVG_OVER_TIME(system.cpu.load_average.1m)) BY host.name, TBUCKET(5m)
</code></pre>
<p>Elasticsearch manages to maintain an advantage of up to 5x compared to the other systems, despite replacing the inverted index on <code>host.name</code> with a doc value skipper:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32cffb96a5847bc2/6a859a418c29443b00b8851f/image5.png" alt="Prefix filter query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90f3cd1cabc4b8e0/6a859a44f5f1a019892ebf2a/image6.png" alt="Prefix filter query performance — high cardinality" /></p>
<h4 id="gaugeaveragewithwindow">Gauge average with window</h4>
<p>We compared the performance of time series aggregations with a window of 90 minutes and time buckets of 30 minutes, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (avg_over_time(system.memory.utilization[90m]))&amp;step=30m
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(AVG_OVER_TIME(system.memory.utilization, 90m))
    BY host.name, TBUCKET(30m)
</code></pre>
<p>Elasticsearch comfortably outperforms the other systems in both low and high cardinality datasets:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b30bd2052cb4c64/6a859a4ad6cf295c04bafe42/image13.png" alt="Gauge average with window — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c811ebf944ec4fa/6a859a4de2447a44c98b08bd/image8.png" alt="Gauge average with window — high cardinality" /></p>
<p>Elasticsearch maintains an advantage that reaches 25x for the low cardinality dataset and 8x for the high cardinality one. ClickHouse is outperformed by close to 4x, denoting the efficiency of our approach for windowed query operations.</p>
<h2 id="whatsnextforelasticsearchmetrics">What's next for Elasticsearch metrics</h2>
<p>Elasticsearch has been extended with metrics storage and processing capabilities that outperform Prometheus, Mimir, and ClickHouse. We're making fast progress with supporting <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL</a> and <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">Prometheus remote write</a>, also available as tech preview in <strong>version 9.4</strong>. These extensions enable users familiar with Prometheus and relevant systems to switch their applications to Elasticsearch — no need to migrate existing dashboards. Since Prometheus integration reuses the same storage and query engine that has been presented in this article, the same performance wins are also expected for Prometheus. Furthermore, collected metrics can be queried with PromQL and ES|QL, side-by-side or in ES|QL query pipelines, further boosting the analytics capabilities far beyond what was conceivable so far with Prometheus-based systems.</p>
<p>The improvements in storage efficiency, indexing throughput and query performance are already impressive, but we're not done. We'll be introducing more refinements to the codec for time series data, further reducing bytes per data point. Batch processing of ingested metrics will be further improved, reducing synchronization overhead and redundant processing layers that are not needed for well-formatted collected metrics. We're also planning to make wider use of doc value skippers, storing pre-computed aggregates like sum and count per block of values, to shortcut data point loading and processing where applicable, as well as use more cpu-friendly partitioning and grouping operations.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>What is a columnar metrics engine and why does it matter?</strong>
A columnar metrics engine stores each field in its own file rather than row-by-row, then processes queries by reading only the columns needed. For time series data, this means Elasticsearch can decode metric values, dimension fields, and timestamps independently, applying vectorized operations across each column. The result is faster aggregations and lower storage overhead compared to row-oriented stores.</p>
<p><strong>How does Elasticsearch compare to Prometheus for time series metrics storage and querying?</strong>
Elasticsearch stores OTel metrics at 3.75 bytes per data point in version 9.4, roughly 2.5x less than Prometheus. For queries, Elasticsearch outperforms Prometheus and Mimir by up to 30x in gauge average and counter rate benchmarks. For the high-cardinality dataset (1.4M time series), Elasticsearch processes 4 hours of data in under 2 seconds while Prometheus takes over 30 seconds.</p>
<p><strong>What is Elasticsearch TSDS and when should I use it?</strong>
TSDS (time-series data streams) is Elasticsearch's storage format for metrics and time series data. It sorts documents by time series identifier (<code>_tsid</code>) and timestamp, stores fields in columnar doc values, and uses specialized codecs for compression. Use TSDS for any metrics workload, particularly OpenTelemetry or Prometheus data, where storage efficiency and query speed matter.</p>
<p><strong>What is the TS source command in ES|QL?</strong>
<code>TS</code> is an ES|QL source command, GA in version 9.4, that executes time series queries using a two-level model: an inner aggregation per time series (such as <code>RATE()</code> or <code>AVG_OVER_TIME()</code>), then an outer aggregation over the results. The compute engine processes data in time series sort order, enabling vectorized and parallel execution. Example: <code>TS metrics | STATS AVG(RATE(cpu.time)) BY host.name, TBUCKET(1h)</code>.</p>
<p><strong>How did Elasticsearch go from 25 bytes to 3.75 bytes per OTel data point?</strong>
Four storage changes contributed across versions 9.1 through 9.4: replacing inverted indices with doc value skippers (-10 bytes), enabling synthetic IDs (-5 bytes), trimming sequence numbers (-4 bytes), and increasing codec block size from 128 to 512 elements (-2 bytes). The result is a 6.7x reduction in storage footprint in roughly one year.</p>
<p><strong>Can Elasticsearch replace Prometheus without migrating dashboards?</strong>
Elasticsearch supports Prometheus remote write (tech preview, version 9.4) and PromQL queries (tech preview, version 9.4). Existing Grafana dashboards using PromQL can point to Elasticsearch with minor modifications, and we expect to offer a seamless migration experience when our Prometheus offering reaches GA. The same TSDS storage and ES|QL compute engine power both PromQL and ES|QL queries, so the performance improvements apply to both.</p>
<p><strong>What are doc value skippers and why do they matter for metrics?</strong>
Doc value skippers are Lucene index structures that store min/max values for blocks of documents. For TSDS, which sorts by <code>_tsid</code> and timestamp, they replace inverted indices on dimension fields and <code>@timestamp</code>. This reduces storage by up to 10 bytes per data point and cuts indexing CPU by about 10%, with no measured regression in query performance for time range and dimension filters.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus</link>
    <guid isPermaLink="false">elasticsearch-columnar-metrics-engine-30x-faster-prometheus</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Kostas Krikellas,Martijn Van Groningen,Nhat Nguyen,Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2478264f91421cc/6a859a514710c6cf3fd3c083/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 19 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[Migrating Datadog and Grafana dashboards and alerts to Kibana with the Observability Migration Platform]]></title>
    <description><![CDATA[Learn how to migrate supported Datadog and Grafana dashboards and alerts to Kibana with the Observability Migration Platform.]]></description>
    <content:encoded><![CDATA[<p>The Observability Migration Platform is a CLI-driven workflow that translates supported Grafana and Datadog assets into Kibana-native outputs and produces the evidence needed to review the result. It changes migration from a manual rebuild into a translation-and-verification workflow that gets teams into <a href="https://www.elastic.co/docs/solutions/observability">Elastic Observability</a> faster.</p>
<h2 id="migrationscoveredbytheobservabilitymigrationplatform">Migrations covered by the Observability Migration Platform</h2>
<p>The current scope covers Datadog and Grafana. The platform can work from exported assets or live APIs, and it focuses on dashboards and alerting content on the Datadog and Grafana paths it currently covers.</p>
<p>Support is not identical across the two sources. Datadog has end-to-end extraction, validation, compile, upload, smoke, and verification workflows, but it currently covers a narrower slice of widgets and monitors. Grafana coverage is broader. The platform provides a practical translation pipeline for the supported paths.</p>
<p>The screenshots below show examples of dashboards after migration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3abf68e4222bf58c/6a7f0d64448e4eb8455c0739/migrated-dashboard-1.jpg" alt="Migrated Node Exporter Full dashboard in Kibana, top of page showing CPU, memory, network, and disk panels" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta6df9a306e4b0172/6a7f0d67ea068ddde7f09ec0/migrated-dashboard-2.jpg" alt="Migrated Node Exporter Full dashboard in Kibana, scrolled to the Memory Meminfo section showing detailed memory panels" /></p>
<h2 id="howtheobservabilitymigrationplatformworks">How the Observability Migration Platform works</h2>
<p>At a high level, the workflow has two halves: source-aware translation on the way in and target-aware validation and delivery on the way out. That split matters because Grafana and Datadog differ not only in JSON shape, but also in query languages, panel types, controls, and alerting models.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8951e8b7f57be9b/6a7f0d6abdcff0c74cc42e71/overview.png" alt="End-to-end flow of the Observability Migration Platform: extract from Grafana or Datadog, normalize and plan, translate queries, panels, and alerts, emit Kibana-native output, validate against an Elastic target, then compile and upload to Kibana while producing verification and review artifacts" /></p>
<p>A run starts with exported assets or live source APIs. From there, the workflow normalizes source-specific objects, chooses a translation path for each supported dashboard, panel, and alerting artifact, and emits Kibana-native output. This is where most of the source-specific logic lives: translating queries or Datadog formulas, mapping panel semantics, carrying forward controls and links where possible, and deciding when an exact translation is not the right answer.</p>
<p>The second half is target-aware. The emitted output can be validated against an Elastic target, compiled, and uploaded to Kibana through the shared runtime. In the happy path, that yields a working translated dashboard. In rougher cases, validation may show that a panel cannot run safely as emitted. When that happens, the workflow is designed to fail conservatively: it can mark the panel for manual review or replace it with an upload-safe placeholder instead of shipping a broken runtime panel.</p>
<p>Just as important, the outcome is not simply "a dashboard showed up in Kibana." The workflow also produces reviewer-facing evidence such as a migration report, manifest, verification packets, and rollout plan so you can see what translated cleanly, what was downgraded or manualized, and what still needs human judgment. Those artifacts are what make the process operationally credible: they give teams something concrete to inspect, compare, and act on.</p>
<h2 id="runningthemigration">Running the migration</h2>
<p>The platform is CLI-driven, and a good fit for migration work that needs to be repeatable, reviewable, and easy to automate. Users can start with a representative slice of dashboards and alerting content from Grafana or Datadog, point the workflow at an Elastic target, and use that first run to understand translation quality, validation results, and how much follow-up review is required.</p>
<p>To run the full path against Elastic, create an <a href="https://www.elastic.co/docs/solutions/observability/get-started">Elastic Observability Serverless</a> project, generate a <a href="https://www.elastic.co/docs/deploy-manage/api-keys/serverless-project-api-keys">Serverless project API key</a>, and point the CLI at your Elasticsearch and Kibana endpoints:</p>
<pre><code>obs-migrate migrate \
  --source grafana \
  --input-mode files \
  --input-dir ./grafana_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
</code></pre>
<p>The run validates the emitted queries against Elastic, compiles the generated dashboards, uploads them to Kibana, and produces the standard migration artifacts for review.</p>
<p>A typical run looks like this:</p>
<ol>
<li>Start with exported assets or live source APIs from Grafana or Datadog.</li>
<li>Choose the asset scope with <code>--assets dashboards</code>, <code>--assets alerts</code>, or <code>--assets all</code>.</li>
<li>Translate the supported dashboards, queries, controls, and alerting artifacts into Kibana-native output.</li>
<li>Validate the emitted content against an Elastic target (if configured), then compile and upload the translated dashboards for dashboard-capable runs.</li>
<li>Review the migration evidence, including <code>migration_report.json</code>, <code>verification_packets.json</code>, <code>run_summary.json</code>, etc., to understand what translated cleanly, where semantic gaps remain, and which dashboards, panels, or alert rules still require human review.</li>
<li>If alert rule creation is enabled, review the migrated rules (which are disabled by default) in Kibana before deciding which ones to enable or redesign.</li>
</ol>
<h2 id="whatsnext">What's next</h2>
<p>The platform is still evolving, and will continue to gain depth and self-service capabilities. The biggest open areas are stronger measured source-to-target semantic verification, further coverage for Datadog, deeper coverage for harder query families and non-dashboard surfaces, and cleaner shared runtime contracts across the workflow.</p>
<p>It is also built to grow over time. The source and target boundaries are explicit by design, which gives the platform room to expand coverage and support additional source paths in the future.</p>
<h2 id="inconclusion">In conclusion</h2>
<p>If you are planning a move into Elastic, a good starting point is to create an <a href="https://www.elastic.co/docs/solutions/observability/get-started">Elastic Observability Serverless</a> project. That gives you the target environment where translated dashboards and alerting content can be validated and reviewed.</p>
<p>To learn more about the migration workflow, talk to your Elastic representative about current access, supported coverage, and how it can help with your migration needs.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana</link>
    <guid isPermaLink="false">migrate-datadog-grafana-dashboards-alerts-to-kibana</guid>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Subham Sarkar,Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt750893f8fb0b487e/6a7f0d6ce02fac5af85d65ac/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 28 Apr 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[Your PromQL queries now run in Kibana!]]></title>
    <description><![CDATA[With PromQL now natively supported in Kibana, write and execute PromQL for analyzing metrics in Discover, in Dashboards visualizations, in alerting rules and wherever else ES|QL is supported. PromQL is currently available in Tech Preview for common metrics analytics use cases.]]></description>
    <content:encoded><![CDATA[<p>Since its initial development in 2012 alongside Prometheus, PromQL has been a cornerstone of time-series monitoring for over a decade.
While Kibana already comprehensively supports time-series analysis via the ES|QL TS command, we are thrilled to introduce native PromQL support for common metrics analytics use cases.
For teams already fluent in PromQL, this support means a near-zero learning curve and significantly easier onboarding directly into the Elastic ecosystem.</p>
<h2 id="runningpromqlqueriesinkibana">Running PromQL queries in Kibana</h2>
<p>In the ES|QL editor in Kibana, enter the <code>PROMQL</code> command, and type your PromQL in that block.
<code>PROMQL</code> marks that segment so Elasticsearch parses it as PromQL inside the wider ES|QL request Kibana sends.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09b0a036963d9786/6a7f1a0fb6b7341e46e491b8/promql-first-look.png" alt="Discover in ES|QL mode with a PROMQL query in the bar" /></p>
<h2 id="whatyoucanquery">What you can query</h2>
<p>Here are a few patterns to get started.</p>
<p><strong>Raw metric</strong></p>
<pre><code>PROMQL container.cpu.usage
</code></pre>
<p><strong>Average across all containers</strong></p>
<pre><code>PROMQL avg(container.cpu.usage)
</code></pre>
<p><strong><code>rate()</code> on a counter</strong></p>
<pre><code>PROMQL rate(docker.network.inbound.bytes)
</code></pre>
<p><strong>Aggregated rate</strong></p>
<pre><code>PROMQL sum(rate(docker.network.inbound.bytes))
</code></pre>
<p><strong>Group by a label</strong></p>
<pre><code>PROMQL sum by (agent.id) (rate(docker.network.inbound.bytes))
</code></pre>
<p>You may notice that none of these examples include <code>start</code>, <code>end</code>, <code>step</code>, or a lookback window on every <code>rate()</code>.
Those parameters are optional: the time picker and Kibana defaults handle most of it for you.</p>
<p>Optionally, you can include the data stream name using the <code>index=</code> parameter.
For example: <code>PROMQL index=metrics-docker.cpu-default container.cpu.usage</code>.
Adding the parameter helps narrow down the scope of what data the query scans.</p>
<p>The current release of PromQL tech preview has over 80% query coverage benchmarked against top Grafana dashboards.
Advanced modifiers and specific functions are in consideration for future releases.</p>
<h2 id="findyourstreamsandmetricnames">Find your streams and metric names</h2>
<p>If you have existing PromQL queries, you can use them directly in the <code>PROMQL</code> command without changes.
If you are writing a query from scratch and need to find the exact field names, run <code>TS metrics-*</code> in Discover to see every metrics data stream.
Each metric appears as a small chart so you can tell at a glance what is active.
Hover over a metric and click the "View details" action to see the field name and the data stream it belongs to.</p>
<p>For a deeper walkthrough, see <a href="https://www.elastic.co/docs/solutions/observability/infra-and-hosts/discover-metrics">Explore metrics data with Discover in Kibana</a>.</p>
<h2 id="timepickerandquerytimehandling">Time picker and query time handling</h2>
<p>The time picker in Kibana sets the time window for the query.
Dashboard panels and Alerting rules work the same way using their own time range, so you do not need to write <code>start=</code> or <code>end=</code> in the query itself.</p>
<p>Step is the gap between two consecutive data points on the chart.
A smaller step means more data points across the same span.
If you do not set <code>step=</code> or <code>buckets=</code>, the default is <code>buckets=100</code>.
You can set <code>step=</code> to a fixed width such as <code>1m</code>, or set <code>buckets=</code> to a different target maximum number of data points.</p>
<h2 id="discoveranddashboards">Discover and Dashboards</h2>
<p>In Discover, switch to ES|QL mode and run your <code>PROMQL</code> query so you can see how the metric behaves over the range you pick, as a time-series chart.
When you want to save that visualization, choose "Save visualization to dashboard" and add it to a new or existing dashboard.</p>
<p>Or go to Dashboards directly: add a panel, choose ES|QL, and write your <code>PROMQL</code> query.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt224342ac307c9dc8/6a7f1a1242a117add795c2ed/dashboard-promql.png" alt="Dashboard: ES|QL visualization with PromQL" /></p>
<h2 id="alerting">Alerting</h2>
<p>You can create alert rules using PromQL.
Go to Alerts, open Manage rules, and create a rule.
Search for Elasticsearch query and select it.
Choose ES|QL as the query type.</p>
<p>Write your <code>PROMQL</code> query, but assign the metric to a variable so you can use it in a <code>WHERE</code> clause for the alert condition:</p>
<pre><code>PROMQL metric_value=(sum by (agent.id) (rate(docker.network.inbound.bytes)))
| WHERE metric_value &gt;= 500
</code></pre>
<p>Select <code>@timestamp</code> for the time field and continue defining the rest of the rule configuration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50854477d5962a3c/6a7f1a15ea068d9643f0a2bf/alert-rule-promql.png" alt="Alert rule: Elasticsearch query with a PROMQL condition" /></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.</li>
<li>Write your query: in the ES|QL editor in Kibana, run your PromQL via <code>PROMQL</code>.
You can also go to Dashboards, add a panel, choose ES|QL, and write the query there.</li>
<li>If you are writing from scratch and need to find metric names, run <code>TS metrics-*</code> in Discover (see "Find your streams and metric names" above).</li>
<li>Check the results and adapt the query if needed.</li>
</ol>
<p>PromQL support in Elasticsearch and Kibana will continue to evolve.
Follow the Observability Labs feed for follow-up posts as coverage and ergonomics improve.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana</link>
    <guid isPermaLink="false">promql-queries-run-in-kibana</guid>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Miguel Sánchez Gómez,Vinay Chandrasekhar,Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt486d62547cd575db/6a7f1a1842a117335495c2f1/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Ship Prometheus Metrics to Elasticsearch with Remote Write]]></title>
    <description><![CDATA[Elasticsearch natively supports Prometheus Remote Write. Add a single remote_write block to your Prometheus config and use Elasticsearch as Prometheus-compatible long-term storage.]]></description>
    <content:encoded><![CDATA[<p>Prometheus has a well-defined protocol for shipping metrics to external storage: <a href="https://prometheus.io/docs/specs/prw/remote_write_spec/">Remote Write</a>.
Elasticsearch now implements this protocol natively, so you can add it as a <code>remote_write</code> destination with a single config block.</p>
<p>This lets you bring your Prometheus metrics into the same cluster which can also store logs, traces, and other data.
One storage backend, one set of access controls, one place to query.</p>
<h2 id="whystoreprometheusmetricsinelasticsearch">Why store Prometheus metrics in Elasticsearch?</h2>
<p>Prometheus local storage is designed for short retention, typically 15 to 30 days.
For anything beyond that, you need a remote storage backend.</p>
<p>Elasticsearch's time series data streams (TSDS) are built for highly efficient long term metrics storage: automatic rollover, time-based partitioning, compression via index sorting, and downsampling to reduce storage costs as data ages.
Your Prometheus scrape configs stay the same.</p>
<p>Recent Elasticsearch releases have significantly reduced the storage footprint for metrics.
A dedicated post with the numbers is coming soon.</p>
<p>On the query side, ES|QL embraces PromQL: a built-in <code>PROMQL</code> function lets your existing queries run unchanged, while the rest of ES|QL is available when you want joins, aggregations, or transformations that span multiple datasets.</p>
<p>And because metrics land in the same store as your logs, traces, and profiling data, correlating signals across types becomes a single query rather than a cross-system investigation.</p>
<h2 id="howitworks">How it works</h2>
<p>For a detailed look at what happens inside Elasticsearch when a Remote Write request arrives — protobuf parsing, metric type inference, TSDS mapping, and data stream routing — see <a href="https://www.elastic.co/blog/prometheus-remote-write-elasticsearch-architecture">How Prometheus Remote Write Ingestion Works in Elasticsearch</a>.</p>
<p>Prometheus sends metrics to Elasticsearch via the standard Remote Write protocol (v1).
The endpoint accepts protobuf-encoded, snappy-compressed <code>WriteRequest</code> payloads.</p>
<p>Each sample becomes an Elasticsearch document in a pre-defined time series data stream.
Prometheus labels become TSDS dimensions.
The metric value is stored in a typed field under <code>metrics.&lt;metric_name&gt;</code>.</p>
<p>Elasticsearch infers the metric type (counter vs gauge) from naming conventions.
Names ending in <code>_total</code>, <code>_sum</code>, <code>_count</code>, or <code>_bucket</code> are treated as counters.
Everything else is treated as a gauge.</p>
<h2 id="settingitup">Setting it up</h2>
<h3 id="step1getanelasticsearchendpoint">Step 1: Get an Elasticsearch endpoint</h3>
<p>You need an Elasticsearch cluster with the Prometheus endpoints enabled.
The simplest option is Elastic Cloud Serverless, where this works out of the box.</p>
<p>For serverless: sign in to <a href="https://cloud.elastic.co">cloud.elastic.co</a>, create an Observability project, and copy the Elasticsearch endpoint from the project settings page.
The endpoint looks like <code>https://&lt;project-id&gt;.es.&lt;region&gt;.&lt;provider&gt;.elastic.cloud</code>.</p>
<h3 id="step2createanapikey">Step 2: Create an API key</h3>
<p>Create an API key scoped to writing metrics data streams only.
In your Elastic Cloud Serverless project, go to <strong>Admin and settings</strong> (the gear icon at the bottom left of the side nav), then <strong>API keys</strong>.</p>
<p>Use the following role descriptor in the <strong>Control security privileges</strong> section:</p>
<pre><code>{
  "ingest": {
    "indices": [
      {
        "names": ["metrics-*"],
        "privileges": ["auto_configure", "create_doc"]
      }
    ]
  }
}
</code></pre>
<p>Copy the key value before closing the dialog.
You will not be able to retrieve it again.</p>
<h3 id="step3configureprometheus">Step 3: Configure Prometheus</h3>
<p>Add the following <code>remote_write</code> block to your <code>prometheus.yml</code>:</p>
<pre><code>remote_write:
  - url: "https://YOUR_ES_ENDPOINT/_prometheus/api/v1/write"
    authorization:
      type: ApiKey
      credentials: YOUR_API_KEY
</code></pre>
<p>That's it.
Prometheus will start shipping metrics to Elasticsearch on the next scrape interval.</p>
<p>If you use <a href="https://grafana.com/docs/alloy/latest/">Grafana Alloy</a> instead of Prometheus, the equivalent configuration is:</p>
<pre><code>prometheus.remote_write "elasticsearch" {
  endpoint {
    url = "https://YOUR_ES_ENDPOINT/_prometheus/api/v1/write"
    headers = {"Authorization" = "ApiKey YOUR_API_KEY"}
  }
}
</code></pre>
<h2 id="routingmetricstoseparatedatastreams">Routing metrics to separate data streams</h2>
<p>By default, all metrics land in <code>metrics-generic.prometheus-default</code>.
You can route metrics from different environments or teams into separate data streams using the dataset and namespace path segments in the URL.</p>
<p>The three URL patterns are:</p>
<ul>
<li><code>/_prometheus/api/v1/write</code> routes to <code>metrics-generic.prometheus-default</code></li>
<li><code>/_prometheus/metrics/{dataset}/api/v1/write</code> routes to <code>metrics-{dataset}.prometheus-default</code></li>
<li><code>/_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code> routes to <code>metrics-{dataset}.prometheus-{namespace}</code></li>
</ul>
<p>For example, using <code>/_prometheus/metrics/infrastructure/production/api/v1/write</code> routes data to <code>metrics-infrastructure.prometheus-production</code>.</p>
<p>This is useful for separating production from staging metrics, or giving different teams their own data streams with independent lifecycle policies.</p>
<h2 id="whatgetsstored">What gets stored</h2>
<p>Here is what a sample document looks like in Elasticsearch:</p>
<pre><code>{
  "@timestamp": "2026-04-02T10:30:00.000Z",
  "data_stream": {
    "type": "metrics",
    "dataset": "generic.prometheus",
    "namespace": "default"
  },
  "labels": {
    "__name__": "prometheus_http_requests_total",
    "handler": "/api/v1/query",
    "code": "200",
    "instance": "localhost:9090",
    "job": "prometheus"
  },
  "metrics": {
    "prometheus_http_requests_total": 42
  }
}
</code></pre>
<p>Labels map to keyword fields that serve as TSDS <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-dimension">dimensions</a>.
The metric value is stored under <code>metrics.&lt;metric_name&gt;</code> with the inferred <code>time_series_metric</code> type (counter or gauge).</p>
<p>Elasticsearch installs a built-in index template matching <code>metrics-*.prometheus-*</code> that configures TSDS mode, <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/passthrough">passthrough</a> dimension container objects, and a 10,000 field limit.
The <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/mapping-limit">field limit</a> is configurable via a custom component template (see the custom metric type inference section below for how to use one).
You do not need to create any templates or mappings yourself.</p>
<h2 id="custommetrictypeinference">Custom metric type inference</h2>
<p>Metric type inference is based on naming conventions.
Metrics that don't follow Prometheus naming best practices may be classified incorrectly.
You can override the defaults by creating a <code>metrics-prometheus@custom</code> component template with your own dynamic templates.
For example, to mark all <code>*_counter</code> metrics as counters:</p>
<pre><code>{
  "template": {
    "mappings": {
      "dynamic_templates": [
        {
          "counter": {
            "path_match": "metrics.*_counter",
            "mapping": {
              "type": "double",
              "time_series_metric": "counter"
            }
          }
        }
      ]
    }
  }
}
</code></pre>
<p>Custom rules are merged with the built-in patterns, so the defaults still apply for metrics you don't override.</p>
<h2 id="currentlimitations">Current limitations</h2>
<p>Only Remote Write v1 is supported.
v2, which brings native histograms and exemplars, is planned.</p>
<p>Staleness markers (special NaN values Prometheus uses to signal a series has disappeared) are not yet stored or respected in queries.</p>
<p>Non-finite values (NaN, Infinity) are silently dropped.</p>
<h2 id="getstarted">Get started</h2>
<p>The Prometheus Remote Write endpoint is available now on <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elasticsearch Serverless</a> with no configuration needed.
To get started with a local cluster, <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">start-local</a> gets you a single-node cluster in minutes.</p>
<p>Once metrics are flowing, you can query them with ES|QL using the built-in <code>PROMQL</code> function for PromQL compatibility, or write native ES|QL queries to join metrics with logs and traces in the same store.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch</link>
    <guid isPermaLink="false">prometheus-remote-write-elasticsearch</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17c564dfb7ca5dd6/6a7f19d65967e538655dd6b1/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How Prometheus Remote Write Ingestion Works in Elasticsearch]]></title>
    <description><![CDATA[A look under the hood at Elasticsearch's Prometheus Remote Write implementation: protobuf parsing, metric type inference, TSDS mapping, and data stream routing.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch recently added native support for the Prometheus Remote Write protocol.
You can point Prometheus (or Grafana Alloy) at an Elasticsearch endpoint and ship metrics without any adapter in between.</p>
<p>This post looks at what happens inside Elasticsearch when a Remote Write request arrives.</p>
<p>If you want to understand the implementation, evaluate how Elasticsearch compares to other Prometheus-compatible backends, or contribute, this is the post for you.
A companion post, <a href="https://www.elastic.co/blog/prometheus-remote-write-elasticsearch">Ship Prometheus Metrics to Elasticsearch with Remote Write</a>, covers the setup and configuration side.</p>
<h2 id="requestlifecyclefromhttptoindexeddocuments">Request lifecycle: from HTTP to indexed documents</h2>
<p>A quick note on the Prometheus data model before we dive in: Prometheus stores all metric values as 64-bit floats and treats the metric name as just another label (<code>__name__</code>).
The storage engine itself is agnostic of whether a value is a counter or a gauge.
Keep this in mind as we walk through how Elasticsearch maps these concepts.</p>
<p>Here is the full path of a Remote Write request through Elasticsearch:</p>
<ol>
<li><strong>HTTP layer</strong> — The endpoint receives a compressed protobuf payload, checks indexing pressure, decompresses with Snappy, and parses the protobuf <code>WriteRequest</code>.</li>
<li><strong>Document construction</strong> — Each sample in each time series becomes an Elasticsearch document with <code>@timestamp</code>, <code>labels.*</code>, and <code>metrics.*</code> fields.</li>
<li><strong>Bulk indexing</strong> — All documents from a single request are written to the target data stream via a single bulk call.</li>
</ol>
<p>The sections below walk through each stage in detail.</p>
<h3 id="httplayer">HTTP layer</h3>
<p>The endpoint accepts <code>application/x-protobuf</code> POST requests.
The incoming request body is tracked against the same <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/pressure">indexing pressure limits</a> that protect the bulk indexing API.
If the cluster is already under heavy indexing load, the request gets rejected with a 429 before any parsing happens.</p>
<p>Prometheus compresses Remote Write payloads with Snappy.
Elasticsearch decompresses the body in a streaming fashion without materializing it into a single contiguous allocation, and validates the declared uncompressed size against a configurable maximum to guard against decompression bombs.</p>
<p>The decompressed body is then deserialized as a protobuf <code>WriteRequest</code>.
Each <code>WriteRequest</code> contains a list of <code>TimeSeries</code> entries, and each <code>TimeSeries</code> contains a set of labels (key-value pairs) and a list of samples (timestamp + float64 value).</p>
<h3 id="documentconstruction">Document construction</h3>
<p>For each sample in each time series, Elasticsearch builds an index request.
Here is what a single document looks like:</p>
<pre><code>{
  "@timestamp": "2026-04-01T12:00:00.000Z",
  "data_stream": {
    "type": "metrics",
    "dataset": "generic.prometheus",
    "namespace": "default"
  },
  "labels": {
    "__name__": "http_requests_total",
    "job": "prometheus",
    "instance": "localhost:9090",
    "method": "GET",
    "status": "200"
  },
  "metrics": {
    "http_requests_total": 1027.0
  }
}
</code></pre>
<p>All labels from the Prometheus time series (including <code>__name__</code>) end up in the <code>labels.*</code> fields.
The metric value goes into <code>metrics.&lt;metric_name&gt;</code>, where <code>&lt;metric_name&gt;</code> is the value of the <code>__name__</code> label.</p>
<p>Time series without a <code>__name__</code> label are dropped entirely, and the samples are counted as failures.
Non-finite values (NaN, Infinity, negative Infinity) are silently skipped.
This includes Prometheus staleness markers, which use a special NaN bit pattern (<code>0x7ff0000000000002</code>) to signal that a series has disappeared.</p>
<h3 id="onesampleonedocument">One sample, one document</h3>
<p>You might wonder whether storing each individual sample as its own document creates significant storage overhead, especially for labels.
A common pattern to reduce that overhead was to group all metrics sharing the same labels and timestamp into a single document.</p>
<p>With recent TSDB improvements, that optimization is no longer necessary.
Elasticsearch has trimmed the per-document storage overhead to the point where there is negligible difference between packing many metrics in a single document and writing each sample separately.
A dedicated post covering these TSDB storage improvements in detail is coming soon.</p>
<h3 id="bulkindexing">Bulk indexing</h3>
<p>All documents from a single Remote Write request are sent to Elasticsearch via a single bulk request.
Each document targets the data stream <code>metrics-{dataset}.prometheus-{namespace}</code> and is indexed as an append-only create operation.</p>
<h2 id="metrictypeinference">Metric type inference</h2>
<p>Remote Write v1 does not reliably transmit metric types alongside samples.
Prometheus sends metadata (type, help text, unit) in separate requests roughly once per minute, and those requests may land on a different node than the samples.
Buffering samples until metadata arrives is not practical in a distributed system, so Elasticsearch infers the type from naming conventions instead.</p>
<p>Metric names ending in <code>_total</code>, <code>_sum</code>, <code>_count</code>, or <code>_bucket</code> are mapped as counters.
Everything else defaults to gauge.
This is a well-established convention that other Prometheus-compatible backends use as well.</p>
<pre><code>http_requests_total             → counter
request_duration_seconds_sum    → counter
request_duration_seconds_count  → counter
request_duration_seconds_bucket → counter
process_resident_memory_bytes   → gauge
go_goroutines                   → gauge
</code></pre>
<p>The heuristic can be wrong.
A metric like <code>temperature_total</code> (if someone named a gauge that way) would be misclassified as a counter.
The main consequence today is that some ES|QL functions like <code>rate()</code> require the metric type to be a counter and will reject a misclassified gauge.
For PromQL, we plan to lift this restriction so that <code>rate()</code> works regardless of the declared type, which will make incorrect inference less consequential.</p>
<p>You can override the inference by creating a <code>metrics-prometheus@custom</code> component template with custom dynamic templates.
For example, to treat all <code>*_counter</code> fields as counters:</p>
<pre><code>PUT /_component_template/metrics-prometheus@custom
{
  "template": {
    "mappings": {
      "dynamic_templates": [
        {
          "counter": {
            "path_match": "metrics.*_counter",
            "mapping": {
              "type": "double",
              "time_series_metric": "counter"
            }
          }
        }
      ]
    }
  }
}
</code></pre>
<p>Custom dynamic templates are merged with the built-in ones, so the default naming-convention rules still apply for metrics you don't explicitly override.</p>
<h2 id="theindextemplate">The index template</h2>
<p>Elasticsearch installs a built-in index template that matches <code>metrics-*.prometheus-*</code>.
This template is what makes field type inference work without manual mapping configuration.</p>
<p><strong>TSDS mode</strong> is enabled, which gives you time-based partitioning, optimized storage, <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-dimension">deduplication</a>, and the ability to downsample data as it ages.</p>
<p><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/passthrough">Passthrough</a> object fields</strong> are used for both the <code>labels</code> and <code>metrics</code> namespaces.
This serves three purposes:</p>
<ol>
<li><p><strong>Namespace isolation</strong>: Labels and metrics live in separate object namespaces (<code>labels.*</code> and <code>metrics.*</code>), so a label named <code>status</code> and a metric named <code>status</code> cannot conflict with each other.</p></li>
<li><p><strong>Dimension identification</strong>: The <code>labels</code> passthrough object is configured with <code>time_series_dimension: true</code>, which means every field under <code>labels.*</code> is automatically treated as a TSDS dimension.
When Prometheus sends a time series with a label you have never seen before, it becomes a dimension without any explicit field mapping.</p></li>
<li><p><strong>Transparent queries</strong>: You don't need to write the <code>labels.</code> or <code>metrics.</code> prefix in ES|QL or PromQL.
A query can reference <code>job</code> instead of <code>labels.job</code>, or <code>http_requests_total</code> instead of <code>metrics.http_requests_total</code>.
The passthrough mapping handles the resolution.</p></li>
</ol>
<p><strong>Dynamic inference for metrics</strong> applies the naming-convention heuristics described above.
When a new metric name appears for the first time, its field mapping is created automatically under <code>metrics.*</code> with the correct <code>time_series_metric</code> annotation.</p>
<p><strong>Failure store</strong> is enabled.
Documents that fail indexing (for example, due to a mapping conflict where the same metric name appears with incompatible types) are routed to a separate failure store instead of being dropped silently.</p>
<h2 id="datastreamrouting">Data stream routing</h2>
<p>The three URL patterns map directly to data stream names:</p>
<p>| URL pattern | 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>This lets you separate metrics from different Prometheus instances or environments into different data streams.
That separation is useful for a few reasons.</p>
<p><strong>Lifecycle isolation</strong>: you can apply different retention policies per data stream.
Production metrics might be kept for 90 days, while dev metrics might expire after 7 days.</p>
<p><strong>Access control</strong>: you can scope API keys to specific data streams.
A team's Prometheus instance writes to <code>metrics-teamA.prometheus-prod</code>, and their API key only has access to that stream.</p>
<p><strong>Query performance</strong>: PromQL queries and Grafana dashboards can be scoped to a specific index pattern, avoiding scans of unrelated data.</p>
<h2 id="errorhandlingandtheremotewritespec">Error handling and the Remote Write spec</h2>
<p>The Remote Write spec defines two response classes: retryable (5xx, 429) and non-retryable (4xx).
Prometheus uses this distinction to decide whether to retry or drop a failed request.</p>
<p>Elasticsearch returns 429 (Too Many Requests) if any sample in the bulk request was rejected due to indexing pressure.
This signals Prometheus to back off and retry with exponential backoff.</p>
<p>For partial failures (some samples indexed, others rejected), the response includes a summary.
It reports how many samples failed, grouped by target index and status code, along with a sample error message from each group.</p>
<p>Time series without a <code>__name__</code> label result in a 400 error for those samples.
Non-finite values (NaN, Infinity) are silently dropped: Prometheus receives a success response and will not retry.</p>
<p>NaN appears most commonly for summary quantiles when no observations have been recorded (for example, a p99 latency metric before any requests arrive) and for staleness markers.
The practical impact of dropping these is limited today: for most queries, a missing sample behaves similarly to a NaN one, since PromQL's lookback window fills the gap with the last known value either way.
The more significant gap is staleness markers, which are covered below.</p>
<h2 id="whatsnextremotewritev2andbeyond">What's next: Remote Write v2 and beyond</h2>
<p>Remote Write v2 is still experimental, which is why the current implementation starts with v1.
But v2 addresses several of v1's shortcomings.</p>
<p><strong>Metadata alongside samples</strong>: v2 sends metric type, unit, and description with each time series in the same request.
This eliminates the need for naming-convention heuristics entirely.</p>
<p><strong>Native histograms</strong>: v2 supports Prometheus native histograms, which map naturally to Elasticsearch's <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram"><code>exponential_histogram</code></a> field type.
Classic histograms (one counter per bucket boundary) are verbose and lose precision at query time.
Native histograms are more compact and more accurate.</p>
<p><strong>Dictionary encoding</strong>: v2 replaces repeated label strings with integer references, reducing payload size significantly for high-cardinality label sets.</p>
<p><strong>Created timestamps</strong>: counters in v2 include a "created" timestamp that marks when the counter was initialized.
This allows backends to detect counter resets more accurately than the current heuristic (value decreased since last sample).</p>
<p>Beyond v2, there are two other items in consideration for future enhancements.</p>
<p><strong>Staleness marker support</strong>: currently, staleness markers (the special NaN that Prometheus writes when a scrape target disappears) are dropped.
Supporting them would allow correct PromQL lookback behavior and avoid the 5-minute "trailing data" artifact where a disappeared series still appears in query results.</p>
<p><strong>Shared metric field</strong>: the current layout creates a separate field for each metric name (<code>metrics.http_requests_total</code>, <code>metrics.go_goroutines</code>, etc.).
This works, but it means the number of field mappings grows with the number of distinct metric names, which is why the field limit is set to 10,000 for Prometheus data streams.
A different approach we're considering is to store the metric name only in the <code>__name__</code> label and write the metric value to a single shared field.
This eliminates the field explosion problem entirely and more closely matches how Prometheus stores data internally.
This direction is part of the broader effort to make Elasticsearch's metrics storage more efficient and more compatible with Prometheus conventions.</p>
<h2 id="availability">Availability</h2>
<p>The Prometheus Remote Write endpoint is available now on <a href="https://cloud.elastic.co/serverless-registration">Elasticsearch Serverless</a> with no additional configuration.</p>
<p>For self-managed clusters, check out <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">start-local</a> to get up and running quickly.</p>
<p>If you run into issues or have feedback, open an issue on the <a href="https://github.com/elastic/elasticsearch">Elasticsearch repository</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture</link>
    <guid isPermaLink="false">prometheus-remote-write-elasticsearch-architecture</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Data Management]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5bbe42711414c88f/6a7f19d2eab5be381320aaec/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Visualizing OpenTelemetry Data in Elastic with OpenTelemetry Content Packages]]></title>
    <description><![CDATA[Learn and explore how OpenTelemetry Content Packages in Elastic provide instant dashboards, alerts, and SLOs for your telemetry data.]]></description>
    <content:encoded><![CDATA[<p>If you've been in the observability space for the last couple of years, you've seen OpenTelemetry go from "promising standard" to the default choice for collecting metrics, logs, and traces. Elastic has been in that journey from early on — which is why we built the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Elastic Distributions of OpenTelemetry (EDOT)</a>: a hardened, production-ready suite of OTel components including the EDOT Collector and language SDKs, tuned for infrastructure and application monitoring without the typical setup overhead.</p>
<p>EDOT is now generally available. The collector, the SDKs, the whole stack — production-ready, enterprise-supported, no asterisks.</p>
<p>But here's the thing: getting your data into Elastic is only half the job. The harder half, in practice, is what happens after. Someone still has to build the dashboards, write the alert rules, and figure out which SLOs are worth tracking — before any of it is useful.</p>
<p>That gap is what OpenTelemetry Content Packages are designed to close.</p>
<hr />
<h2 id="whatareopentelemetrycontentpackages">What Are OpenTelemetry Content Packages?</h2>
<p>Elastic's traditional Beats-based integrations always bundled data collection and visualizations together — you got curated dashboards and alerts the moment you turned something on. As Elastic moves to an OpenTelemetry-first world, that same philosophy carries over, but the model is cleaner.</p>
<p>OpenTelemetry Content Packs are purely about the observability assets for a given service. No data collection config is bundled in, because in an OTel world, the collector handles that. Each package contains:</p>
<ul>
<li><strong>Dashboards</strong> — curated, pre-built Kibana visualizations tailored to the service being monitored</li>
<li><strong>Alert rules</strong> — pre-configured alerting rules that fire on meaningful thresholds, helping teams minimize Mean Time to Detect (MTTD) and Mean Time to Resolve (MTTR)</li>
<li><strong>SLO templates</strong> — ready-made Service Level Objective definitions you can apply immediately to track reliability targets, error budgets, and burn rates</li>
</ul>
<p>More asset types are planned for future packages as the content pack model continues to evolve.</p>
<hr />
<h2 id="howdoesitwork">How Does It Work?</h2>
<p>The core idea is simple: as soon as data arrives in Elastic, the right dashboards, alert rules, and SLO templates are ready to use. The content package activates based on the incoming data, regardless of how that data was collected.</p>
<p>One of the most powerful aspects of this system is <strong>automatic installation</strong>. When Elastic detects that data for a particular service has started arriving in Elasticsearch, the corresponding content pack is installed automatically — no manual steps, no hunting through the integrations catalog. By the time you open Kibana, your dashboards are already there waiting for you, your alert rules are ready to be enabled, and your SLO templates are pre-loaded.</p>
<p>To get the data flowing in the first place, we need to configure the collector — a YAML file that defines the building blocks of your telemetry pipeline:</p>
<ul>
<li><strong>Receivers</strong> — define what data to collect and from where. Each service has its own receiver (for example, the MySQL receiver scrapes metrics directly from the database).</li>
<li><strong>Exporters</strong> — define where the collected data is sent. In our case, we use the Elasticsearch exporter, which ships the telemetry data directly into Elasticsearch in OpenTelemetry native format.</li>
<li><strong>Pipelines</strong> — wire the receivers and exporters together, defining the flow of data through the collector.</li>
</ul>
<p>Once this configuration is in place and the collector is running, data starts flowing into Elasticsearch — and the content pack takes it from there.</p>
<h4 id="datasources">Data Sources</h4>
<p>OpenTelemetry data can reach Elastic through any of the following:</p>
<ul>
<li><strong><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector</a></strong> — the Elastic Distribution of the OpenTelemetry Collector, embedded in or used alongside the Elastic Agent</li>
<li><strong><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">Upstream OTel Collector</a></strong> — the standard community OpenTelemetry Collector (Contrib or custom builds)</li>
<li><strong><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder">EDOT Cloud Forwarder (ECF)</a></strong> — a serverless OTel Collector that collects telemetry from AWS, GCP, and Azure (VPC Flow Logs, CloudTrail, CloudWatch, and more) and forwards it directly to Elastic Observability, with no infrastructure to manage</li>
</ul>
<p>The content pack doesn't care how the data arrived — only that it's there.</p>
<hr />
<h2 id="seeingitinpracticemysqlmonitoring">Seeing It in Practice: MySQL Monitoring</h2>
<p>Take a team running MySQL who wants to track query throughput, connection counts, buffer pool utilization, and slow query rates — and get alerted before small problems turn into 2am incidents. Historically, that means hours of dashboard building, custom alert queries, and a lot of guesswork about which metrics actually matter.</p>
<p>With the <strong><a href="https://www.elastic.co/docs/reference/integrations/mysql_otel">MySQL OpenTelemetry Assets Package</a></strong>, that work is already done. Here's how the whole thing comes together.</p>
<h3 id="step1getthedatain">Step 1: Get the Data In</h3>
<p>The data pipeline is driven by a collector configuration that defines receivers (where to scrape data from), processors (how to enrich or transform it), and exporters (where to send it — in this case, Elasticsearch).</p>
<p>Regardless of whether you use the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector</a> or the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">Upstream OTel Collector</a>, the fundamental configuration structure is the same. The configuration below uses separate receivers for the primary and replica instances, because replication metrics are only available on replicas. Replace the placeholders with your actual endpoints, credentials, and Elasticsearch details.</p>
<pre><code>receivers:
  mysql/primary:
    endpoint: &lt;MYSQL_PRIMARY_ENDPOINT&gt;
    username: &lt;MYSQL_USER&gt;
    password: &lt;MYSQL_PASSWORD&gt;
    collection_interval: 10s
    statement_events:
      digest_text_limit: 120
      limit: 250
    query_sample_collection:
      max_rows_per_query: 100
    events:
      db.server.query_sample:
        enabled: true
      db.server.top_query:
        enabled: true
    metrics:
      mysql.client.network.io:
        enabled: true
      mysql.connection.errors:
        enabled: true
      mysql.max_used_connections:
        enabled: true
      mysql.query.client.count:
        enabled: true
      mysql.query.count:
        enabled: true
      mysql.query.slow.count:
        enabled: true
      mysql.table.rows:
        enabled: true
      mysql.table.size:
        enabled: true

processors:
  resourcedetection:
    detectors: [system, env]

exporters:
  elasticsearch/otel:
    endpoint: &lt;ES_ENDPOINT&gt;
    api_key: &lt;ES_API_KEY&gt;
    mapping:
      mode: otel

service:
  pipelines:
    metrics:
      receivers: [mysql/primary, mysql/replica]
      processors: [resourcedetection]
      exporters: [elasticsearch/otel]
</code></pre>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/mysqlreceiver/README.md#mysql-receiver">MySQL receiver</a> scrapes metrics and events from the database at the configured interval and emits them as OpenTelemetry metrics. These flow through the pipeline and land in Elasticsearch, ready to be visualized.</p>
<h3 id="step2openkibanaeverythingsalreadythere">Step 2: Open Kibana — Everything's Already There</h3>
<h4 id="dashboards">Dashboards</h4>
<p>As soon as the MySQL metrics and events arrive in Elasticsearch, the <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel">MySQL OpenTelemetry Assets Package</a> is automatically installed in the background. By the time you navigate to Kibana, the <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#screenshots">dashboards</a> are already populated and waiting.</p>
<p>Users immediately get visibility into:</p>
<ul>
<li>Active and max connections</li>
<li>Query throughput — statements executed per second</li>
<li>InnoDB buffer pool hit rate and memory usage</li>
<li>Slow query count and trends</li>
<li>Table lock waits and contention</li>
<li>Bytes sent and received over time</li>
<li>Replication lag (for replicated setups)</li>
</ul>
<p>No manual field mapping. No dashboard building from scratch. Just data in, insights out.</p>
<p>Below are some screenshots of the MySQL OpenTelemetry dashboard in Kibana, showing the out-of-the-box visualizations that are automatically available as soon as your data starts flowing in.</p>
<p>Overview Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5152c95269216cdf/6a7f1c149090b0601984ee53/overview.png" alt="" /></p>
<p>Queries Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b009de3d2d1df00/6a7f1c182f00b23996efef4f/queries.png" alt="" /></p>
<p>Availability Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4c4ba1782931fe/6a7f1c1b448e4e42635c0b89/availability.png" alt="" /></p>
<h4 id="alertrulesreadytoenable">Alert Rules, Ready to Enable</h4>
<p>The package includes six pre-built <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#alert-rules">alert rules</a> — covering high connection error rates, slow query spikes, thread saturation, replication lag, buffer pool dirty page ratio, and row lock contention — each with recommended thresholds and severity levels. These are available immediately on install and can be enabled, tuned, and extended directly in Kibana without any custom query authoring. Below is an example of one of the alerts.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3779d7ac5a0a0128/6a7f1c1e05b7b514c318bd65/alert1.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdaf98fbb471eb1f1/6a7f1c215967e561495dd6ef/alert2.png" alt="" /></p>
<h4 id="slotemplatespreloaded">SLO Templates, Pre-Loaded</h4>
<p>Four <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#slo-templates">SLO templates</a> are included out of the box, tracking replication lag, connection exhaustion errors, slow query rate, and connected thread count — each with a pre-configured target and 30-day rolling window. Teams can adopt them as-is or tune the thresholds to match their own reliability requirements.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd307a19b82ea64b8/6a7f1c2505b7b5756918bd6f/slo.png" alt="" /></p>
<hr />
<h2 id="whatsavailabletoday">What's Available Today</h2>
<p>The MySQL OpenTelemetry Assets Package is just one example from a growing library of OpenTelemetry Content Packages that Elastic has already built out. Content packs are available for a range of services — and we have also started extending this to the cloud, with initial support for Cloud Service Provider integrations that use the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder">EDOT Cloud Forwarder (ECF)</a> to bring AWS, GCP, and Azure telemetry into Elastic with ready-made dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt783f630fca5a601e/6a7f1c28c2e914bbab01701c/contentpacks.png" alt="" /></p>
<p>The same pattern holds across all of them — data in, and a complete observability package (dashboards, alert rules, SLO templates) instantly ready — whether you're monitoring a self-managed database or cloud-native services from your preferred cloud service provider.</p>
<h2 id="wherethisisgoing">Where This Is Going</h2>
<p>The next step worth watching is <strong>OTel Integration Packages</strong>, which will let you push collector configurations directly from the Kibana UI — making the entire setup experience point-and-click, from data collection through to visualization, with no YAML editing required.</p>
<hr />
<h2 id="getstarted">Get Started</h2>
<p>Ready to try it? Start with the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector documentation</a> and explore the growing library of OpenTelemetry content packages in Kibana's Integrations page.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/visualizing-opentelemetry-data-elastic-content-packages</link>
    <guid isPermaLink="false">visualizing-opentelemetry-data-elastic-content-packages</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Ishleen Kaur]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte829026a132ec529/6a7f1c2bb43770a7fb4d7142/otelcp.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 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[Network monitoring with Elastic: Unifying network observability]]></title>
    <description><![CDATA[Learn how to unify network monitoring using Elastic observability and AI. We'll showcase how to correlate network data, identify root causes and fix issues.]]></description>
    <content:encoded><![CDATA[<h2 id="introductionthenetworkmonitoringfragmentationproblem">Introduction: The Network Monitoring Fragmentation Problem</h2>
<p>In five years working with Enterprise accounts at Elastic, I have heard the same challenge again and again:</p>
<p><strong>"We have several network monitoring tools, and we would love to correlate all of them into one platform."</strong></p>
<p>For many organizations, the barrier to true correlation isn't a lack of data, but where that data lives. Frequently, we see SNMP metrics, flow data, and logs isolated in purpose-built silos or dashboards. Without a unified data store and a proper correlation engine, piecing together the full narrative — from a topology change to a performance degradation — becomes a manual, time-consuming puzzle.</p>
<p>When an incident happens, engineers become <strong>human correlation engines</strong> — manually jumping between systems, copying timestamps, cross-referencing device names, and trying to piece together what actually happened. A simple question like "Did this interface failure impact application performance?" requires querying multiple tools and mentally correlating the results.</p>
<p>The real cost isn't the tool licenses — it's the time lost during critical incidents.</p>
<p>This lab is my answer to a fundamental question: <strong>Can Elastic become the unified foundation that actually correlates network data?</strong></p>
<p>More importantly, it demonstrates that Elastic is fully ready for network operations — capable of ingesting diverse telemetry and using AI to correlate relationships, identify root causes, and resolve issues in seconds instead of hours.</p>
<h2 id="theproblemnetworkobservabilityisbroken">The Problem: Network Observability is Broken</h2>
<p>Let me paint a typical scenario I encounter with enterprise network teams:</p>
<p><strong>The Fragmented Reality:</strong></p>
<ul>
<li>No single source of truth</li>
<li>Manual correlation during incidents (15-30 minutes per event)</li>
<li>Fragmented teams (network vs. platform engineers)</li>
<li>Limited automation capabilities</li>
<li>No AI-powered analysis</li>
</ul>
<p><strong>When a link goes down at 2 AM:</strong></p>
<ul>
<li>Notice the alert - 2 minutes</li>
<li>Log into monitoring tool to see the metric - 3 minutes</li>
<li>Switch to traffic analyzer to check impact - 5 minutes</li>
<li>Open log management to search for related messages - 10 minutes</li>
<li>Manually correlate timestamps across systems - 8 minutes</li>
<li>Create a ticket and copy context from multiple tools - 8 minutes</li>
</ul>
<p><strong>Time to initial diagnosis: 36 minutes</strong></p>
<p>This workflow is expensive, error-prone, and doesn't scale.</p>
<h2 id="thevisionelasticasaunifiednetworkobservabilityplatform">The Vision: Elastic as a Unified Network Observability Platform</h2>
<p>What if you could:</p>
<ul>
<li>Collect SNMP metrics, NetFlow, traps, and topology data in <strong>one platform</strong></li>
<li>Correlate network events with application performance <strong>automatically</strong></li>
<li>Generate executive dashboards without separate BI tools</li>
<li>Use <strong>AI to analyze incidents in seconds</strong>, not hours</li>
<li>Trigger alerting from network events</li>
</ul>
<p>This is what this lab aims to demonstrate.</p>
<h2 id="whatibuiltaproductiongradenetworksimulation">What I Built: A Production-Grade Network Simulation</h2>
<p>To demonstrate how Elastic unifies network data, I needed a realistic environment that generates real-world telemetry. Enter <strong>Containerlab</strong>  —  a Docker-based solution that enables us to create a network simulation framework.</p>
<h3 id="labarchitecture">Lab Architecture</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2d5eece08d2850c/6a7f0e7796b5a6bf6087b4eb/lab-topology.jpg" alt="Lab Topology" /></p>
<p>I simulated a Service Provider core network with:</p>
<ul>
<li><strong>7 FRR routers</strong> forming an OSPF Area 0 mesh</li>
<li><strong>2 Ubuntu hosts</strong> for additional use cases</li>
<li><strong>2 Layer 2 switches</strong> for access layer segmentation</li>
<li><strong>3 telemetry collectors</strong> feeding Elastic Cloud</li>
</ul>
<p><strong>Total containers:</strong> 14</p>
<p><strong>Deployment time:</strong> 12-15 minutes (fully automated)</p>
<p><strong>Full deployment instructions and topology details are available in the <a href="https://github.com/DeBaker1974/Containerlab-OSPF">GitHub repository README</a>.</strong></p>
<h2 id="thethreetelemetrypipelinesprovingmultisourcecorrelation">The Three Telemetry Pipelines: Proving Multi-Source Correlation</h2>
<p>What makes this lab production-ready is its <strong>hybrid observability approach</strong> — proving that Elastic can unify disparate network data sources.</p>
<p>| Pipeline | Data Type | Collection Method | Collector | Use Case |
| :---- | :---- | :---- | :---- | :---- |
| <strong>SNMP Metrics</strong> | Interface stats, system health, LLDP topology | Active polling  | OTEL Collector | Capacity planning, trend analysis |
| <strong>NetFlow</strong> | Traffic flows | Push-based export | Elastic Agent | Top talkers, security investigation |
| <strong>SNMP Traps</strong> | Interface up/down events | Event-driven | Logstash | Real-time incident detection |</p>
<p>This unified architecture proves Elastic can replace multiple specialized network monitoring tools with a single platform.</p>
<h2 id="thepowerofcorrelationoneplatformonequery">The Power of Correlation: One Platform, One Query</h2>
<p>When a network incident occurs, you need to answer questions like:</p>
<ul>
<li>Which interface failed? <em>(SNMP metrics)</em></li>
<li>What traffic was affected? <em>(NetFlow)</em></li>
<li>What was the sequence of events? <em>(SNMP traps)</em></li>
<li>Which devices are downstream? <em>(LLDP topology)</em></li>
</ul>
<p><strong>The Problem:</strong> modern tools offer separate modules glued together, forcing users to navigate different spaces for different sets of data.</p>
<p><strong>The Reality:</strong> You still have to pivot. You see a spike in the Metrics module, but to see why, you have to open the Logs module and manually align the time picker. The data lives in different tables or backends, making true correlation impossible without human intervention.</p>
<p><strong>The Elastic Difference:</strong> One Store, One Language, One AI</p>
<p>Elastic makes it simple. Whether it's an SNMP counter (metric), a NetFlow record (flow), or a Syslog message (log), it is all stored in a unified datastore powered by the Elasticsearch engine. This allows users to easily search across multiple datasets in a single query.</p>
<pre><code>FROM logs-*
| WHERE host.name == "csr23" AND interface.name == "eth1"
</code></pre>
<p><strong>Time required: 3 seconds</strong></p>
<p>Furthermore, as you will see later, the exact location of the data becomes agnostic to the user when leveraging the AI Assistant.</p>
<h2 id="datatransformationfromcrypticoidstoactionableintelligence">Data Transformation: From Cryptic OIDs to Actionable Intelligence</h2>
<p>Raw SNMP traps are notoriously difficult to interpret at a glance. In our current lab setup, the data arrives looking like this:</p>
<pre><code>OID: 1.3.6.1.6.3.1.1.5.3
ifIndex: 2
ifDescr: eth1
</code></pre>
<p>While traditional Network Management Platforms (NMPs) handle OID translation natively, bringing that clarity into Elastic requires a specific configuration.</p>
<p>In this initial lab, we are intentionally working with this raw data to demonstrate how AI assistants can interpret these events even without pre-existing context.</p>
<p>However, the strategy for the next phase of this project is to implement Elasticsearch Ingest Pipelines. This will allow us to map raw OIDs to human-readable names. This step is crucial for bridging the gap between Network tools and Application Observability platforms, allowing network events to be instantly correlated with application errors and infrastructure logs.</p>
<p><strong>The Target State</strong></p>
<p>Once the pipeline is implemented in the next lab, we will transform that raw trap into searchable, meaningful data:</p>
<pre><code>{
  "event.action": "interface-down",
  "host.name": "csr23",
  "interface.name": "eth1",
  "interface.oper_status_text": "Link Down"
}
</code></pre>
<p><strong>The result:</strong></p>
<ul>
<li>Human-readable fields</li>
<li>Searchable dimensions for filtering</li>
<li>Context for automation rules and dashboards</li>
<li>Correlation keys for joining with metrics and flows</li>
</ul>
<p>In our next blog post, we will walk through building the ingest pipeline that performs this transformation — step by step.</p>
<h2 id="intelligentalertingfromnoisetoactionableintelligence">Intelligent Alerting: From Noise to Actionable Intelligence</h2>
<p>Traditional network monitoring relies on simple threshold alerts — "interface down," "high CPU." These alerts flood your inbox but provide <strong>zero context</strong> about root cause, impact, or remediation.</p>
<h3 id="thelabsapproachesqlaiassistant">The Lab's Approach: ES|QL + AI Assistant</h3>
<p><strong>1. Semantic Detection with ES|QL</strong></p>
<p>Instead of generic threshold alerts, the lab uses ES|QL to detect specific event patterns:</p>
<pre><code>FROM logs-snmp.trap-prod
| WHERE snmp.trap_oid == "1.3.6.1.6.3.1.1.5.3"
| KEEP @timestamp, host.name, interface.name, message
</code></pre>
<p><strong>2. Automatic AI-Powered Investigation</strong></p>
<p>When the alert triggers, it invokes the <strong>Observability AI Assistant</strong> with a structured investigation prompt that:</p>
<ul>
<li>Performs immediate triage (which device, which interface, when)</li>
<li>Assesses OSPF impact and traffic rerouting</li>
<li>Correlates with other recent failures</li>
<li>Generates severity assessment and recommended actions</li>
</ul>
<h3 id="thetransformation">The Transformation</h3>
<p>| Traditional Alerting | Intelligent Alerting (Elastic) |
| :---: | :---: |
| <strong>Email: "Interface down on csr23"</strong> | Structured analysis with device context |
| <strong>Manual investigation: 20-30 min</strong> | AI-automated investigation: 90 seconds |
| <strong>Engineer correlates across tools</strong> | Automatic cross-source correlation |
| <strong>No business impact assessment</strong> | Severity + recommended actions included |</p>
<h2 id="acceleratingincidentresponsewiththeelasticaiassistant">Accelerating Incident Response with the Elastic AI Assistant</h2>
<p>This is where the Elastic AI Assistant demonstrates its operational value — moving beyond passive data collection to actively interpret and explain network events in real-time</p>
<p>When an engineer views a trap document in Discover and asks:</p>
<p><strong><em>"Explain this log message"</em></strong></p>
<p>The AI Assistant provides comprehensive analysis including:</p>
<ul>
<li><strong>What happened:</strong> Plain-language explanation of the SNMP trap</li>
<li><strong>Device context:</strong> Router role, interface purpose, network position</li>
<li><strong>Impact analysis:</strong> OSPF neighbor status, traffic rerouting assessment</li>
<li><strong>Root cause possibilities:</strong> Physical layer, link layer, administrative causes</li>
<li><strong>Recommended actions:</strong> Immediate steps, investigation queries, validation checks</li>
<li><strong>Severity assessment:</strong> Business and technical impact rating</li>
</ul>
<h3 id="manualtriagevsaiassistedinvestigation">Manual Triage vs. AI-Assisted Investigation</h3>
<p>| Before | After (Elastic AI) |
| :---- | :---- |
| <strong>Google the OID → 5 min</strong> | Click "Explain this log" → 20 seconds |
| <strong>Open network diagram → 3 min</strong> | Topology context auto-provided |
| <strong>Query multiple tools → 10 min</strong> | Cross-source correlation instant |
| <strong>Assess business impact → 5 min</strong> | Impact analysis auto-generated |
| <strong>Total: ~28 minutes</strong> | <strong>Total: ~20 seconds</strong> |</p>
<h2 id="thevaluepropositiononeplatformonedatamodeloneai">The Value Proposition: One Platform, One Data Model, One AI</h2>
<h3 id="whatthislabdemonstrates">What This Lab Demonstrates</h3>
<p>Elastic provides:</p>
<ul>
<li><strong>One unified platform</strong> for metrics, logs, flows</li>
<li><strong>One data model</strong> (SemConv) for consistent correlation</li>
<li><strong>One search interface</strong> (Kibana) for all network data</li>
<li><strong>One AI assistant</strong> that understands all your network telemetry</li>
<li><strong>AI-powered alerting</strong> with automated investigation</li>
</ul>
<h3 id="businessimpact">Business Impact</h3>
<p><strong>Efficiency Gains:</strong></p>
<ul>
<li><strong>85% reduction in MTTR</strong> (36 min → 5 min for initial diagnosis)</li>
<li><strong>90% reduction</strong> in manual correlation time</li>
<li>Junior engineers gain access to <strong>AI-powered expert analysis</strong></li>
</ul>
<p><strong>Operational Benefits:</strong></p>
<ul>
<li>Network engineers focus on <strong>strategy, not tool-switching</strong></li>
<li><strong>Cross-functional collaboration</strong> in one platform</li>
<li><strong>Reduced tool sprawl</strong> and management overhead</li>
</ul>
<h2 id="lessonslearned">Lessons Learned</h2>
<p>After building this lab, several key insights emerged regarding how network data fits into the broader observability ecosystem:</p>
<p><strong>1. Extending Observability to the Network</strong></p>
<p>Elastic is already the gold standard for high-volume logs and application traces. This lab demonstrates that the same engine seamlessly handles network telemetry without needing a separate, siloed tool.</p>
<ul>
<li>Scale: The same architecture that ingests petabytes of application logs easily handles millions of interface counters.</li>
<li>Structure: Native support for complex nested documents allows for rich SNMP trap data (variable bindings) without flattening or losing context.</li>
<li>Speed: Real-time search applies equally to network events, enabling sub-second troubleshooting.</li>
</ul>
<p><strong>2. OpenTelemetry Semantic Conventions (SemConv) as the Universal Translator</strong></p>
<p>The power isn't just in storing the data, but in standardizing it. By mapping SNMP and NetFlow to the <strong>OpenTelemetry Semantic Conventions (SemConv)</strong>, network data finally speaks the same language as the rest of the stack.</p>
<ul>
<li><strong>Unified Search:</strong> Query across firewall logs, server metrics, and switch telemetry in a single search bar.</li>
<li><strong>Instant Visualization:</strong> Pre-built dashboards work immediately because the field names are standardized.</li>
<li><strong>Cross-Domain Correlation</strong>: Easily correlates a spike in application latency with a specific interface saturation event.</li>
</ul>
<p><strong>3. AI Assistants Thrive on Context</strong></p>
<p>While the AI in this lab was powerful on its own, the experiment highlighted a critical realization: an AI Assistant becomes exponentially more effective when coupled with a specific Knowledge Base.</p>
<p><strong>Context is King:</strong> The AI delivers better root cause analysis when provided with rich metadata, such as device roles and topology maps. Without it, the advice remains generic.</p>
<p><strong>Pro Tip (and What’s Next):</strong></p>
<p>To get organization-specific advice rather than generic suggestions, you need to feed the AI your documentation.</p>
<ul>
<li><strong>The Goal:</strong> Create a Knowledge Base containing device roles, network topology diagrams, and troubleshooting procedures.</li>
<li><strong>The Next Step:</strong> In my next blog post, I will demonstrate exactly how to do this — connecting a Knowledge Base to the AI Assistant to enable fully context-aware troubleshooting.</li>
</ul>
<h2 id="conclusioncompletingtheobservabilitypicture">Conclusion: Completing the Observability Picture</h2>
<p>Elastic is already widely recognized as the standard for Application and Security observability. The goal of this lab wasn't to ask if Elastic can handle networking, but to demonstrate the immense value of bringing network data into that existing ecosystem.</p>
<p>The verdict is clear: Elastic acts as that unified foundation. It effectively breaks down the silo between Network Engineering and the rest of IT.</p>
<p>This isn't just about consolidating dashboards or replacing legacy tools. It is about establishing the Elasticsearch AI Platform as the single source of truth where network telemetry sits right alongside application and infrastructure data.</p>
<p>By treating network data as a first-class citizen in the observability stack, we unlock automated correlation, AI-assisted investigation, and the speed required to resolve incidents before they impact the business. The capabilities are in place, and the foundation is solid — Elastic is ready to unify your network with the rest of your digital business.</p>
<h2 id="readytotryityourself">Ready to Try It Yourself?</h2>
<p>Check out <a href="https://github.com/DeBaker1974/Containerlab-OSPF">github.com/DeBaker1974/Containerlab-OSPF</a></p>
<p>The repository includes:</p>
<ul>
<li>Complete deployment scripts (12-15 minute automated setup)</li>
<li>Pre-configured telemetry pipelines</li>
<li>Kibana dashboards</li>
<li>Alert rules with AI Assistant integration</li>
<li>Detailed README</li>
</ul>
<p><strong>Not ready to build? Try Elastic Serverless:</strong> <a href="https://cloud.elastic.co/registration">Start a free 14-day trial</a> and explore AI-powered observability with your own data.</p>
<p><strong>Special thanks to the Containerlab and FRRouting communities for their incredible open-source tools, and to Sheriff Lawal (CCIE, CISSP), Sr. Manager, Solutions Architecture at Elastic, for mentoring on this project.</strong></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/network-monitoring-with-elastic-unifying-network-observability</link>
    <guid isPermaLink="false">network-monitoring-with-elastic-unifying-network-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Patrick Boulanger]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt644de3218af4a6a1/6a7f0e7a73d9bd4b1129dbc1/article-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 16 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic's metrics analytics gets 5x faster]]></title>
    <description><![CDATA[Explore Elastic's metrics analytics enhancements, including faster ES|QL queries, TSDS updates and OpenTelemetry exponential histogram support.]]></description>
    <content:encoded><![CDATA[<p>In our <a href="https://www.elastic.co/observability-labs/blog/metrics-explore-analyze-with-esql-discover">previous blog in this series</a>, we explored the fundamentals of analyzing metrics using the Elasticsearch Query Language (ES|QL) and the interactive power of Discover. Building on that foundation, we are excited to announce a suite of powerful enhancements to Time Series Data Streams (Elastic’s TSDB) and ES|QL designed to provide even more comprehensive and blazingly faster metrics analytics capabilities!</p>
<p>These latest updates, available in v9.3 and in Serverless, introduce significant performance gains, sophisticated time series functions, and native OpenTelemetry exponential histogram support that directly benefit SREs and Observability practitioners.</p>
<h2 id="queryperformanceandstorageoptimizations">Query Performance and Storage Optimizations</h2>
<p>Speed is paramount when diagnosing incidents. Compared to prior releases, we have achieved a 5x+ improvement in query latency when wildcarding or filtering by dimensions. Additionally, storage efficiency for OpenTelemetry metrics data has improved by approximately 2x, significantly reducing the infrastructure footprint required to retain high-volume observability data. If you’re hungry to learn more about what architectural updates are driving these optimizations, stay tuned… Tech blogs are on their way! </p>
<h2 id="expandedtimeseriesanalyticsinesql">Expanded Time Series Analytics in ES|QL</h2>
<p>The <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts">ESQL TS source command</a>, which targets time series indices and enables <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">time series aggregation functions</a>, has been significantly enhanced to support complex analytics capabilities.</p>
<p>We have expanded the <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-functions-operators">library of time series functions</a> to include essential tools for identifying anomalies and trends.</p>
<ul>
<li><code>PERCENTILE_OVER_TIME</code>, <code>STDDEV_OVER_TIME</code>, <code>VARIANCE_OVER_TIME</code>: Calculate the percentile, standard deviation, or variance of a field over time, which is critical for understanding distribution and variability in service latency or resource usage.</li>
</ul>
<p>Example: Seeing the worst-case latency in 5-minute intervals.</p>
<pre><code>TS metrics*  | STATS MAX(PERCENTILE_OVER_TIME(kafka.consumer.fetch_latency_avg, 99))
&amp;nbsp; BY TBUCKET(5m)
</code></pre>
<ul>
<li><code>DERIV</code>: This command calculates the derivative of a numeric field over time using linear regression, useful for analyzing the rate of change in system metrics.</li>
</ul>
<p>Example: trending gauge values over time.</p>
<pre><code>TS metrics*  | STATS AVG(DERIV(container.memory.available))
&amp;nbsp; BY TBUCKET(1 hour)
</code></pre>
<ul>
<li><code>CLAMP</code>: To handle noisy data or outliers, this function limits sample values to a specified lower and upper bound.</li>
</ul>
<p>Example: handling saturation metrics (like CPU or Memory utilization) where spikes or measurement errors can occasionally report values over 100%, making the rest of the data look like a flat line at the bottom of the chart.\</p>
<pre><code>TS metrics*  | STATS AVG(CLAMP(k8s.pod.memory.node.utilization, 0, 100))
&amp;nbsp; BY k8s.pod.name
</code></pre>
<ul>
<li><code>TRANGE</code>: This new filter function allows you to filter data for a specific time range using the <code>@timestamp</code> attribute, simplifying query syntax for time-bound investigations.</li>
</ul>
<p>Example: Filtering and showing metrics for the last 4 hours.</p>
<pre><code>TS metrics*  | WHERE TRANGE(4h) | STATS AVG(host.cpu.pct)
&amp;nbsp; BY TBUCKET(5m)
</code></pre>
<p><strong>Window Functions</strong> To smoothen results over specific periods, ES|QL now introduces window functions. Most time series aggregation functions now accept an optional second argument that specifies a sliding time window. For example, you can calculate a rate over a 10-minute sliding window while bucketing results by minute.</p>
<p>Example: Calculating the average rate of requests per host for every minute, using values over a sliding window of 5 minutes.</p>
<pre><code>TS metrics*  | STATS AVG(RATE(app.frontend.requests, 5m))
&amp;nbsp; BY TBUCKET(1m)
</code></pre>
<p>Accepted window values are currently limited to multiples of the time bucket interval in the BY clause. Windows that are smaller than the time bucket interval or larger but not a multiple of the time bucket interval will be supported in feature releases. </p>
<h2 id="nativeopentelemetryexponentialhistograms">Native OpenTelemetry Exponential Histograms</h2>
<p>Elastic now provides native support for OpenTelemetry exponential histograms, enabling efficient ingest, querying, and downsampling of high-fidelity distribution data.</p>
<p>We have introduced a new <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram">exponential_histogram</a> field type designed to capture distributions with fixed, exponentially spaced bucket boundaries. Because these fields are primarily intended for aggregations, the histogram is stored as compact doc values and is not indexed, optimizing storage efficiency. These fields are fully supported in ES|QL aggregation functions such as <code>PERCENTILES</code>, <code>AVG</code>, <code>MIN</code>, <code>MAX</code>, and <code>SUM</code>.</p>
<p>You can index documents with exponential histograms automatically through our <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-otlp#configure-histogram-handling">OTLP endpoint</a> or manually. For example, let’s create an index with an exponential histogram field and a keyword field:</p>
<pre><code>PUT my-index-000001
{
&amp;nbsp;&amp;nbsp;"settings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"index": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"mode": "time_series",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"routing_path": ["http.path"],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time_series": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"start_time": "2026-01-21T00:00:00Z",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"end_time": "2026-01-25T00:00:00Z"
&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;"mappings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"properties": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"@timestamp": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "date"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"http.path": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "keyword",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time_series_dimension": true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "exponential_histogram",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time_series_metric": "histogram"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}
</code></pre>
<p>Index a document with a full exponential histogram payload:</p>
<pre><code>POST my-index-000001/_doc
{
&amp;nbsp;&amp;nbsp;"@timestamp": "2026-01-22T21:25:00.000Z",
&amp;nbsp;&amp;nbsp;"http.path": "/foo",
&amp;nbsp;&amp;nbsp;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"scale":3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"sum":73.2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"min":3.12,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"max":7.02,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"positive": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"indices":[13,14,15,16,17,18,19,20,21,22],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"counts":[1,1,2,2,1,2,1,3,1,1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}

POST my-index-000001/_doc
{
&amp;nbsp;&amp;nbsp;"@timestamp": "2026-01-22T21:26:00.000Z",
&amp;nbsp;&amp;nbsp;"http.path": "/bar",
&amp;nbsp;&amp;nbsp;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"scale":3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"sum":45.86,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"min":2.15,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"max":5.1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"positive": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"indices":[8,9,10,11,12,13,14,15,16,17,18],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"counts":[1,1,1,1,1,1,1,2,1,1,2]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}
</code></pre>
<p>And finally, query the time series index using ES|QL and the TS source command:</p>
<pre><code>TS my-index-000001  | STATS MIN(responseTime), MAX(responseTime),
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AVG(responseTime), MEDIAN(responseTime),
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PERCENTILE(responseTime, 90)
&amp;nbsp; BY http.path
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f2a02723540ef2e/6a7f08276693f85d04663d71/exponential_histogram_esql_example.png" alt="Alt text" /></p>
<h2 id="enhanceddownsampling">Enhanced Downsampling</h2>
<p>Downsampling is essential for long-term data retention. We have introduced a new <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/downsampling-concepts#downsampling-methods">"last value" downsampling mode</a>. This method exchanges accuracy for storage efficiency and performance by keeping only the last sample value, providing a lightweight alternative to calculating aggregate metrics.</p>
<p>You can <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/run-downsampling">configure a time series data stream</a> for last value downsampling in a similar way as regular downsampling, just by setting the <code>downsampling_method</code> to <code>last_value</code>. For example, by using a data stream lifecycle:</p>
<pre><code>PUT _data_stream/my-data-stream/_lifecycle
{
&amp;nbsp; "data_retention": "7d",
&amp;nbsp; "downsampling_method": "last_value",
&amp;nbsp; "downsampling": [
 &amp;nbsp; &amp;nbsp; {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; "after": "1m",
 &amp;nbsp; &amp;nbsp; &amp;nbsp; "fixed_interval": "10m"
&amp;nbsp; &amp;nbsp; &amp;nbsp; },
&amp;nbsp; &amp;nbsp; &amp;nbsp; {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "after": "1d",
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "fixed_interval": "1h"
&amp;nbsp; &amp;nbsp; &amp;nbsp; }
 &amp;nbsp; ]
}
</code></pre>
<h2 id="inconclusion">In Conclusion</h2>
<p>These enhancements mark a significant step forward in Elastic's metrics analytics capabilities, delivering 5x+ faster query latency, 2x storage efficiency and specialized commands like <code>DERIV</code>, <code>CLAMP</code>, and <code>PERCENTILE_OVER_TIME</code>. With native support for OpenTelemetry exponential histograms and expanded downsampling options, SREs can now perform richer, more cost-effective analysis on their observability data. This release empowers teams to detect anomalies faster and manage long-term metrics retention with greater efficiency.</p>
<p>We welcome you to <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">try the new features</a> today!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-metrics-analytics</link>
    <guid isPermaLink="false">elastic-metrics-analytics</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar,Yannis Roussos]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13437afca64e5c55/6a7f082aead8ec35f6baa678/elastic_metrics_leaner_blog_image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Explore and Analyze Metrics with Ease in Elastic Observability]]></title>
    <description><![CDATA[The latest enhancements to ES|QL and Discover based metrics exploration unleash a potent set of tools for quick and effective metrics analytics.]]></description>
    <content:encoded><![CDATA[<h2 id="metricsarecriticalinidentifyingthewhat">Metrics are critical in identifying the “what”</h2>
<p>As a core pillar of Observability, metrics offer a highly structured, quantitative view of system performance and health. They provide a crucial symptomatic perspective—revealing <em>what</em> is happening, such as high application latency, increasing service errors, or spiking container CPU utilization, which is essential for initiating alerting and triaging efforts. This capability for effective monitoring, alerting, and triaging is paramount to ensuring robust service delivery and achieving successful business outcomes.</p>
<p>Elastic Observability provides a comprehensive, end-to-end experience for metrics data. Elastic ensures that metrics data can be collected from numerous sources, enriched as needed and shipped to the Elastic Stack. Elastic efficiently stores this time series data, including high-cardinality metrics, utilizing the <a href="https://www.elastic.co/observability-labs/blog/time-series-data-streams-observability-metrics">TSDS index mode</a> (Time Series Data Stream), introduced in <a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0#efficient-storage-of-metrics-with-tsdb,-now-generally-available">prior versions</a> and used across Elastic time series <a href="https://www.elastic.co/blog/70-percent-storage-savings-for-metrics-with-elastic-observability">integrations</a>. This foundation ensures comprehensive observability through out-of-the-box dashboards, alerts, SLOs, and streamlined data management.</p>
<p>Elastic Observability 9.2 provides enhancements to metrics exploration and analysis through powerful query language extensions and expanded UI capabilities. These enhancements focus on making analysis on TSDS data via counter rates and common aggregations over time easier and faster than ever before.</p>
<p>The main metrics enhancements center on these key features, offered as Tech Preview:</p>
<ol>
<li>Metrics analytics with TSDS and ES|QL</li>
<li>Interactive metrics exploration in Discover</li>
<li>OTLP endpoint for metrics</li>
</ol>
<h2 id="metricsanalyticswithtsdsandesql">Metrics analytics with TSDS and ES|QL</h2>
<p>The introduction of the new <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> source command</a> in <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> (Elasticsearch Query Language) on TSDS metrics dramatically simplifies time series analysis.</p>
<p>The <code>TS</code> command is specifically designed to target only time series indices, differentiating it from the general <code>FROM</code> command. Its core power lies in enabling a dedicated suite of time series aggregation functions within the <code>STATS</code> command.</p>
<p>This mechanism utilizes a dual aggregation paradigm, which is standard for time series querying. These queries involve two aggregation functions:</p>
<ul>
<li><p><strong>Inner (Time Series) function:</strong> Applied implicitly per time series, often over bucketed time intervals.</p></li>
<li><p><strong>Outer (Regular) function:</strong> Used to aggregate the results of the inner function across groups. For instance, if you use <code>STATS SUM(RATE(search_requests)) BY TBUCKET(1 hour), host</code>, the <code>RATE()</code> function is the inner function applied per time series in hourly buckets, and <code>SUM()</code> is the outer function, summing these rates for each host and hourly bucket.</p></li>
</ul>
<p>If an ES|QL query using the <code>TS</code> command is missing an inner (time series) aggregation function, <code>LAST_OVER_TIME()</code> is implicitly assumed and used. For example, <code>TS metrics | STATS AVG(memory_usage)</code> is equivalent to <code>TS metrics | STATS AVG(LAST_OVER_TIME(memory_usage))</code>.</p>
<h3 id="keytimeseriesaggregationfunctionsavailableinesqlviatscommand">Key time series aggregation functions available in ES|QL via <code>TS</code> command</h3>
<p>These functions allow for powerful analysis on time-series data:</p>
<p>|                                                        |                                                                                                                                                                                                                                                                                                                                       |                                                               |
| :----------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------: |
|                      <strong>Function</strong>                      |                                                                                                                                                            <strong>Description</strong>                                                                                                                                                            |                      <strong>Example Use Case</strong>                     |
|                <code>RATE()</code> <strong>/</strong> <code>IRATE()</code>                | Calculates the per-second average rate of increase of a counter (<code>RATE</code>), accounting for non-monotonic breaks like counter resets, making it the most appropriate function for counters, or the per-second rate of increase between the last two data points (<code>IRATE</code>), ignoring all but the last two points for high responsiveness. |      Calculating request per second (RPS) or throughput.      |
|                    <code>AVG_OVER_TIME()</code>                   |                                                                                                                                 Calculates the average of a numeric field over the defined time range.                                                                                                                                |        Determining average resource usage over an hour.       |
|                    <code>SUM_OVER_TIME()</code>                   |                                                                                                                                           Calculates the sum of a field over the time range.                                                                                                                                          |           Total errors over a specific time window.           |
|        <code>MAX_OVER_TIME()</code> <strong>/</strong> <code>MIN_OVER_TIME()</code>       |                                                                                                                                     Calculates the maximum or minimum value of a field over time.                                                                                                                                     |             Identifying peak resource consumption.            |
|               <code>DELTA()</code> <strong>/</strong> <code>IDELTA()</code>               |                                                                      Calculates the absolute change of a gauge field over a time window (<code>DELTA</code>) or specifically between the last two data points (<code>IDELTA</code>), making <code>IDELTA</code> more responsive to recent changes.                                                                     | Tracking changes in system gauge metrics (e.g., buffer size). |
|                      <code>INCREASE()</code>                      |                                                                                                                                      Calculates the absolute increase of a counter (<code>INCREASE</code>).                                                                                                                                      |   Analyzing immediate rate changes in fast-moving counters.   |
|      <code>FIRST_OVER_TIME()</code> <strong>/</strong> <code>LAST_OVER_TIME()</code>      |                                                                                                                   Calculates the earliest or latest recorded value of a field, determined by the <code>@timestamp</code> field.                                                                                                                  |  Inspecting initial and final metric states within a bucket.  |
|    <code>ABSENT_OVER_TIME()</code> <strong>/</strong> <code>PRESENT_OVER_TIME()</code>    |                                                                                                                            Calculates the absence or presence of a field in the result over the time range.                                                                                                                           |             Identifying monitoring coverage gaps.             |
| <code>COUNT_OVER_TIME()</code> <strong>/</strong> <code>COUNT_DISTINCT_OVER_TIME()</code> |                                                                                                                            Calculates the total count or the count of distinct values of a field over time.                                                                                                                           |          Measuring frequency or cardinality changes.          |</p>
<p>These functions, available with the <code>TS</code> command, allow SREs and Ops teams to easily perform rate calculations and other common aggregations, enabling efficient metrics analysis as a routine part of observability workflows. And it’s much faster, too! Internal performance testing has revealed that TS commands outperform other ways of querying metrics data by an order of magnitude or more, and consistently! </p>
<h2 id="interactivemetricsexplorationindiscover">Interactive metrics exploration in Discover</h2>
<p>The 9.2 release introduces the capability to explore and analyze metrics directly and interactively within the Discover interface. In addition to exploring and analyzing logs and raw events, Discover now provides a dedicated environment for metrics exploration:</p>
<ul>
<li><p><strong>Easy start:</strong> Begin exploration simply by querying metrics ingested via <code>TS metrics-*</code>.</p></li>
<li><p><strong>Grid view and pre-applied aggregations:</strong> This command displays all metrics in a grid format at a glance, immediately applying the appropriate aggregations based on the metric type, such as <code>rate</code> versus <code>avg</code>.</p></li>
<li><p><strong>Search and group-by:</strong> Quickly search for specific metrics by name. Also easily group and analyze metrics by dimensions (labels) and specific values. This allows narrowing down to metrics and dimensions of choice for targeted analysis.</p></li>
<li><p><strong>Quick access to details:</strong> Furthermore, the interface provides access to crucial details, including query and response details, the underlying ES|QL commands, the metric field type, and applicable dimensions, for each metric.</p></li>
<li><p><strong>Easy tweaking and dashboarding:</strong> The system automatically populates ES|QL queries, aiding in making easy tweaks, slicing, and dicing the data. Once analyzed, metrics and resulting analyses can be added to new or existing dashboards with ease.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58ccd6deb4688879/6a7f0d5cc2cc0979e92495bc/metrics-discover-ts-command.png" alt="Interactive metrics exploration in Discover" /></p>
<h2 id="otlpendpointformetrics">OTLP endpoint for metrics</h2>
<p>We are also introducing a native OpenTelemetry Protocol (OTLP) endpoint specifically for metrics ingest directly into Elasticsearch. The endpoint especially benefits self-managed customers, and will be integrated into our <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic Cloud Managed OTLP Endpoint</a> for Elastic-managed offerings. The native endpoint and related updates improve ingest performance and scalability of OTel metrics, providing up to 60% higher throughput via <code>_otlp</code>, and up to 25% higher throughput when using classic <code>_bulk</code> methods. </p>
<h2 id="inconclusion">In Conclusion</h2>
<p>By merging the power of ES|QL's new time series aggregations with the familiar interactive experience of Discover, Elastic 9.2 enables a potent set of metrics analytics tools. The tools significantly boost the exploration and analysis phase of any observability workflow. And we’re just getting started on unleashing the full power of metrics in Elastic Observability!</p>
<p>We welcome you to <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">try the new features</a> today!</p>
<p>Also learn more about how we provide metrics analytics for AWS, Azure, GCP, Kubernetes, and LLMs on <a href="https://www.elastic.co/observability-labs">Observability Labs</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/metrics-explore-analyze-with-esql-discover</link>
    <guid isPermaLink="false">metrics-explore-analyze-with-esql-discover</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8fdb910165be324/6a7f0d5f63e959271573de1a/metrics-blog-image-ts-discover.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Observability for Amazon MQ with Elastic: Demystifying Messaging Flows with Real-Time Insights]]></title>
    <description><![CDATA[RabbitMQ, managed by Amazon MQ, enables asynchronous communication in distributed architectures but introduces operational risks such as retries, processing delays, and queue backlogs. Elastic’s Amazon MQ integration for RabbitMQ delivers deep observability into broker health, queue performance, message flow, and resource usage through Amazon CloudWatch metrics and logs. This blog outlines key operational risks associated with RabbitMQ and explains how Elastic observability helps maintain system reliability and optimize message delivery at scale.]]></description>
    <content:encoded><![CDATA[<h2 id="managingthehiddencomplexityofmessagedrivenarchitectures">Managing the Hidden Complexity of Message-Driven Architectures</h2>
<p>Amazon MQ is a managed message broker service for <a href="http://activemq.apache.org/">Apache ActiveMQ</a> Classic and <a href="https://www.rabbitmq.com/">RabbitMQ</a> that manages the setup, operation, and maintenance of message brokers. Messaging systems like RabbitMQ, managed by <a href="https://aws.amazon.com/amazon-mq/">Amazon MQ</a>, are pivotal in modern decoupled, event-driven applications. By serving as an intermediary between services, RabbitMQ facilitates asynchronous communication through message queuing, routing, and reliable delivery, making it an ideal fit for microservices, real-time pipelines, and event-driven architectures. However, this flexibility introduces operational challenges, such as retries, processing delays, consumer failures, and queue backlogs, which can gradually impact downstream performance and system reliability.</p>
<p>With Elastic’s <a href="https://www.elastic.co/docs/reference/integrations/aws_mq">Amazon MQ integration</a>, users gain deep visibility into message flow patterns, queue performance, and consumer health. This integration allows for the proactive detection of bottlenecks, helps optimize system behaviour, and ensures reliable message delivery at scale.</p>
<p>In this blog, we'll dive into the operational challenges of RabbitMQ in modern architectures, while also examining the common gaps and strategies for overcoming them.</p>
<h2 id="whyobservabilityforrabbitmqonamazonmqmatters">Why Observability for RabbitMQ on Amazon MQ Matters?</h2>
<p>RabbitMQ brokers are integral to distributed systems, handling tasks ranging from order processing to payment workflows and notification delivery. Any disruption can cascade into significant downstream issues. Observability into RabbitMQ helps answer critical operational questions like:​</p>
<ul>
<li>Is CPU and memory utilization increasing over time?</li>
<li>What are the trends in the message publish rate, message confirmation rate?</li>
<li>Are consumers failing to acknowledge messages?</li>
<li>Which queues are experiencing abnormal growth?</li>
<li>Are there an increasing number of messages being dead-lettered over time?</li>
</ul>
<h2 id="enhancedobservabilitywithamazonmqintegration">Enhanced Observability with Amazon MQ Integration</h2>
<p>Elastic provides a dedicated <a href="https://www.elastic.co/docs/reference/integrations/aws_mq">Amazon MQ integration</a> for RabbitMQ that utilizes Amazon CloudWatch metrics and logs to deliver comprehensive observability data. This integration enables the ingestion of metrics related to connections, nodes, queues, exchanges, and system logs.</p>
<p>By deploying <a href="https://www.elastic.co/elastic-agent">Elastic Agent</a> with this integration, the users can monitor:​</p>
<ul>
<li><strong>Queue performance and Dead-letter queue (DLQ) metrics</strong> include total message count (<code>MessageCount.max</code>), messages ready for delivery (<code>MessageReadyCount.max</code>), and unacknowledged messages (<code>MessageUnacknowledgedCount.max</code>). <code>MessageCount.max</code> metric tracks the total number of messages in a queue, including those that have been dead-lettered, and monitoring this over time can help identify trends in message accumulation, which may suggest issues leading to dead-lettering.</li>
<li><strong>Consumer behaviour</strong> through metrics like consumer count (<code>ConsumerCount.max</code>) and acknowledgement rate (<code>AckRate.max</code>), which help identify underperforming consumers or potential backlogs.</li>
<li><strong>Messaging throughput</strong> by tracking publish (<code>PublishRate.max</code>), confirm (<code>ConfirmRate.max</code>), and acknowledgement rates in real time. These are crucial for understanding application messaging patterns and flow.</li>
<li><strong>Broker and node-level health,</strong> including memory usage (<code>RabbitMQMemUsed.max</code>), CPU utilization (<code>SystemCpuUtilization.max</code>), disk availability (<code>RabbitMQDiskFree.min</code>), and file descriptor usage (<code>RabbitMQFdUsed.max</code>). These indicators are essential for diagnosing resource saturation and avoiding service disruption.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc24f4a23bb4ba33e/6a85c765501a8507cdfbb290/amazonmq-rabbitmq-dashboard-overview.png" alt="" /></p>
<h2 id="integratingamazonmqmetricsintoelasticobservability">Integrating Amazon MQ Metrics into Elastic Observability</h2>
<p>Elastic's Amazon MQ integration facilitates the ingestion of CloudWatch metrics and logs into Elastic Observability, delivering near real-time insights into RabbitMQ. The prebuilt Amazon MQ dashboard visualizes this data, providing a centralized view of broker health, messaging activity, and resource usage, helping users quickly detect and resolve issues. Elastic's <a href="https://www.elastic.co/docs/solutions/observability/incident-management/alerting">alerting</a> for Observability enables proactive notifications based on custom conditions, while its <a href="https://www.elastic.co/docs/solutions/observability/incident-management/service-level-objectives-slos">SLO</a> capabilities allow users to define and track key performance targets, strengthening system reliability and service commitments. </p>
<p>Elastic brings together logs and metrics from Amazon MQ alongside data from a wide range of other services and applications, whether running in AWS, on-premises, or across multi-cloud environments, offering unified observability from a single platform.</p>
<h3 id="prerequisites">Prerequisites</h3>
<p>To follow along, ensure you have:</p>
<ul>
<li>An account on <a href="http://cloud.elastic.co/">Elastic Cloud</a> and a deployed stack in AWS (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>). Ensure you are using version 8.16.5 or higher. Alternatively, you can use <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>, a fully managed solution that eliminates infrastructure management, automatically scales based on usage, and lets you focus entirely on extracting value from your data.</li>
<li>An AWS account with permissions to pull the necessary data from AWS. <a href="https://docs.elastic.co/en/integrations/aws#aws-permissions">See details in our documentation</a>.</li>
</ul>
<h3 id="architecture">Architecture</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7e5165fe435a3cd/6a85c7689bf994127a0a04bc/rabbitmq_lambda_messageflow.png" alt="" /></p>
<h2 id="tracingauditflowsfromrabbitmqtoawslambda">Tracing Audit Flows from RabbitMQ to AWS Lambda</h2>
<p>Consider a financial audit trail use case, where every user action, such as a funds transfer, is published to RabbitMQ. A Python-based AWS Lambda function consumes these messages, deduplicates them using the <strong>id</strong> field, and logs structured audit events for downstream analysis.</p>
<p>Sample payload sent through RabbitMQ:</p>
<pre><code>{
&amp;nbsp;&amp;nbsp;"id": "txn-849302",
&amp;nbsp;&amp;nbsp;"type": "audit",
&amp;nbsp;&amp;nbsp;"payload": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"user_id": "u-10245",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"event": "funds.transfer",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"amount": 1200.75,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"currency": "USD",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"timestamp": "T14:20:15Z",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"ip": "192.168.0.8",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"location": "New York, USA"
&amp;nbsp;&amp;nbsp;}
}
</code></pre>
<p>You can now correlate message publishing activity from RabbitMQ with AWS Lambda invocation logs, track processing latency, and configure alerts for conditions like drops in consumer throughput or an unexpected surge in RabbitMQ queue depth.</p>
<h3 id="awslambdafunctionprocessingrabbitmqmessages">AWS Lambda Function: Processing RabbitMQ Messages</h3>
<p>This Python-based AWS Lambda function processes audit events received from RabbitMQ. It deduplicates messages based on the <strong>id</strong> field and logs structured event data for downstream analysis or compliance. Save the code below in a file named <strong>app.py</strong>.</p>
<pre><code>import json
import logging
import base64
# Configure logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# In-memory set to track processed message IDs for deduplication
processed_ids = set()
def lambda_handler(event, context):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info("Lambda triggered by RabbitMQ event")
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if 'rmqMessagesByQueue' not in event:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.warning("Invalid event: missing 'rmqMessagesByQueue'")
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {'statusCode': 400, 'body': 'Invalid RabbitMQ event'}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for queue_name, messages in event['rmqMessagesByQueue'].items():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f"Processing queue: {queue_name}, Messages count: {len(messages)}")
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for msg in messages:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&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;raw_data = msg['data']
&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;decoded_json = base64.b64decode(raw_data).decode('utf-8')
&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;message = json.loads(decoded_json)
&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;logger.info(f"Decoded message: {json.dumps(message)}")
&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;message_id = message.get('id')
&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;if not message_id:
&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;logger.warning("Message missing 'id', skipping.")
&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;continue
&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;if message_id in processed_ids:
&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;logger.warning(f"Duplicate message detected: {message_id}")
&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;continue
&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;payload = message.get('payload', {})
&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;logger.info(f"Processing message ID: {message_id}")
&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;logger.info(f"Event Type: {message.get('type')}")
&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;logger.info(f"User ID: {payload.get('user_id')}")
&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;logger.info(f"Event: {payload.get('event')}")
&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;logger.info(f"Amount: {payload.get('amount')} {payload.get('currency')}")
&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;logger.info(f"Timestamp: {payload.get('timestamp')}")
&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;logger.info(f"IP Address: {payload.get('ip')}")
&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;logger.info(f"Location: {payload.get('location')}")
&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;processed_ids.add(message_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except Exception as e:
&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;logger.error(f"Error processing message: {str(e)}")
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {'statusCode': 200, 'body': 'Messages processed successfully'}
</code></pre>
<h3 id="settingupawssecretsmanager">Setting up AWS Secrets Manager</h3>
<p>To securely store and manage your RabbitMQ credentials, use AWS Secrets Manager.​</p>
<ol>
<li><strong>Create a New Secret:</strong></li>
</ol>
<ul>
<li>Navigate to the<a href="https://console.aws.amazon.com/secretsmanager/"> AWS Secrets Manager console</a>.</li>
<li>Choose <strong>Store a new secret</strong>.</li>
<li>Select <strong>Other type of secret</strong>.</li>
<li>Enter the following key-value pairs:<ul>
<li><code>username</code>: Your RabbitMQ username</li>
<li><code>password</code>: Your RabbitMQ password</li></ul></li>
</ul>
<ol>
<li><strong>Configure the Secret:</strong></li>
</ol>
<ul>
<li>Provide a meaningful name, such as <code>RabbitMQAccess</code>.</li>
<li>Optionally, add tags and set rotation if needed.​</li>
</ul>
<ol>
<li><strong>Store the Secret:</strong></li>
</ol>
<ul>
<li>Review the settings and store the secret. Note the ARN of the secret you have created.
 <img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt08666b47e1f94017/6a85c76b2d64d51bca081caa/aws-secret-manager-configuration.png" alt="" /></li>
</ul>
<h3 id="settingupamazonmqforrabbitmq">Setting up Amazon MQ for RabbitMQ</h3>
<p>To get started with RabbitMQ on Amazon MQ, follow these steps to set up your broker.</p>
<ul>
<li><p>Open the <a href="https://console.aws.amazon.com/amazonmq/">Amazon MQ console</a>.</p></li>
<li><p>Create a new broker with the <strong>RabbitMQ</strong> engine.</p></li>
<li><p>Choose your preferred deployment option—<strong>single-instance</strong> or <strong>clustered</strong></p></li>
<li><p>Use the same <strong>username</strong> and <strong>password</strong> that you previously stored in <strong>AWS Secrets Manager</strong>.</p></li>
<li><p>Under <strong>Additional settings</strong>, enable <strong>CloudWatch Logs</strong> for observability.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt167e5c95f8e3f76e/6a85c76eeaf245dce0a49ec8/amazonmq-cloudwatch-enable.png" alt="" /></p></li>
<li><p>Configure access and security settings, ensuring that the broker is accessible to your AWS Lambda function.</p></li>
<li><p>After the broker is created, note the following important details:</p></li>
<li><p>ARN of the RabbitMQ broker.</p></li>
<li><p>RabbitMQ web console URL.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb73efc6fdb659c87/6a85c77043c0b728ac2f057d/amazonmq-rabbitmq-configuration-summary.png" alt="" /></p></li>
<li><p>You’ll need the RabbitMQ log group ARN to set up Elastic’s Amazon MQ integration for RabbitMQ. Follow these steps to locate it:</p></li>
<li><p>Go to the <strong>General – Enabled Logs</strong> section of the broker. </p></li>
<li><p>Copy the <strong>CloudWatch log group ARN</strong>.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02016bdb2becda09/6a85c77333f244cd0b49f454/amazonmq-rabbitmq-loggroup-arn.png" alt="" /></p></li>
</ul>
<h3 id="createarabbitmqqueue">Create a RabbitMQ Queue</h3>
<p>Now that the RabbitMQ broker is configured, use the management console to create a queue where messages will be published.</p>
<ul>
<li>Access the RabbitMQ management console using the web console URL.</li>
<li>Create a new queue (example: <strong>myQueue</strong>) to receive messages.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbecef240548d449b/6a85c77511893c2658a7aae8/rabbitmq-create-queue.png" alt="" /></li>
</ul>
<h3 id="buildanddeploytheawslambdafunction">Build and deploy the AWS Lambda function</h3>
<p>In this section, we'll set up the Lambda function using AWS SAM, add the message processing logic, and deploy it to AWS. This Lambda function will be responsible for consuming messages from RabbitMQ and logging audit events.</p>
<p>Before continuing, make sure you have completed the following prerequisites.</p>
<ul>
<li><p><a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/prerequisites.html">AWS SAM prerequisites</a></p></li>
<li><p><a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html">Install the AWS SAM CLI</a></p></li>
</ul>
<p>Next, follow the steps outlined below to continue with the setup.</p>
<ol>
<li>In your command line, run the command <code>sam init</code> from a directory of your choice.</li>
<li>The AWS SAM CLI will walk you through the setup.<ul>
<li>Select <strong>AWS Quick Start Templates</strong>.</li>
<li>Choose the <strong>Hello World Example</strong> </li>
<li>Use the <strong>Python</strong> runtime and <strong>zip</strong> package type.</li>
<li>Proceed with the default options.</li>
<li>Name your application as <strong>sample-rabbitmq-app</strong>.</li>
<li>The AWS SAM CLI downloads your starting template and creates the application project directory structure.</li></ul></li>
<li>From your command line, move to the newly created sample-rabbitmq-app directory.<ul>
<li>Replace the content of the <strong>hello_world/app.py</strong> file with the lambda function code for rabbitmq message processing.</li>
<li>In the <strong>template.yaml</strong> file, use the values mentioned below to update the file content.
<code>yaml
Resources:
&amp;nbsp;SampleRabbitMQApp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;Type: AWS::Serverless::Function
&amp;nbsp;&amp;nbsp;&amp;nbsp;Properties:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CodeUri: hello_world/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Description: A starter AWS Lambda function.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MemorySize: 128
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Timeout: 3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Handler: app.lambda_handler
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Runtime: python3.10
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PackageType: Zip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Policies:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- Statement:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- Effect: Allow
&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;Resource: '*'
&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;Action:
&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;- mq:DescribeBroker
&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;- secretsmanager:GetSecretValue
&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;- ec2:CreateNetworkInterface
&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;- ec2:DescribeNetworkInterfaces
&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;- ec2:DescribeVpcs
&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;- ec2:DeleteNetworkInterface
&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;- ec2:DescribeSubnets
&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;- ec2:DescribeSecurityGroups
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Events:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MQEvent:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Type: MQ
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Properties:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Broker: &lt;ARN of the Broker&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Queues:
&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;- myQueue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SourceAccessConfigurations:
&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;- Type: BASIC_AUTH
&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;URI: &lt;ARN of the secret&gt;
</code></li></ul></li>
<li>Run the command <code>sam deploy --guided</code> and wait for the confirmation message. This deploys all of the resources.</li>
</ol>
<h3 id="sendingauditeventstorabbitmqandtriggeringlambda">Sending Audit Events to RabbitMQ and Triggering Lambda</h3>
<p>To test the end-to-end setup, simulate the flow by publishing audit event data into RabbitMQ using its web UI. Once the message is sent, it triggers the Lambda function. </p>
<ol>
<li>Navigate to the <a href="https://console.aws.amazon.com/amazon-mq/home">Amazon MQ console</a> and select your newly created broker.</li>
<li>Locate and open the Rabbit web console URL<br />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8eab69977143aaac/6a85c77811893c0963a7aaec/amazonmq-rabbitmq-webconsole-details.png" alt="" /></li>
<li>Under the <strong>Queues and Streams</strong> tab, select the target queue (example: <strong>myQueue</strong>).</li>
<li>Enter the message payload, and click <strong>Publish message</strong> to send it to the queue.<br />
Here’s a sample payload published via RabbitMQ:</li>
</ol>
<pre><code>   {
     "id": "txn-849302",
     "type": "audit",
     "payload": {
       "user_id": "u-10245",
       "event": "funds.transfer",
       "amount": 1200.75,
       "currency": "USD",
       "timestamp": "T14:20:15Z",
       "ip": "192.168.0.8",
       "location": "New York, USA"
     }
   }
</code></pre>
<ol>
<li>Navigate to the AWS Lambda function created earlier.</li>
<li>Under the <strong>Monitor</strong> tab, click <strong>View CloudWatch logs</strong>.</li>
<li>Check the latest log stream to confirm that the Lambda was triggered by Amazon MQ and that the message was processed successfully.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6745896b03a43a58/6a85c77b80984c3844668f47/amazonmq-lambda-logstream.png" alt="" /></li>
</ol>
<h2 id="configuringamazonmqintegrationformetricsandlogscollection">Configuring Amazon MQ integration for Metrics and Logs collection</h2>
<p>Elastic’s <a href="https://www.elastic.co/docs/reference/integrations/aws_mq">Amazon MQ integration</a> simplifies the collection of logs and metrics from RabbitMQ brokers managed by Amazon MQ. Logs are ingested via <strong>Amazon CloudWatch Logs</strong>, while metrics are fetched from the specified AWS region at a defined interval.</p>
<p>Elastic provides a default configuration for metrics collection. You can accept these defaults or adjust settings such as the <strong>Collection Period</strong> to better fit your needs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84b7cd1485929dc9/6a85c77dd7b2e743e7fe843e/amazonmq-metrics-configuration.png" alt="" /></p>
<p>To enable the collection of logs:</p>
<ol>
<li>Navigate to the <a href="https://console.aws.amazon.com/amazon-mq/home">Amazon MQ console</a> and select the newly created broker.</li>
<li>Click the <strong>Logs</strong> hyperlink under the <strong>General – Enabled Logs</strong> section to open the detailed log settings page.</li>
<li>From this page, copy the <strong>CloudWatch log group ARN</strong>.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02016bdb2becda09/6a85c77333f244cd0b49f454/amazonmq-rabbitmq-loggroup-arn.png" alt="" /></li>
<li>In <strong>Elastic</strong>, set up the <strong>Amazon MQ integration</strong> and paste the CloudWatch log group ARN.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f9312d1eeac7165/6a85c78199083f572540f947/amazonmq-logs-configuration.png" alt="" /></li>
<li><strong>Accept Defaults or Customize Settings</strong> – Elastic provides a <strong>default configuration</strong> for logs collection. You can accept these defaults or adjust settings such as <strong>collection intervals</strong> to better fit your needs.</li>
</ol>
<h3 id="visualizingrabbitmqworkloadswiththeprebuiltamazonmqdashboard">Visualizing RabbitMQ Workloads with the Pre-Built Amazon MQ Dashboard</h3>
<p>You can access the RabbitMQ dashboard by:</p>
<ol>
<li><p>Navigate to the Dashboard Menu – Select the Dashboard menu option in Elastic and search for <strong>[Amazon MQ] RabbitMQ Overview</strong> to open the dashboard.</p></li>
<li><p>Navigate to the Integrations Menu – Open the <strong>Integrations</strong> menu in Elastic, select <strong>Amazon MQ</strong>, go to the <strong>Assets</strong> tab, and choose <strong>[Amazon MQ] RabbitMQ Overview</strong> from the dashboard assets</p></li>
</ol>
<p>The Amazon MQ RabbitMQ dashboard in the Elastic integration delivers a comprehensive overview of broker health and messaging activity. It provides real-time insights into broker resource utilization, queue and topic performance, connection trends, and messaging throughput. The dashboard helps users track system behaviour, detect performance bottlenecks, and ensure reliable message delivery across distributed applications.</p>
<h4 id="brokermetrics">Broker Metrics</h4>
<p>This section provides a centralised view of the overall health and performance of the RabbitMQ broker on Amazon MQ. The visualizations highlights the number of configured exchanges and queues, active broker connections, producers, consumers, and total messages in flight. System-level metrics such as CPU utilization, memory consumption, and free disk space help assess whether the broker has sufficient resources to handle current workloads.</p>
<p>Message flow metrics such as publish rate, confirmation rate, and acknowledgement rate are displayed to provide visibility into how messages are processed through the broker. Monitoring trends in these values helps detect message delivery issues, throughput degradation, or potential saturation of the broker under load.</p>
<h4 id="nodemetrics">Node Metrics</h4>
<p>Node-level visibility helps identify resource imbalances across nodes in clustered RabbitMQ setups. This section includes per-node CPU usage, memory consumption, and available disk space, offering insight into the underlying infrastructure's ability to support broker operations.</p>
<h4 id="queuemetrics">Queue Metrics</h4>
<p>Queue-specific insights are critical for understanding message delivery patterns and backlog conditions. This section details total messages, ready messages, and unacknowledged messages, segmented by broker, virtual host, and queue.</p>
<p>By observing how these counts change over time, users can identify slow consumers, message build-ups, or delivery issues that may affect application performance or lead to dropped messages under pressure.</p>
<h4 id="logs">Logs</h4>
<p>This section displays log level, process ID, and raw message content. These logs provide immediate visibility into events such as connection failures, resource thresholds being hit, or unexpected queue behaviors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt08bdcf335184796a/6a85c784331d7a8019c316d0/amazonmq-rabbitmq-dashboard.png" alt="" /></p>
<h3 id="detectingqueuebacklogswithalertingrules">Detecting Queue Backlogs with Alerting Rules</h3>
<p>Elastic’s <a href="https://www.elastic.co/docs/solutions/observability/incident-management/alerting">alert</a> framework allows you to define rules that monitor critical RabbitMQ metrics and automatically trigger actions when specific thresholds are breached. </p>
<h4 id="alertqueuebacklogmessagereadyorunacknowledgedmessages">Alert: Queue Backlog (Message Ready or Unacknowledged Messages)</h4>
<p>This alert helps detect queue backlog in Amazon MQ by evaluating two metrics </p>
<ul>
<li><code>MessageUnacknowledgedCount.max</code> and </li>
<li><code>MessageReadyCount.max</code>. </li>
</ul>
<p>The alert is triggered if either condition persists for more than <strong>10 minutes</strong>:</p>
<ul>
<li><code>MessageUnacknowledgedCount.max</code> exceeds <strong>5,000</strong></li>
<li><code>MessageReadyCount.max</code> exceeds <strong>7,000</strong></li>
</ul>
<p>These thresholds should be adjusted based on typical message volume and consumer throughput. Sustained high values can indicate that consumers are not keeping up or message delivery pipelines are congested, potentially causing delays or dropped messages. Sustained high values may result in processing delays or dropped messages if not addressed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50e0eb4b370c9c66/6a85c78799083f279d40f94b/amazonmq-alert-configuration.png" alt="" /></p>
<h3 id="trackingresourceutilizationtomaintainrabbitmqperformance">Tracking Resource Utilization to Maintain RabbitMQ Performance</h3>
<p>Elastic’s <a href="https://www.elastic.co/docs/solutions/observability/incident-management/service-level-objectives-slos">Service-level objectives (SLOs)</a> capabilities allow you to define and monitor performance targets using key indicators like latency, availability, and error rates. Once configured, Elastic continuously evaluates these SLOs in real time, offering intuitive dashboards, alerts for threshold violations, and insights into error budget consumption. This enables teams to stay ahead of issues, ensuring service reliability and consistent performance.</p>
<h4 id="slonoderesourcehealthcpumemorydisk">SLO: Node Resource Health (CPU, Memory, Disk)</h4>
<p>This SLO focuses on ensuring RabbitMQ brokers and nodes have sufficient resources to process messages without performance degradation. It tracks CPU, memory, and disk usage across RabbitMQ brokers and nodes to prevent resource exhaustion that could lead to service interruptions.</p>
<p><strong>Target thresholds:</strong></p>
<ul>
<li><code>SystemCpuUtilization.max</code> remains below <strong>85%</strong> for <strong>99%</strong> of the time.</li>
<li><code>RabbitMQMemUsed.max</code> remains below <strong>80%</strong> of <code>RabbitMQMemLimit.max</code> for <strong>99%</strong> of the time.</li>
<li><code>RabbitMQDiskFree.min</code> remains above <strong>25%</strong> of <code>RabbitMQDiskFreeLimit.max</code> for <strong>99%</strong> of the time.</li>
</ul>
<p>Sustained high values in CPU or memory usage can signal resource contention, which may result in slower message processing or downtime. Low disk availability may cause the broker to stop accepting messages, risking message loss. These thresholds are designed to catch early signs of resource saturation and ensure smooth, uninterrupted message flow across RabbitMQ deployments.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b36bd4f9f3fc9bb/6a85c78a18249ca4e818f70d/amazonmq-slo-configuration.png" alt="" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>As RabbitMQ-based messaging architectures scale and become more complex, the need for in-depth visibility into system performance and potential issues deepens. Elastic’s <a href="https://www.elastic.co/docs/reference/integrations/aws_mq">Amazon MQ integration</a> brings that visibility front and center—helping you go beyond basic health checks to understand real-time messaging throughput, queue backlog trends, and resource saturation across your brokers and consumers.</p>
<p>By leveraging the prebuilt dashboards, configuring alerts and SLOs, you can proactively detect anomalies, fine-tune consumer performance, and ensure reliable delivery across your event-driven applications.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/amazonmq-observability-rabbitmq-integration</link>
    <guid isPermaLink="false">amazonmq-observability-rabbitmq-integration</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Agi K Thomas,Udayasimha Theepireddy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a221b8b9fa36929/6a85c78d43c0b790d62f058e/AmazonMQ-observability-RabbitMQ.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 02 May 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[Instrumenting your OpenAI-powered Python, Node.js, and Java Applications with EDOT]]></title>
    <description><![CDATA[Elastic is proud to introduce OpenAI support in our Python, Node.js and Java EDOT SDKs. These add logs, metrics and tracing to applications that use OpenAI compatible services without any code change.]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Last year, <a href="https://www.elastic.co/blog/elastic-distributions-opentelemetry">we announced Elastic Distribution of OpenTelemetry</a> (a.k.a. EDOT) language SDKs, which collect logs, traces and metrics from applications. When this was announced, we didn’t yet support Large Language Model (LLM) providers such as OpenAI. This limited insight developers had into Generative AI (GenAI) applications.</p>
<p>In a <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">prior post</a>, we reviewed LLM observability focus, such as token usage, chat latency and knowing which tools (like DuckDuckGo) your application uses. With the right logs, traces and metrics, developers can answer questions like "Which version of a model generated this response?" or "What was the exact chat prompt created by my RAG application?"</p>
<p>In the last six months, Elastic invested a lot of energy alongside others in the OpenTelemetry community towards shared specifications on these areas, including code to collect LLM related logs, metrics and traces. Our goal was to extend the zero code (agent) approach EDOT brings to GenAI use cases.</p>
<p>Today, we announce our first GenAI instrumentation capability in the EDOT language SDKs: OpenAI. Below, you’ll see how to observe GenAI applications using our Python, Node.js and Java EDOT SDKs.</p>
<h2 id="exampleapplication">Example application</h2>
<p>Many of us may be familiar with <a href="https://chatgpt.com/">ChatGPT</a>, which is frontend for OpenAI’s GPT model family. Using this, you can ask a question and the assistant might reply correctly depending on what you ask and text the LLM was trained on.</p>
<p>Here’s an example of an esoteric question answered by ChatGPT:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltedb3a50448470314/6a7f08cbbd21986162757f1f/chatgpt-screenshot.png" alt="ChatGPT answer" /></p>
<p>Our example application will simply ask this predefined question and print the result. We’ll write it in three languages: Python, JavaScript and Java.</p>
<p>We’ll execute each with a "zero code" (agent) approach, so that logs, metrics and traces are captured and visible in an Elastic Stack configured with Kibana and APM server. If you don’t have a stack running, use <a href="https://github.com/elastic/elasticsearch-labs/tree/main/docker">instructions from Elasticsearch Labs</a> to set one up.</p>
<p>Regardless of programming language, three variables are needed: the OpenAI API key, the location of your Elastic APM server, and the service name of the application. You’ll write these to a file named <code>.env</code>.</p>
<pre><code>OPENAI_API_KEY=sk-YOUR_API_KEY
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8200
OTEL_SERVICE_NAME=openai-example
</code></pre>
<p>By default instrumentations does not capture the content sent to the OpenAI API in the GenAI events sent to logs, if you want to capture it add the following:</p>
<pre><code>OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
</code></pre>
<p>Each time the application is run, it sends logs, traces and metrics to the APM server, which you can find by querying Kibana like this for the application "openai-example"</p>
<p>http://localhost:5601/app/apm/services/openai-example/transactions</p>
<p>When you choose a trace, you’ll see the LLM request made by the OpenAI SDK, and HTTP traffic caused by it:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5987344c4d68ea32/6a7f08cf96b5a6107687b2cd/kibana-transaction-timeline.png" alt="Kibana transaction timeline" /></p>
<p>Select the logs tab to see the exact request and response to OpenAI. This data is critical for Q/A and evaluation use cases.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta35f22e88805b0ee/6a7f08d24c4bfbe008ccd395/kibana-transaction-logs.png" alt="Kibana transaction logs" /></p>
<p>You can also go to the Metrics Explorer and make a graph of "gen_ai.client.token.usage" or "gen_ai.client.operation.duration" over all the times you ran the application:</p>
<p>http://localhost:5601/app/metrics/explorer</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0561523ff7bc4b29/6a7f08d51967eac5b5330593/kibana-metrics-explorer.png" alt="Kibana Metrics Explorer" /></p>
<p>Continue to see exactly how this application looks and is run, in Python, Java and Node.js. Those already using our EDOT language SDKs will be familiar with how this works.</p>
<h2 id="python">Python</h2>
<p>Assuming you have python installed, the first thing would be to setup a virtual environment and install the required packages: the OpenAI client, a helper tool to read the <code>.env</code> file and our <a href="https://github.com/elastic/elastic-otel-python">EDOT Python</a> package:</p>
<pre><code>python3 -m venv .venv
source .venv/bin/activate
pip install openai "python-dotenv[cli]" elastic-opentelemetry
</code></pre>
<p>Next, run <code>edot-bootstrap</code> which analyzes the code to install any relevant instrumentation available:</p>
<pre><code>edot-bootstrap —-action=install
</code></pre>
<p>Now, create your <code>.env</code>file, as described earlier in this article, and the below source code in <code>chat.py</code></p>
<pre><code>import os

import openai

CHAT_MODEL = os.environ.get("CHAT_MODEL", "gpt-4o-mini")


def main():
  client = openai.Client()

  messages = [
    {
      "role": "user",
        "content": "Answer in up to 3 words: Which ocean contains Bouvet Island?",
    }
  ]

  chat_completion = client.chat.completions.create(model=CHAT_MODEL, messages=messages)
  print(chat_completion.choices[0].message.content)

if __name__ == "__main__":
  main()
</code></pre>
<p>Now you can run everything with:</p>
<pre><code>dotenv run -- opentelemetry-instrument python chat.py
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example (along with the instructions) in the Python EDOT repository <a href="https://github.com/elastic/elastic-otel-python/tree/main/examples/openai">here</a>.</p>
<p>Finally, if you would like to try a more comprehensive example, take a look at <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">chatbot-rag-app</a> which uses OpenAI with Elasticsearch’s <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elser</a> retrieval model.</p>
<h2 id="java">Java</h2>
<p>There are multiple popular ways to initialize a Java project. Since we are using OpenAI, the first step is to configure the dependency <a href="https://central.sonatype.com/artifact/com.openai/openai-java"><code>com.openai:openai-java</code></a> and write the below source as <code>Chat.java.</code></p>
<pre><code>package openai.example;

import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;


final class Chat {

  public static void main(String[] args) {
    String chatModel = System.getenv().getOrDefault("CHAT_MODEL", "gpt-4o-mini");

    OpenAIClient client = OpenAIOkHttpClient.fromEnv();

    String message = "Answer in up to 3 words: Which ocean contains Bouvet Island?";
    ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
        .addMessage(ChatCompletionUserMessageParam.builder()
          .content(message)
          .build())
        .model(chatModel)
        .build();

    ChatCompletion chatCompletion = client.chat().completions().create(params);
    System.out.println(chatCompletion.choices().get(0).message().content().get());
  }
}
</code></pre>
<p>Build the project such that all dependencies are in a single jar. For example, if using Gradle, you would use the <code>com.gradleup.shadow</code>plugin.</p>
<p>Next, create your <code>.env</code>file, as described earlier, and download shdotenv which we’ll use to load it.</p>
<pre><code>curl -O -L https://github.com/ko1nksm/shdotenv/releases/download/v0.14.0/shdotenv
chmod +x ./shdotenv
</code></pre>
<p>At this point, you have a jar and configuration you can use to run the OpenAI example. The next step is to download the EDOT Java javaagent binary. This is the part that records and exports logs, metrics and traces.</p>
<pre><code>curl -o elastic-otel-javaagent.jar -L 'https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&amp;g=co.elastic.otel&amp;a=elastic-otel-javaagent&amp;v=LATEST'
</code></pre>
<p>Assuming you assembled a file named <code>openai-example-all.jar</code>, run it with EDOT like this:</p>
<pre><code>./shdotenv java -javaagent:elastic-otel-javaagent.jar -jar openai-example-all.jar
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example in the EDOT Java source repository <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/openai">here</a>.</p>
<h2 id="nodejs">Node.js</h2>
<p>Assuming you already have npm installed and configured, run the following commands to initialize a project for the example. This includes the <a href="https://www.npmjs.com/package/openai">openai</a> package and <a href="https://www.npmjs.com/package/@elastic/opentelemetry-node"><code>@elastic/opentelemetry-node</code></a> (EDOT Node.js)</p>
<pre><code>npm init -y
npm install openai @elastic/opentelemetry-node
</code></pre>
<p>Next, create your <code>.env</code> file, as described earlier in this article and the below source code in <code>index.js</code></p>
<pre><code>const {OpenAI} = require('openai');

let chatModel = process.env.CHAT_MODEL ?? 'gpt-4o-mini';

async function main() {
 const client = new OpenAI();
 const completion = await client.chat.completions.create({
  model: chatModel,
  messages: [
   {
    role: 'user',
    content: 'Answer in up to 3 words: Which ocean contains Bouvet Island?',
   },
  ],
 });
 console.log(completion.choices[0].message.content);
}

main();
</code></pre>
<p>With this in place, run the above source with EDOT like this:</p>
<pre><code>node --env-file .env --require @elastic/opentelemetry-node index.js
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example in the EDOT Node.js source repository <a href="https://github.com/elastic/elastic-otel-node/tree/main/examples/openai">here</a>.</p>
<p>Finally, if you would like to try a more comprehensive example, take a look at <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/openai-embeddings">openai-embeddings</a> which uses OpenAI with Elasticsearch as a vector database!</p>
<h2 id="closingnotes">Closing Notes</h2>
<p>Above you’ve seen how to observe the official OpenAI SDK in three different languages, using Elastic Distribution of OpenTelemetry (EDOT).</p>
<p>It is important to note that some of the OpenAI SDKs and also OpenTelemetry specifications around generative AI are experimental. If you find this helps you, or find glitches, please join our slack and let us know about it.</p>
<p>Several LLM platforms accept requests from the OpenAI client SDK, by setting <code>OPENAI_BASE_URL</code> and choosing relevant models. During development, we tested against OpenAI Platform and Azure OpenAI Service. We also ran integration tests against Ollama, contributing improvements its OpenAI support released in v0.5.12. Whatever your choice of OpenAI compatible platform, we hope this new tooling helps you understand your LLM usage.</p>
<p>Finally, while the first Generative AI SDK instrumented with EDOT is OpenAI, you’ll see more soon. We are already working on Bedrock, and collaborating with others in the OpenTelemetry community for other platforms. Keep watching this blog for exciting updates.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai</link>
    <guid isPermaLink="false">elastic-opentelemetry-openai</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Adrian Cole]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43126fbb328992ce/6a84041c5751aa67087e402a/elastic-opentelemetry-openai.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Supercharge Your vSphere Monitoring with Enhanced vSphere Integration]]></title>
    <description><![CDATA[Supercharge Your vSphere Monitoring with Enhanced vSphere Integration]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.vmware.com/products/cloud-infrastructure/vsphere">vSphere</a> is VMware's cloud computing virtualization platform that provides a powerful suite for managing virtualized resources. It allows organizations to create, manage, and optimize virtual environments, providing advanced capabilities such as high availability, load balancing, and simplified resource allocation. vSphere enables efficient utilization of hardware resources, reducing costs while increasing the flexibility and scalability of IT infrastructure.</p>
<p>With the release of an upgraded <a href="https://www.elastic.co/docs/current/integrations/vsphere">vSphere integration</a> we now support an enhanced set of metrics and datastreams. Package version 1.15.0 onwards introduces new datastreams that significantly improve the collection of performance metrics, providing deeper insights into your vSphere environment.</p>
<p>This enhanced version includes a total of seven datastreams, featuring critical new metrics such as disk performance, memory utilization, and network status. Additionally, these datastreams now offer detailed visibility into associated resources like hosts, clusters, and resource pools. To make the most of these insights, we’ve also introduced prebuilt dashboards, helping teams monitor and troubleshoot their vSphere environments with ease and precision.</p>
<p>We have expanded the performance metrics to encompass a broader range of insights across all datastreams, while also introducing new datastreams for clusters, resource pools, and networks. This enhanced integration version now includes a total of seven datastreams, featuring critical new metrics such as disk performance, memory utilization, and network status. Additionally, these datastreams now offer detailed visibility into associated resources like hosts, clusters, and resource pools. </p>
<p>Each datastream also includes detailed alarm information, such as the alarm name, description, status (e.g. critical or warning), and the affected entity's name. To make the most of these insights, we’ve also introduced prebuilt dashboards, helping teams monitor and troubleshoot their vSphere environments with ease and precision.</p>
<h2 id="overviewofthedatastreams">Overview of the Datastreams</h2>
<ul>
<li><strong>Host Datastream:</strong> This datastream monitors the disk performance of the host, including metrics such as disk latency, average read/write bytes, uptime, and status. It also captures network metrics, such as packet information, network bandwidth, and utilization, as well as CPU and memory usage of the host. Additionally, it lists associated datastores, virtual machines, and networks within vSphere.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta168f441354fc97e/6a7f1b6642a117cc8c95c333/hosts.png" alt="Host Datastream" /></p>
<ul>
<li><strong>Virtual Machine Datastream:</strong> This datastream tracks the used and available CPU and memory resources of virtual machines, along with the uptime and status of each VM. It includes information about the host on which the VM is running, as well as detailed snapshot metrics like the number of snapshots, creation dates, and descriptions. Additionally, it provides insights into associated hosts and datastores.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77c54f960b7de840/6a7f1b699090b0e39084ee31/virtualmachine.png" alt="Virtual Machine Datastream" /></p>
<ul>
<li><p><strong>Datastore Datastream:</strong> This datastream provides information on the total, used, and available capacity of datastores, along with their overall status. It also captures metrics such as the average read/write rate and lists the hosts and virtual machines connected to each datastore.</p></li>
<li><p><strong>Datastore Cluster:</strong> A datastore cluster in vSphere is a collection of datastores grouped together for efficient storage management. This datastream provides details on the total capacity and free space in the storage pod, along with the list of datastores within the cluster.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt065c647cb8e5d02f/6a7f1b6dc2e914310301700a/datastore.png" alt="Datastore Datastream" /></p>
<ul>
<li><p><strong>Resource Pool:</strong> Resource pools in vSphere serve as logical abstractions that allow flexible allocation of CPU and memory resources. This datastream captures memory metrics, including swapped, ballooned, and shared memory, as well as CPU metrics like distributed and static CPU entitlement. It also lists the virtual machines associated with each resource pool.</p></li>
<li><p><strong>Network Datastream:</strong> This datastream captures the overall configuration and status of the network, including network types (e.g., vSS, vDS). It also lists the hosts and virtual machines connected to each network.</p></li>
<li><p><strong>Cluster Datastream:</strong> A Cluster in vSphere is a collection of ESXi hosts and their associated virtual machines that function as a unified resource pool. Clustering in vSphere allows administrators to manage multiple hosts and resources centrally, providing high availability, load balancing, and scalability to the virtual environment. This datastream includes metrics indicating whether HA or admission control is enabled and lists the hosts, networks, and datastores associated with the cluster.</p></li>
</ul>
<h2 id="alarmssupportinvsphereintegration">Alarms support in vSphere Integration</h2>
<p>Alarms are a vital part of the vSphere integration, providing real-time insights into critical events across your virtual environment. In the updated Elastic’s vSphere integration, alarms are now reported for all the entities. They include detailed information such as the alarm name, description, severity (e.g., critical or warning), affected entity, and triggered time. These alarms are seamlessly integrated into datastreams, helping administrators and SREs quickly identify and resolve issues like resource shortages or performance bottlenecks.</p>
<h4 id="examplealarm">Example Alarm</h4>
<pre><code>"triggered_alarms": [
  {
    "description": "Default alarm to monitor host memory usage",
    "entity_name": "host_us",
    "id": "alarm-4.host-12",
    "name": "Host memory usage",
    "status": "red",
    "triggered_time": "2024-08-28T10:31:26.621Z"
  }
]
</code></pre>
<p>This example highlights a triggered alarm for monitoring host memory usage, indicating a critical status (red) for the host "host_us." Such alarms empower teams to act swiftly and maintain the stability of their vSphere environment. </p>
<h2 id="letstryitout">Lets Try It Out!</h2>
<p>The new <a href="https://www.elastic.co/docs/current/integrations/vsphere">vSphere integration</a> in Elastic Cloud is more than just a monitoring tool; it’s a comprehensive solution that empowers you to manage and optimize your virtual environments effectively. With deeper insights and enhanced data granularity, you can ensure high availability, improved load balancing, and smarter resource allocation. Spin up an Elastic Cloud, and start monitoring your vSphere infrastructure.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/supercharge-your-vsphere-monitoring-with-enhanced-vsphere-integration</link>
    <guid isPermaLink="false">supercharge-your-vsphere-monitoring-with-enhanced-vsphere-integration</guid>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Ishleen Kaur,Lalit Satapathy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc78bdc85ffd6827/6a7f1b6fe02fac3fe55d69cf/title.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 11 Dec 2024 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[Adding free and open Elastic APM as part of your Elastic Observability deployment]]></title>
    <description><![CDATA[Learn how to gather application trace data and store it alongside the logs and metrics from your applications and infrastructure with Elastic Observability and Elastic APM.]]></description>
    <content:encoded><![CDATA[<p>In a recent post, we showed you <a href="https://www.elastic.co/blog/getting-started-with-free-and-open-elastic-observability">how to get started with the free and open tier of Elastic Observability</a>. Below, we'll walk through what you need to do to expand your deployment so you can start gathering metrics from application performance monitoring (APM) or "tracing" data in your observability cluster, for free.</p>
<h2 id="whatisapm">What is APM?</h2>
<p>Application performance monitoring lets you see where your applications spend their time, what they are doing, what other applications or services they are calling, and what errors or exceptions they are encountering.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75d1516cb178adb6/6a85c74c501a8561c6fbb28a/screenshot-serverless-distributed-trace.png" alt="" /></p>
<p>In addition, APM also lets you see history and trends for key performance indicators, such as latency and throughput, as well as transaction and dependency information:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt886c0c626ef5bb89/6a85c74f80984c6d8e668f32/ruby-overview.png" alt="" /></p>
<p>Whether you're setting up alerts for SLA breaches, trying to gauge the impact of your latest release, or deciding where to make the next improvement, APM can help with your root-cause analysis to help improve your users' experience and drive your mean time to resolution (MTTR) toward zero.</p>
<h2 id="logicalarchitecture">Logical architecture</h2>
<p>Elastic APM relies on the APM Integration inside Elastic Agent, which forwards application trace and metric data from applications instrumented with APM agents to an Elastic Observability cluster. Elastic APM supports multiple agent flavors:</p>
<ul>
<li>Native Elastic APM Agents, available for <a href="https://www.elastic.co/guide/en/apm/agent/index.html">multiple languages</a>, including Java, .NET, Go, Ruby, Python, Node.js, PHP, and client-side JavaScript</li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/get-started/current/open-telemetry-elastic.html">OpenTelemetry</a></li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/get-started/current/opentracing.html">OpenTracing</a></li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/server/current/jaeger.html">Jaeger</a></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0218d3e17c29a3b2/6a85c7515c27902af0f59a71/blog-elastic-observability-instrumented-services.png" alt="" /></p>
<p>In this blog, we'll provide a quick example of how to instrument code with the native Elastic APM Python agent, but the overall steps are similar for other languages.</p>
<p>Please note that there is a strong distinction between the <strong>Elastic APM Agent</strong> and the <strong>Elastic Agent</strong>. These are very different components, as you can see in the diagram above, so it's important not to confuse them.</p>
<h2 id="installtheelasticagent">Install the Elastic Agent</h2>
<p>The first step is to install the Elastic Agent. You either need Fleet <a href="https://www.elastic.co/guide/en/fleet/current/add-a-fleet-server.html">installed first</a>, or you can install the Elastic Agent standalone. Install the Elastic Agent somewhere by <a href="https://www.elastic.co/guide/en/fleet/master/elastic-agent-installation.html">following this guide</a>. This will give you an APM Integration endpoint you can hit. Note that this step is not necessary in Elastic Cloud, as we host the APM Integration for you. Check Elastic Agent is up by running:</p>
<pre><code>curl &lt;ELASTIC_AGENT_HOSTNAME&gt;:8200
</code></pre>
<h2 id="instrumentingsamplecodewithanelasticapmagent">Instrumenting sample code with an Elastic APM agent</h2>
<p>The instructions for the various language agents differ based on the programming language, but at a high level they have a similar flow. First, you add the dependency for the agent in the language's native spec, then you configure the agent to let it know how to find the APM Integration.</p>
<p>You can try out any flavor you'd like, but I am going to walk through the Python instructions using this Python example that <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample">I created</a>.</p>
<h3 id="getthesamplecodeoruseyourown">Get the sample code (or use your own)</h3>
<p>To get started, I clone the GitHub repository then change to the directory:</p>
<pre><code>git clone https://github.com/davidgeorgehope/PythonElasticAPMExample
cd PythonElasticAPMExample
</code></pre>
<h3 id="howtoaddthedependency">How to add the dependency</h3>
<p>Adding the Elastic APM Dependency is simple — check the app.py file from <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample/blob/main/app.py">the github repo</a> and you will notice the following lines of code.</p>
<pre><code>import elasticapm
from elasticapm import Client

app = Flask(__name__)
app.config["ELASTIC_APM"] = {    "SERVICE_NAME": os.environ.get("APM_SERVICE_NAME", "flask-app"),    "SECRET_TOKEN": os.environ.get("APM_SECRET_TOKEN", ""),    "SERVER_URL": os.environ.get("APM_SERVER_URL", "http://localhost:8200"),}
elasticapm.instrumentation.control.instrument()
client = Client(app.config["ELASTIC_APM"])
</code></pre>
<p>The Python library for Flask is capable of auto detecting transactions, but you can also start transactions in code as per the following, as we have done in this example:</p>
<pre><code>@app.route("/")
def hello():
    client.begin_transaction('demo-transaction')
    client.end_transaction('demo-transaction', 'success')
</code></pre>
<h3 id="configuretheagent">Configure the agent</h3>
<p>The agents need to send application trace data to the APM Integration, and to do this it has to be reachable. I configured the Elastic Agent to listen on my local host's IP, so anything in my subnet can send data to it. As you can see from the code below, we use docker-compose.yml to pass in the config via environment variables. Please edit these variables for your own Elastic installation.</p>
<pre><code># docker-compose.yml
version: "3.9"
services:
  flask_app:
    build: .
    ports:
      - "5001:5001"
    environment:
      - PORT=5001
      - APM_SERVICE_NAME=flask-app
      - APM_SECRET_TOKEN=your_secret_token
      - APM_SERVER_URL=http://host.docker.internal:8200
</code></pre>
<p>Some commentary on the above:</p>
<ul>
<li><strong>service_name:</strong> If you leave this out it will just default to the application's name, but you can override that here.</li>
<li><strong>secret_token:</strong> <a href="https://www.elastic.co/guide/en/apm/server/current/secret-token.html">Secret tokens</a> allow you to authorize requests to the APM Server, but they require that the APM Server is set up with SSL/TLS and that a secret token has been set up. We're not using HTTPS between the agents and the APM Server, so we'll comment this one out.</li>
<li><strong>server_url:</strong> This is how the agent can reach the APM Integration inside Elastic Agent. Replace this with the name or IP of your host running Elastic Agent.</li>
</ul>
<p>Now that the Elastic APM side of the configuration is done, we simply follow the steps from the <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample/blob/main/README.md">README</a> to start up.</p>
<pre><code>docker-compose up --build -d
</code></pre>
<p>The build step will take several minutes.</p>
<p>You can navigate to the running sample application by visiting http://localhost:5001. There's not a lot to the sample, but it does generate some APM data. To generate a bit of a load, you can reload them a few times or run a quick little script:</p>
<pre><code>#!/bin/bash
# load_test.sh
url="http://localhost:5001"
for i in {1..1000}
do
  curl -s -o /dev/null $url
  sleep 1
done
</code></pre>
<p>This will just reload the pages every second.</p>
<p>Back in Kibana, navigate back to the APM app (hamburger icon, then select <strong>APM</strong> ) and you should see our new flask-app service (I let mine run so it shows a bit more history):</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0b22219b36a9336/6a85c7549d2b718e27f938c4/blog-elastic-observability-services.png" alt="" /></p>
<p>The Service Overview page provides an at-a-glance summary of the health of a service in one place. If you're a developer or an SRE, this is the page that will help you answer questions like:</p>
<ul>
<li>How did a new deployment impact performance?</li>
<li>What are the top impacted transactions?</li>
<li>How does performance correlate with underlying infrastructure?</li>
</ul>
<p>This view provides a list of all of the applications that have sent application trace data to Elastic APM in the specified period of time (in this case, the last 15 minutes). There are also sparklines showing mini graphs of latency, throughput, and error rate. Clicking on <strong>flask-app</strong> takes us to the <strong>service overview</strong> page, which shows the various transactions within the service (recall that my script is hitting the / endpoint, as seen in the <strong>Transactions</strong> section). We get bigger graphs for <strong>Latency</strong> , <strong>Throughput</strong> , <strong>Errors</strong> , and <strong>Error Rates</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d432bbc41e408db/6a85c75768266682891eab66/blog-elastic-observability-flask-app.png" alt="" /></p>
<p>When you're instrumenting real applications, under real load, you'll see a lot more connectivity (and errors!)</p>
<p>Clicking on a transaction in the transaction view, in this case, our sample app's demo-transaction transaction, we can see exactly what operations were called:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb4f05eae471a702c/6a85c75a342d69fd7c21b03f/blog-elastic-observability-flask-app-demo-transaction.png" alt="" /></p>
<p>This includes detailed information about calls to external services, such as database queries:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt897018765d4cb3af/6a85c75d342d69678e21b043/blog-elastic-observability-span-details.png" alt="" /></p>
<h2 id="whatsnext">What's next?</h2>
<p>Now that you've got your Elastic Observability cluster up and running and collecting out-of-the-box application trace data, explore the public APIs for the languages that your applications are using, which allow you to take your APM data to the next level. The APIs allow you to add custom metadata, define business transactions, create custom spans, and more. You can find the public API specs for the various APM agents (such as <a href="https://www.elastic.co/guide/en/apm/agent/java/current/public-api.html">Java</a>, <a href="https://www.elastic.co/guide/en/apm/agent/ruby/current/api.html">Ruby</a>, <a href="https://www.elastic.co/guide/en/apm/agent/python/current/index.html">Python</a>, and more) on the APM agent <a href="https://www.elastic.co/guide/en/apm/agent/index.html">documentation pages</a>.</p>
<p>If you'd like to learn more about Elastic APM, check out <a href="https://www.elastic.co/webinars/introduction-to-elastic-apm-in-the-shift-to-cloud-native">our webinar on Elastic APM in the shift to cloud native</a> to see other ways that Elastic APM can help you in your ecosystem.</p>
<p>If you decide that you'd rather have us host your observability cluster, you can sign up for a free trial of the <a href="https://www.elastic.co/cloud/">Elasticsearch Service on Elastic Cloud</a> and change your agents to point to your new cluster.</p>
<p><em>Originally published May 5, 2021; updated April 6, 2023.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/free-open-elastic-apm-observability-deployment</link>
    <guid isPermaLink="false">free-open-elastic-apm-observability-deployment</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8a4183daa602b2a/6a85c760bc5bb342fdf81a2d/blog-thumb-release-apm.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 Feb 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[Elastic Observability monitors metrics for Microsoft Azure in just minutes]]></title>
    <description><![CDATA[Follow this step-by-step process to enable Elastic Observability for Microsoft Azure metrics.]]></description>
    <content:encoded><![CDATA[<p>Developers and SREs choose Microsoft Azure to run their applications because it is a trustworthy world-class cloud platform. It has also proven itself over the years as an extremely powerful and reliable infrastructure for hosting business-critical applications.</p>
<p>Elastic Observability offers over 25 out-of-the-box integrations for Microsoft Azure services with more on the way. A full list of Azure integrations can be found in <a href="https://docs.elastic.co/integrations/azure">our online documentation</a>.</p>
<p>Elastic Observability aggregates not only logs but also metrics for Azure services and the applications running on Azure compute services (Virtual Machines, Functions, Kubernetes Service, etc.). All this data can be analyzed visually and more intuitively using Elastic®’s advanced machine learning (ML) capabilities, which help detect performance issues and surface root causes before end users are affected.</p>
<p>For more details on how Elastic Observability provides application performance monitoring (APM) capabilities such as service maps, tracing, dependencies, and ML-based metrics correlations, read <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">APM correlations in Elastic Observability: Automatically identifying probable causes of slow or failed transactions</a>.</p>
<p>That’s right, Elastic offers capabilities to collect, aggregate, and analyze metrics for Microsoft Azure services and applications running on Azure. Elastic Observability is for more than just capturing logs — it offers a unified observability solution for Microsoft Azure workloads.</p>
<p>In this blog, we’ll review how Elastic Observability can monitor metrics for a three-tier web application running on Microsoft Azure and leveraging:</p>
<ul>
<li>Microsoft Azure Virtual Machines</li>
<li>Microsoft Azure SQL database</li>
<li>Microsoft Azure Virtual Network</li>
</ul>
<p>As you will see, once the integration is installed, metrics will arrive instantly and you can immediately start deriving insights from metrics.</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>Here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li>Ensure you have a Microsoft Azure account and an Azure service principal with permission to read monitoring data from Microsoft Azure (<a href="https://docs.elastic.co/integrations/azure_metrics/monitor#integration-specific-configuration-notes">see details in our documentation</a>).</li>
<li>This post does <em>not</em> cover application monitoring; instead, we will focus on how Microsoft Azure services can be easily monitored. If you want to get started with examples of application monitoring, see our <a href="https://github.com/elastic/observability-examples/tree/main/azure/container-apps">Hello World observability code samples</a>.</li>
<li>In order to see metrics, you will need to load the application. We’ve also created a Playwright script to drive traffic to the application.</li>
</ul>
<h2 id="threetierapplicationoverview">Three-tier application overview</h2>
<p>Before we dive into the Elastic deployment setup and configuration, let's review what we are monitoring. If you follow the <a href="https://learn.microsoft.com/en-us/training/modules/n-tier-architecture/">Microsoft Learn N-tier example app</a> instructions for deploying the "What's for Lunch?" app, you will have the following deployed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78865baac7b2a393/6a85c9fb331d7a5460c3177f/blog-elastic-three-tier-application-overview.png" alt="three tier application overview" /></p>
<p>What’s deployed:</p>
<ul>
<li>Microsoft Azure VM presentation tier that renders an HTML client in the user's browser and enables user requests to be sent to the “What’s for Lunch?” app</li>
<li>Microsoft Azure VM application tier that communicates with the presentation and the database tier</li>
<li>Microsoft Azure SQL instance in the database tier, handling requests from the application tier to store and serve data</li>
</ul>
<p>At the end of the blog, we will also provide a Playwright script that can be run to send requests to this app in order to load it with example data and exercise its functionality. This will help drive metrics to “light up” the dashboards.</p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Let’s walk through the details of how to deploy the example three-tier application, Azure integration on Elastic and visualize what gets ingested in Elastic’s Kibana® dashboards.</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">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb1b5998e0b8cc454/6a85c9fd5c27905315f59b07/blog-elastic-free-trial.png" alt="elastic cloud free trial sign up" /></p>
<h3 id="step1deploythemicrosoftazurethreetierapplication">Step 1: Deploy the Microsoft Azure three-tier application</h3>
<p>From the <a href="https://portal.azure.com/">Azure portal</a>, click the Cloud Shell icon at the top of the portal to open Cloud Shell…</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5fc8d120fc905c77/6a85ca00bc5bb3d25af81ad9/blog-elastic-open-cloud-shell.png" alt="open cloud shell" /></p>
<p>… and when the Cloud Shell first opens, select <strong>Bash</strong> as the shell type to use.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaaf43e6f0672dc9b/6a85ca03ba7acc9689992130/blog-elastic-cloud-shell-bash.png" alt="cloud shell bash" /></p>
<p>If you’re prompted that “You have no storage mounted,” then click the <strong>Create storage</strong> button to create a file store to be used for saving and editing files from Cloud Shell.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24691c80db0ce204/6a85ca06331d7aa36bc31783/blog-elastic-create-storage.png" alt="cloud shell create storage" /></p>
<p>You should now see the open Cloud Shell terminal.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4dc12c5d67ebb249/6a85ca0998292672295838ca/blog-elastic-cloud-shell-terminal.png" alt="cloud shell terminal" /></p>
<p>Run the following command in Cloud Shell to define the environment variables that we’ll be using in the Cloud Shell commands required to deploy and view the sample application.</p>
<p>Be sure to specify a valid RESOURCE_GROUP from your available <a href="https://portal.azure.com/#view/HubsExtension/BrowseResourceGroups">Resource Groups listed in the Azure portal</a>. Also specify a new password to replace the SpecifyNewPasswordHere placeholder text before running the command. See the Microsoft <a href="https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16#password-complexity">password policy documentation</a> for password requirements.</p>
<pre><code>RESOURCE_GROUP="test"
APP_PASSWORD="SpecifyNewPasswordHere"
</code></pre>
<p>Run the following az deployment group create command, which will deploy the example three-tier web app in around five minutes.</p>
<pre><code>az deployment group create --resource-group $RESOURCE_GROUP --template-uri https://raw.githubusercontent.com/MicrosoftDocs/mslearn-n-tier-architecture/master/Deployment/azuredeploy.json --parameters password=$APP_PASSWORD
</code></pre>
<p>After the deployment has completed, run the following command, which returns the URL for the app.</p>
<pre><code>az deployment group show --output table --resource-group $RESOURCE_GROUP --name azuredeploy --query properties.outputs.webSiteUrl
</code></pre>
<p>Copy the web app URL and paste it into a browser to view the example “What’s for Lunch?” web app.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte72de03d59114cc0/6a85ca0beaf2457371a49f49/blog-elastic-whats-for-lunch.png" alt="whats for lunch app" /></p>
<h3 id="step2createanazureserviceprincipalandgrantaccesspermission">Step 2: Create an Azure service principal and grant access permission</h3>
<p>Go to the <a href="https://portal.azure.com/">Microsoft Azure Portal</a>. Search for active directory and select <strong>Microsoft Entra ID</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b89c41ce941474c/6a85ca0ef61d6e00729c2afb/blog-elastic-active-directory.png" alt="search active directory" /></p>
<p>Copy the <strong>Tenant ID</strong> for use in a later step in this blog post. This ID is required to configure Elastic Agent to connect to your Azure account.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc67ef8fd47a07daf/6a85ca1168266642671eabe7/blog-elastic-your-organization-overview.png" alt="your organization overview" /></p>
<p>In the navigation pane, select <strong>App registrations</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63caee3d530d8fb6/6a85ca1480984c3ea4668fba/blog-elastic-your-organization-overview-app-registrations.png" alt="your organization overview app registrations" /></p>
<p>Then click <strong>New registration</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28d2ae4600d587ab/6a85ca171aa1e1c669ff8d47/blog-elastic-your-organization-new-registration.png" alt="your organization new registrations" /></p>
<p>Type the name of your application (this tutorial uses three-tier-app-azure) and click <strong>Register</strong> (accept the default values for other settings).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b60e56405060b44/6a85ca1a11893cefaaa7ab7c/blog-elastic-register_an_application.png" alt="register an application" /></p>
<p>Copy the <strong>Application (client) ID</strong> and save it for later. This ID is required to configure Elastic Agent to connect to your Azure account.</p>
<p>In the navigation pane, select <strong>Certificates &amp; secrets</strong> , and then click <strong>New client secret</strong> to create a new security key.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a42c8cd0b462f5d/6a85ca1df5f1a033fb2ec8df/blog-elastic-three-tier-app-new-client-secret.png" alt="three tier app new client secret" /></p>
<p>Type a description of the secret and select an expiration. Click <strong>Add</strong> to create the client secret. Under <strong>Value</strong> , copy the secret value and save it (along with your client ID) for later.</p>
<p>After creating the Azure service principal, you need to grant it the correct permissions. In the Azure Portal, search for and select <strong>Subscriptions</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27de6595abfe44ac/6a85ca1fabdc2927991224d6/blog-elastic-three-tier-subscriptions.png" alt="three tier subscriptions" /></p>
<p>In the Subscriptions page, click the name of your subscription. On the subscription details page, copy your <strong>Subscription ID</strong> and save it for a later step.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc969b5a5eecfbb8e/6a85ca2293ffb98248b9142b/blog-elastic-subscription-essentials-copy.png" alt="subscription essentials copy" /></p>
<p>In the navigation pane, select <strong>Access control (IAM)</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcc0d6f8281122752/6a85ca259a32f15cbfa7dfd0/blog-elastic-subscription-access-control.png" alt="subscription access control" /></p>
<p>Click <strong>Add</strong> and select <strong>Add role assignment</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b3b8d7bdf3f141e/6a85ca2807829039d0321752/blog-elastic-subscription-access-control-add-role-assignment.png" alt="subscription access control add role assignment" /></p>
<p>On the <strong>Role</strong> tab, select the <strong>Monitoring Reader</strong> role and then click <strong>Next</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99f68cb7713f8205/6a85ca2b27c5cd03ec5f73d8/blog-elastic-add-role-assignment-monitoring-readers.png" alt="add role assignment monitoring reader" /></p>
<p>On the <strong>Members</strong> tab, select the option to assign access to <strong>User, group, or service principal</strong>. Click <strong>Select members</strong> , and then search for and select the principal you created earlier. For the description, enter the name of your service principal. Click <strong>Next</strong> to review the role assignment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ac85a836aeaa428/6a85ca2ef9373d43ad96f584/blog-elastic-add-role-assignment-description.png" alt="add role assignment description" /></p>
<p>Click <strong>Review + assign</strong> to grant the service principal access to your subscription.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d1eb5fc55533145/6a85ca30abdc2963671224da/blog-elastic-add-role-assignment-review-assign.png" alt="add role assignment review assign" /></p>
<h3 id="step3createanazurevminstance">Step 3: Create an Azure VM instance</h3>
<p>In the Azure Portal, search for and select <strong>Virtual machines</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6df66ab8fe5350d7/6a85ca3311893cc69ba7ab80/blog-elastic-search-virtual-machines.png" alt="search virtual machines" /></p>
<p>On the <strong>Virtual machines</strong> page, click <strong>+ Create</strong> and select <strong>Azure virtual machine</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ee0dafa647fa8fe/6a85ca36bc5bb3bd24f81ae1/blog-elastic-azure-virtual-machine.png" alt="azure virtual machine" /></p>
<p>On the Virtual machine creation page, enter a name like “metrics-vm” for the virtual machine name and select VM Size to be “Standard_D2s_v3 - 2 vcpus, 8 GiB memory.” Click the <strong>Next : Disks</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt087222cdca1f2bf1/6a85ca3943c0b7ea592f05fc/blog-elastic-create-virtual-macine-next-disks.png" alt="create a virtual machine next disks" /></p>
<p>On the <strong>Disks</strong> page, keep the default settings and click the <strong>Next : Networking</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c10808899e56ad5/6a85ca3c4710c6e42ad3cb33/blog-elastic-create-virtual-machine-next-networking.png" alt="create a virtual machine next networking" /></p>
<p>On the <strong>Networking</strong> page, demo-vnet should be selected for <strong>Virtual network</strong> and demo-biz-subnet should be selected for <strong>Subnet</strong>. These resources are created as part of the three-tier example app’s deployment that was done in Step 1.</p>
<p>Click the <strong>Review + create</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaa7213850eedec1e/6a85ca405c2790854bf59b0b/blog-elastic-create-virtual-machine-review-create.png" alt="create virtual machine review create" /></p>
<p>On the <strong>Review</strong> page, click the <strong>Create</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49cea7999c6d0609/6a85ca4327c5cd23e75f73de/blog-elastic-create-virtual-machine-validation-passed.png" alt="create virtual machine validation passed" /></p>
<h3 id="step4installtheazureresourcemetricsintegration">Step 4: Install the Azure Resource Metrics integration</h3>
<p>In your <a href="https://cloud.elastic.co/home">Elastic Cloud</a> deployment, navigate to the Elastic Azure integrations by selecting <strong>Integrations</strong> from the top-level menu. Search for azure resource and click the <strong>Azure Resource Metrics</strong> tile.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2a117bba617a23a/6a85ca46ba7acce765992136/blog-elastic-integrations-azure-resource-metrics.png" alt="integrations azure resource metrics" /></p>
<p>Click <strong>Add Azure Resource Metrics.</strong></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88cb6bba93e24136/6a85ca4c342d69099a21b0d7/blog-elastic-azure-resource-metrics.png" alt="azure resource metrics" /></p>
<p>Click <strong>Add integration only (skip agent installation)</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92fcc0204f8e2c6f/6a85ca4f80984c32d3668fc8/blog-elastic-add-integration-only.png" alt="add integration only" /></p>
<p>Enter the values that you saved previously for Client ID, Client Secret, Tenant ID, and Subscription ID.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a0db24f97f4278c/6a85ca53eaf24562dea49f51/blog-elastic-add-azure-resource-metrics-integration.png" alt="add azure resource metrics integration" /></p>
<p>As you can see, the Azure Resource Metrics integration will collect a significant amount of data from eight Azure services. Click <strong>Save and continue</strong>.</p>
<p>You’ll be presented with a confirmation dialog window. Click <strong>Add Elastic Agent to your hosts</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e7144de0af25985/6a85ca57bc5bb3a239f81ae5/blog-elastic-azure-resource-metrics-integration-added.png" alt="azure resource metrics integration added" /></p>
<p>This will display the instructions required to install the Elastic agent. Copy the command under the <strong>Linux Tar</strong> tab.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20087af321e53897/6a85ca5c11893cf7cba7ab86/blog-elastic-add-agent.png" alt="add agent linux tar" /></p>
<p>Next you will need to use SSH to log in to the Azure VM instance and run the commands copied from <strong>Linux Tar</strong> tab. Go to <a href="https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Compute/VirtualMachines">Azure Virtual Machines</a> in the Azure portal. Then click the name of the VM instance that you created in Step 3.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30bf828236f78a33/6a85ca60d7b2e7f92dfe84c8/blog-elastic-metrics-vm.png" alt="metrics vm" /></p>
<p>Click the <strong>Select</strong> button in the <strong>SSH Using Azure CLI</strong> section.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cba533db9a395fc/6a85ca6433f244843549f4fa/blog-elastic-metrics-vm-connect.png" alt="metrics vm connect" /></p>
<p>Select the “I understand …” checkbox and then click the <strong>Configure + connect</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b7633d6cff018a8/6a85ca6943c0b707022f0604/blog-elastic-ssh-using-azure-cli.png" alt="ssh using azure cli" /></p>
<p>Once you are SSH’d inside the VM instance terminal window, run the commands copied previously from <strong>Linux Tar tab</strong> in the <strong>Install Elastic Agent on your host</strong> instructions. When the installation completes, you’ll see a confirmation message in the Install Elastic Agent on your host form.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0af6bd380836ec18/6a85ca6c27c5cdc79b5f73e8/blog-elastic-add-agent-confirmed.png" alt="add agent confirmed" /></p>
<p>Super! The Elastic agent is sending data to Elastic Cloud. Now let’s observe some metrics.</p>
<h3 id="step5runtrafficagainsttheapplication">Step 5: Run traffic against the application</h3>
<p>While getting the application running is fairly easy, there is nothing to monitor or observe with Elastic unless you add a load on the application.</p>
<p>Here is a simple script you can also run using <a href="https://playwright.dev/">Playwright</a> to add traffic and exercise the functionality of the Azure three-tier application:</p>
<pre><code>import { test, expect } from "@playwright/test";

test("homepage for Microsoft Azure three tier app", async ({ page }) =&gt; {
  // Load web app
  await page.goto("http://20.172.198.231/");
  // Add lunch suggestions
  await page.fill("id=txtAdd", "tacos");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=txtAdd", "sushi");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=txtAdd", "pizza");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=txtAdd", "burgers");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=txtAdd", "salad");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=txtAdd", "sandwiches");
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  // Click vote buttons
  await page.getByRole("button").nth(1).click();
  await page.getByRole("button").nth(3).click();
  await page.getByRole("button").nth(5).click();
  await page.getByRole("button").nth(7).click();
  await page.getByRole("button").nth(9).click();
  await page.getByRole("button").nth(11).click();
  // Click remove buttons
  await page.getByRole("button").nth(12).click();
  await page.getByRole("button").nth(10).click();
  await page.getByRole("button").nth(8).click();
  await page.getByRole("button").nth(6).click();
  await page.getByRole("button").nth(4).click();
  await page.getByRole("button").nth(2).click();
});
</code></pre>
<h3 id="step6viewazuredashboardsinelastic">Step 6: View Azure dashboards in Elastic</h3>
<p>With Elastic Agent running, you can go to Elastic Dashboards to view what’s being ingested. Simply search for “dashboard” in Elastic and choose <strong>Dashboard</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33b70441f8217fae/6a85ca6f1aa1e10ef4ff8d4d/blog-elastic-dashboard.png" alt="dashboard" /></p>
<p>This will open the Elastic Dashboards page. In the Dashboards search box, search for azure vm and click the <strong>[Azure Metrics] Compute VMs Overview</strong> dashboard, one of the many out-of-the-box dashboards available.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta63ce646f43a10c7/6a85ca73f5f1a021832ec8eb/blog-elastic-dashboards-create.png" alt="dashboards create" /></p>
<p>You will see a Dashboard populated with your deployed application’s VM metrics.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdacd5a541976c209/6a85ca779d2b71018ff9396f/blog-elastic-azure-compute-vm.png" alt="azure compute vm" /></p>
<p>On the Azure Compute VM dashboard, we can see the following sampling of some of the many available metrics:</p>
<ul>
<li>CPU utilization</li>
<li>Available memory</li>
<li>Network sent and received bytes</li>
<li>Disk writes and reads metrics</li>
</ul>
<p>For metrics not covered by out-of-the-box dashboards, custom dashboards can be easily created to visualize metrics that are important to you.</p>
<p><strong>Congratulations, you have now started monitoring metrics from Microsoft Azure services for your application!</strong></p>
<h2 id="analyzeyourdatawithelasticaiassistant">Analyze your data with Elastic AI Assistant</h2>
<p>Once metrics and logs (or either one) are in Elastic, start analyzing your data with <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">context-aware insights using the Elastic AI Assistant for Observability</a>.</p>
<h2 id="conclusionmonitoringmicrosoftazureservicemetricswithelasticobservabilityiseasy">Conclusion: Monitoring Microsoft Azure service metrics with Elastic Observability is easy!</h2>
<p>We hope you’ve gotten an appreciation for how Elastic Observability can help you monitor Azure service metrics. Here’s a quick recap of what you learned:</p>
<ul>
<li>Elastic Observability supports ingest and analysis of Azure service metrics.</li>
<li>It’s easy to set up ingest from Azure services via the Elastic Agent.</li>
<li>Elastic Observability has multiple out-of-the-box Azure service dashboards you can use to preliminarily review information and then modify for your needs.</li>
</ul>
<p>Try it out for yourself by signing up via <a href="https://portal.azure.com/#view/Microsoft_Azure_Marketplace/GalleryItemDetailsBladeNopdl/id/elastic.ec-azure-pp">Microsoft Azure 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_azure_regions">Elastic Cloud regions on Microsoft Azure</a> around the world. Your Azure Marketplace purchase of Elastic will be included in your monthly consolidated billing statement and will draw against your committed spend with Microsoft Azure.</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/observability-monitors-metrics-microsoft-azure</link>
    <guid isPermaLink="false">observability-monitors-metrics-microsoft-azure</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Jonathan Simon,Hemant Malik]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87fce3f72364a189/6a85ca7bf61d6eabbc9c2b01/Azure_Dark_(1).png" length="0" type="image/png"/>
    <pubDate>Mon, 29 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Observability monitors metrics for Google Cloud in just minutes]]></title>
    <description><![CDATA[Follow this step-by-step process to enable Elastic Observability for Google Cloud Platform metrics.]]></description>
    <content:encoded><![CDATA[<p>Developers and SREs choose to host their applications on Google Cloud Platform (GCP) for its reliability, speed, and ease of use. On Google Cloud, development teams are finding additional value in migrating to Kubernetes on GKE, leveraging the latest serverless options like Cloud Run, and improving traditional, tiered applications with managed services.</p>
<p>Elastic Observability offers 16 out-of-the-box integrations for Google Cloud services with more on the way. A full list of Google Cloud integrations can be found in <a href="https://docs.elastic.co/en/integrations/gcp">our online documentation</a>.</p>
<p>In addition to our native Google Cloud integrations, Elastic Observability aggregates not only logs but also metrics for Google Cloud services and the applications running on Google Cloud compute services (Compute Engine, Cloud Run, Cloud Functions, Kubernetes Engine). All this data can be analyzed visually and more intuitively using Elastic®’s advanced machine learning (ML) capabilities, which help detect performance issues and surface root causes before end users are affected.</p>
<p>For more details on how Elastic Observability provides application performance monitoring (APM) capabilities such as service maps, tracing, dependencies, and ML based metrics correlations, read: <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">APM correlations in Elastic Observability: Automatically identifying probable causes of slow or failed transactions</a>.</p>
<p>That’s right, Elastic offers metrics ingest, aggregation, and analysis for Google Cloud services and applications on Google Cloud compute services. Elastic is more than logs — it offers a unified observability solution for Google Cloud environments.</p>
<p>In this blog, I’ll review how Elastic Observability can monitor metrics for a three-tier web application running on Google Cloud services, which include:</p>
<ul>
<li>Google Cloud Run</li>
<li>Google Cloud SQL for PostgreSQL</li>
<li>Google Cloud Memorystore for Redis</li>
<li>Google Cloud VPC Network</li>
</ul>
<p>As you will see, once the integration is installed, metrics will arrive instantly and you can immediately start reviewing metrics.</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>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>Ensure you have a Google Cloud project and a Service Account with permissions to pull the necessary data from Google Cloud (<a href="https://docs.elastic.co/en/integrations/gcp#authentication">see details in our documentation</a>).</li>
<li>We used <a href="https://cloud.google.com/architecture/application-development/three-tier-web-app">Google Cloud’s three-tier app</a> and deployed it using the Google Cloud console.</li>
<li>We’ll walk through installing the general <a href="https://docs.elastic.co/en/integrations/gcp">Elastic Google Cloud Platform Integration</a>, which covers the services we want to collect metrics for.</li>
<li>We will <em>not</em> cover application monitoring; instead, we will focus on how Google Cloud services can be easily monitored.</li>
<li>In order to see metrics, you will need to load the application. We’ve also created a playwright script to drive traffic to the application.</li>
</ul>
<h2 id="threetierapplicationoverview">Three-tier application overview</h2>
<p>Before we dive into the Elastic configuration, let's review what we are monitoring. If you follow the <a href="https://cloud.google.com/architecture/application-development/three-tier-web-app">Jump Start Solution: Three-tier web app</a> instructions for<a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop"></a>deploying the task-tracking app, you will have the following deployed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfdfc1ac184579a1b/6a85c99ff5f1a04ffa2ec8cd/1.png" alt="1" /></p>
<p>What’s deployed:</p>
<ul>
<li>Cloud Run frontend tier that renders an HTML client in the user's browser and enables user requests to be sent to the task-tracking app</li>
<li>Cloud Run middle tier API layer that communicates with the frontend and the database tier</li>
<li>Memorystore for Redis instance in the database tier, caching and serving data that is read frequently</li>
<li>Cloud SQL for PostgreSQL instance in the database tier, handling requests that can't be served from the in-memory Redis cache</li>
</ul>
<p>At the end of the blog, we will also provide a Playwright script that can be run to send requests to this app in order to load it with example data and exercise its functionality. This will help drive metrics to “light up” the dashboards.</p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Let’s walk through the details of how to get the application, Google Cloud integration on Elastic, and what gets ingested.</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/blt95cfd5da2363e653/6a85c9a227c5cd311b5f73ca/2.png" alt="2 - start free trial" /></p>
<h3 id="step1deploythegooglecloudthreetierapplication">Step 1: Deploy the Google Cloud three-tier application</h3>
<p>Follow the instructions listed out in <a href="https://cloud.google.com/architecture/application-development/three-tier-web-app">Jump Start Solution: Three-tier web app</a> choosing the <strong>Deploy through the console</strong> option for deployment.</p>
<h3 id="step2createagooglecloudserviceaccountanddownloadcredentialsfile">Step 2: Create a Google Cloud Service Account and download credentials file</h3>
<p>Once you’ve installed the app, the next step is to create a <em>Service Account</em> with a <em>Role</em> and a <em>Service Account Key</em> that will be used by Elastic’s integration to access data in your Google Cloud project.</p>
<p>Go to Google Cloud <a href="https://console.cloud.google.com/iam-admin/roles">IAM Roles</a> to create a Role with the necessary permissions. Click the <strong>CREATE ROLE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6968bc5b63bf668/6a85c9a5d6cf29ed61bb08d2/3.png" alt="3" /></p>
<p>Give the Role a <strong>Title</strong> and an <strong>ID</strong>. Then add the 10 assigned permissions listed here.</p>
<ul>
<li>cloudsql.instances.list</li>
<li>compute.instances.list</li>
<li>monitoring.metricDescriptors.list</li>
<li>monitoring.timeSeries.list</li>
<li>pubsub.subscriptions.consume</li>
<li>pubsub.subscriptions.create</li>
<li>pubsub.subscriptions.get</li>
<li>pubsub.topics.attachSubscription</li>
<li>redis.instances.list</li>
<li>run.services.list</li>
</ul>
<p>These permissions are a minimal set of what’s required for this blog post. You should add permissions for all the services for which you would like to collect metrics. If you need to add or remove permissions in the future, the Role’s permissions can be updated as many times as necessary.</p>
<p>Click the <strong>CREATE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc83cc805a73a02f0/6a85c9a893ffb91bfbb9140f/4.png" alt="4" /></p>
<p>Go to Google Cloud <a href="https://console.cloud.google.com/iam-admin/serviceaccounts">IAM Service Accounts</a> to create a Service Account that will be used by the Elastic integration for access to Google Cloud. Click the <strong>CREATE SERVICE ACCOUNT</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b281c0557b900a9/6a85c9abd6cf2975bcbb08d6/5.png" alt="5" /></p>
<p>Enter a <strong>Service account name</strong> and a <strong>Service account ID.</strong> Click the <strong>CREATE AND CONTINUE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt334653fa031b1d0f/6a85c9ad8c29444f1cb89029/6.png" alt="6" /></p>
<p>Then select the <strong>Role</strong> that you created previously and click the <strong>CONTINUE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45e5869885446f4c/6a85c9b02d64d537e2081d0c/7.png" alt="7" /></p>
<p>Click the <strong>DONE</strong> button to complete the Service Account creation process.</p>
<p>Next select the Service Account you just created to see its details page. Under the <strong>KEYS</strong> tab, click the <strong>ADD KEY</strong> dropdown and select <strong>Create new key</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7520bd47c8578699/6a85c9b393ffb92bd3b91413/8.png" alt="8" /></p>
<p>In the Create private key dialog window, with the <strong>Key type</strong> set as JSON, click the <strong>CREATE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte01e021c5d511dd8/6a85c9b59a32f188bca7dfba/9.png" alt="9" /></p>
<p>The JSON credentials file key will be automatically downloaded to your local computer’s <strong>Downloads</strong> folder. The credentials file will be named something like:</p>
<pre><code>your-project-id-12a1234b1234.json
</code></pre>
<p>You can rename the file to be something else. For the purpose of this blog, we’ll rename it to:</p>
<pre><code>credentials.json
</code></pre>
<h3 id="step3createagooglecloudvminstance">Step 3: Create a Google Cloud VM instance</h3>
<p>To create the Compute Engine VM instance in Google Cloud, go to <a href="https://console.cloud.google.com/compute/instances">Compute Engine</a>. Then select <strong>CREATE INSTANCE.</strong></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9cd96f99791f8b61/6a85c9b8eaf2452324a49f3b/10.png" alt="10" /></p>
<p>Enter the following values for the VM instance details:</p>
<ul>
<li>Enter a <strong>Name</strong> of your choice for the VM instance.</li>
<li>Expand the <strong>Advanced Options</strong> section and the <strong>Networking</strong> sub-section.</li>
<li>Enter allow-ssh as the Networking tag.</li>
<li>Select the <strong>Network Interface</strong> to use the <strong>tiered-web-app-private-network</strong> , which is the network on which the Google Cloud three-tier web app is deployed.</li>
</ul>
<p>Click the <strong>CREATE</strong> button to create the VM instance.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45a74a3aa5beeea1/6a85c9bb5c2790459cf59afb/11.png" alt="11" /></p>
<h3 id="step4sshintothegooglecloudvminstanceanduploadthecredentialsfile">Step 4: SSH in to the Google Cloud VM instance and upload the credentials file</h3>
<p>In order to SSH into the Google Cloud VM instance you just created in the previous step, you’ll need to create a Firewall rule in <strong>tiered-web-app-private-network</strong> , which is the network where the VM instance resides.</p>
<p>Go to the Google Cloud <a href="https://console.cloud.google.com/net-security/firewall-manager/firewall-policies/list"><strong>Firewall policies</strong></a> page. Click the <strong>CREATE FIREWALL RULE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt599fa2da6ded5f6e/6a85c9be078290cb06321748/12.png" alt="12" /></p>
<p>Enter the following values for the Firewall Rule.</p>
<ul>
<li>Enter a firewall rule <strong>Name</strong>.</li>
<li>Select <strong>tiered-web-app-private-network</strong> for the <strong>Network</strong>.</li>
<li>Enter allow-ssh for <strong>Target Tags</strong>.</li>
<li>Enter 0.0.0.0/0 for the <strong>Source IPv4 ranges</strong>.Click <strong>TCP</strong> and set the <strong>Ports</strong> to <strong>22</strong>.</li>
</ul>
<p>Click <strong>CREATE</strong> to create the firewall rule.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb1991c77845dec7/6a85c9c12d64d5d73c081d12/13.png" alt="13" /></p>
<p>After the new Firewall rule is created, you can now SSH into your VM instance. Go to the <a href="https://console.cloud.google.com/compute/instances">Google Cloud VM instances</a> and select the VM instance you created in the previous step to see its details page. Click the <strong>SSH</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d2871bb6db22507/6a85c9c39a32f11ecba7dfbe/14.png" alt="14" /></p>
<p>Once you are SSH’d inside the VM instance terminal window, click the <strong>UPLOAD FILE</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb3c81be45dd3f9ba/6a85c9c6f9373db18696f572/15.png" alt="15" /></p>
<p>Select the credentials.json file located on your local computer and click the <strong>Upload Files</strong> button to upload the file.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt79c12de30cb5718a/6a85c9c95c27903ef5f59b01/16.png" alt="16" /></p>
<p>In the VM instance’s SSH terminal, run the following command to get the full path to your Google Cloud Service Account credentials file.</p>
<pre><code>realpath credentials.json
</code></pre>
<p>This should return the full path to your Google Cloud Service Account credentials file.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta577fd983ea101ae/6a85c9cc331d7a4888c31773/17.png" alt="17" /></p>
<p>Copy the credentials file’s full path and save it in a handy location to be used in a later step.</p>
<h3 id="step5addtheelasticgooglecloudintegration">Step 5: Add the Elastic Google Cloud integration</h3>
<p>Navigate to the Google Cloud Platform integration in Elastic by selecting <strong>Integrations</strong> from the top-level menu. Search for google and click the <strong>Google Cloud Platform</strong> tile.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt523984e613b2fd5a/6a85c9cf18249c75d018f7a5/18.png" alt="18" /></p>
<p>Click <strong>Add Google Cloud Platform</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38e92105e192750a/6a85c9d2f9373d568896f57a/19.png" alt="19" /></p>
<p>Click <strong>Add integration only (skip agent installation)</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde5bcf660bcff089/6a85c9d580984c7e2f668fb6/20.png" alt="20" /></p>
<p>Update the <strong>Project Id</strong> input text box to be your Google Cloud Project ID. Next, paste in the credentials file’s full path into the <strong>Credentials File</strong> input text box.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71794a67ac930227/6a85c9d911893cad59a7ab72/21.png" alt="21" /></p>
<p>As you can see, the general Elastic Google Cloud Platform Integration will collect a significant amount of data from 16 Google Cloud services. If you don’t want to install this general Elastic Google Cloud Platform Integration, you can select individual integrations to install. Click <strong>Save and continue</strong>.</p>
<p>You’ll be presented with a confirmation dialog window. Click <strong>Add Elastic Agent to your hosts</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80bdaa482f6dec89/6a85c9dc4710c67a09d3cb2b/22.png" alt="22" /></p>
<p>This will display the instructions required to install the Elastic agent. Copy the command under the <strong>Linux Tar</strong> tab.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12d8a54bba642985/6a85c9df9bf99423670a055d/23.png" alt="23" /></p>
<p>Next you will need to use SSH to log in to the Google Cloud VM instance and run the commands copied from <strong>Linux Tar</strong> tab. Go to <a href="https://console.cloud.google.com/compute/instances">Compute Engine</a>. Then click the name of the VM instance that you created in Step 2. Log in to the VM by clicking the <strong>SSH</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d2871bb6db22507/6a85c9c39a32f11ecba7dfbe/14.png" alt="24 - instance" /></p>
<p>Once you are SSH’d inside the VM instance terminal window, run the commands copied previously from <strong>Linux Tar tab</strong> in the <strong>Install Elastic Agent on your host</strong> instructions.</p>
<p>When the installation completes, you’ll see a confirmation message in the Install Elastic Agent on your host form. Click the <strong>Add the integration</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt901cf4f0fb9eb9ae/6a85c9e293ffb94a3cb91425/25.png" alt="25 - add agent" /></p>
<p>Excellent! The Elastic agent is sending data to Elastic Cloud. Now let’s observe some metrics.</p>
<h3 id="step6runtrafficagainsttheapplication">Step 6: Run traffic against the application</h3>
<p>While getting the application running is fairly easy, there is nothing to monitor or observe with Elastic unless you add a load on the application.</p>
<p>Here is a simple script you can also run using <a href="https://playwright.dev/">Playwright</a> to add traffic and exercise the functionality of the Google Cloud three-tier application:</p>
<pre><code>import { test, expect } from "@playwright/test";

test("homepage for Google Cloud Threetierapp", async ({ page }) =&gt; {
  await page.goto("https://tiered-web-app-fe-zg62dali3a-uc.a.run.app");
  // Insert 2 todo items
  await page.fill("id=todo-new", (Math.random() * 100).toString());
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  await page.fill("id=todo-new", (Math.random() * 100).toString());
  await page.keyboard.press("Enter");
  await page.waitForTimeout(1000);
  // Click one todo item
  await page.getByRole("checkbox").nth(0).check();
  await page.waitForTimeout(1000);
  // Delete one todo item
  const deleteButton = page.getByText("delete").nth(0);
  await deleteButton.dispatchEvent("click");
  await page.waitForTimeout(4000);
});
</code></pre>
<h3 id="step7gotogoogleclouddashboardsinelastic">Step 7: Go to Google Cloud dashboards in Elastic</h3>
<p>With Elastic Agent running, you can go to Elastic Dashboards to view what’s being ingested. Simply search for “dashboard” in Elastic and choose <strong>Dashboards.</strong></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99f34efcec28fb3d/6a85c9e5eaf2450b34a49f41/26.png" alt="26 - dashboard" /></p>
<p>This will open the Elastic Dashboards page.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt263a7b1684f098bb/6a85c9e78c294450d1b89035/27.png" alt="27" /></p>
<p>In the Dashboards search box, search for GCP and click the <strong>[Metrics GCP] CloudSQL PostgreSQL Overview</strong> dashboard, one of the many out-of-the-box dashboards available. Let’s see what comes up.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt221cab7a03a1a453/6a85c9ea11893c22f3a7ab76/28.png" alt="28" /></p>
<p>On the Cloud SQL dashboard, we can see the following sampling of some of the many available metrics:</p>
<ul>
<li>Disk write ops</li>
<li>CPU utilization</li>
<li>Network sent and received bytes</li>
<li>Transaction count</li>
<li>Disk bytes used</li>
<li>Disk quota</li>
<li>Memory usage</li>
<li>Disk read ops</li>
</ul>
<p>Next let’s take a look at metrics for Cloud Run.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc43564072438bcca/6a85c9edeaf2459bfea49f45/29.png" alt="29 - line graphs" /></p>
<p>We’ve created a custom dashboard using the <strong>Create dashboard</strong> button on the Elastic Dashboards page. Here we see a few of the numerous available metrics:</p>
<ul>
<li>Container instance count</li>
<li>CPU utilization for the three-tier app frontend and API</li>
<li>Request count for the three-tier app frontend and API</li>
<li>Bytes in and out of the API</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt117da1e00a38d27b/6a85c9f0e2447a805b8b13e6/30.png" alt="30" /></p>
<p>This is a custom dashboard created for MemoryStore where we can see the following sampling of the available metrics:</p>
<ul>
<li>Network traffic to the Memorystore Redis instance</li>
<li>Count of the keys stored in Memorystore Redis</li>
<li>CPU utilization of the Memorystore Redis instance</li>
<li>Memory usage of the Memorystore Redis instance</li>
</ul>
<p><strong>Congratulations, you have now started monitoring metrics from key Google Cloud services for your application!</strong></p>
<h2 id="whattomonitorongooglecloudnext">What to monitor on Google Cloud next?</h2>
<h3 id="addlogsfromgooglecloudservices">Add logs from Google Cloud Services</h3>
<p>Now that metrics are being monitored, you can also now add logging. There are several options for ingesting logs.</p>
<p>The Google Cloud Platform Integration in the Elastic Agent has four separate logs settings: audit logs, firewall logs, VPC Flow logs, and DNS logs. Just ensure you turn on what you wish to receive.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltebe1cdd2c89adb40/6a85c9f39a32f12a9ba7dfc4/31.png" alt="31" /></p>
<h3 id="analyzeyourdatawithelasticmachinelearning">Analyze your data with Elastic machine learning</h3>
<p>Once metrics and logs (or either one) are in Elastic, start analyzing your data through Elastic’s ML capabilities. A great review of these features can be found here:</p>
<ul>
<li><a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">Correlating APM Telemetry to determine root causes in transactions</a></li>
<li><a href="https://www.elastic.co/elasticon/archive/2020/global/machine-learning-and-the-elastic-stack-everywhere-you-need-it">Introduction to Elastic Machine Learning</a></li>
</ul>
<h2 id="conclusionmonitoringgooglecloudservicemetricswithelasticobservabilityiseasy">Conclusion: Monitoring Google Cloud service metrics with Elastic Observability is easy!</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you monitor Google Cloud service metrics. Here’s a quick recap of lessons and what you learned:</p>
<ul>
<li>Elastic Observability supports ingest and analysis of Google Cloud service metrics.</li>
<li>It’s easy to set up ingest from Google Cloud services via the Elastic Agent.</li>
<li>Elastic Observability has multiple out-of-the-box Google Cloud service dashboards you can use to preliminarily review information and then modify for your needs.</li>
<li>For metrics not covered by out-of-the-box dashboards, custom dashboards can be easily created to visualize metrics that are important to you.</li>
<li>16 Google Cloud services are supported as part of Google Cloud Platform Integration on Elastic Observability, with more services being added regularly.</li>
<li>As noted in related blogs, you can analyze your Google Cloud service metrics with Elastic’s machine learning capabilities.</li>
</ul>
<p>Try it out for yourself by signing up via <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud">Google Cloud 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_google_cloud_platform_gcp_regions">Elastic Cloud regions on Google Cloud</a> around the world. Your Google Cloud Marketplace purchase of Elastic will be included in your monthly consolidated billing statement and will draw against your committed spend with Google Cloud.</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/observability-monitors-metrics-google-cloud</link>
    <guid isPermaLink="false">observability-monitors-metrics-google-cloud</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Jonathan Simon,Eric Lowry]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4366673716734c18/6a85c9f6501a85860dfbb30a/serverless-launch-blog-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 20 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[Achieving seamless API management: Introducing AWS API Gateway integration with Elastic]]></title>
    <description><![CDATA[With Elastic's AWS API Gateway integration, application owners and developers unlock the capability to proactively identify and resolve problems, fine-tune resource utilization, and provide extraordinary digital experiences to their users.]]></description>
    <content:encoded><![CDATA[<p><a href="https://aws.amazon.com/api-gateway/">AWS API Gateway</a> is a powerful service that redefines API management. It serves as a gateway for creating, deploying, and managing APIs, enabling businesses to establish seamless connections between different applications and services. With features like authentication, authorization, and traffic control, API Gateway ensures the security and reliability of API interactions.</p>
<p>In an era where APIs serve as the backbone of modern applications, having the means to maintain visibility and control over these vital components is absolutely essential. In this blog post, we dive deep into the comprehensive observability solution offered by Elastic<sup>®</sup>, ensuring real-time visibility, advanced analytics, and actionable insights, empowering you to fine-tune your API Gateway for optimal performance.</p>
<p>For application owners and developers, this integration stands as a beacon of empowerment. Elastic's meticulous orchestration of the seamless merging of metrics, logs, and traces, built upon the robust <a href="https://www.elastic.co/elastic-stack">ELK Stack</a> foundation, equips them with potent real-time monitoring and analysis tools. These tools facilitate precise performance optimization and swift issue resolution, all within a secure and dependable environment.</p>
<p>With Elastic's AWS API Gateway integration, application owners and developers unlock the capability to proactively identify and resolve problems, fine-tune resource utilization, and provide extraordinary digital experiences to their users.</p>
<h2 id="architecture">Architecture</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2279a21e558b0a0e/6a85c7918c29442987b88faa/elastic-blog-1-architecture.png" alt="architecture" /></p>
<h2 id="whytheawsapigatewayintegrationmatters">Why the AWS API Gateway integration matters</h2>
<p>API Gateway now serves as the foundation of contemporary application development, simplifying the process of creating and overseeing APIs on a large scale. Yet, monitoring and troubleshooting these API endpoints can be challenging. With the new AWS API Gateway integration introduced by Elastic, you can gain the following:</p>
<ul>
<li><strong>Unprecedented visibility:</strong> Monitor your API Gateway endpoints' performance, error rates, and usage metrics in real time. Get a comprehensive view of your APIs' health and performance.</li>
<li><strong>Log analysis:</strong> Dive deep into API Gateway logs with ease. Our integration enables you to collect and analyze logs for HTTP, REST, and Websocket API types, helping you troubleshoot issues and gain valuable insights.</li>
<li><strong>Rapid issue resolution:</strong> Identify and resolve issues in your API Gateway workflows faster than ever. <a href="https://www.elastic.co/observability">Elastic Observability's</a> powerful search and analytics tools help you pinpoint problems with ease.</li>
<li><strong>Alerting and notifications:</strong> Set up custom alerts based on API Gateway metrics and logs. Receive notifications when performance thresholds are breached, ensuring that you can take action promptly.</li>
<li><strong>Optimized costs:</strong> Visualize resource usage and performance metrics for your API Gateway deployments. Use these insights to optimize resource allocation and reduce operational costs.</li>
<li><strong>Custom dashboards:</strong> Create customized dashboards and visualizations tailored to your API Gateway monitoring needs. Stay in control with real-time data and actionable insights.</li>
<li><strong>Effortless integration:</strong> Seamlessly connect your AWS API Gateway to our observability solution. Our intuitive setup process ensures a smooth integration experience.</li>
<li><strong>Scalability:</strong> Whether you have a handful of APIs or a complex API Gateway landscape, our observability solution scales to meet your needs. Grow confidently as your API infrastructure expands.</li>
</ul>
<h2 id="howtogetstarted">How to get started</h2>
<p>Getting started with the AWS API Gateway integration in Elastic Observability is seamless. Here's a quick overview of the steps:</p>
<h3 id="prerequisitesandconfigurations">Prerequisites and configurations</h3>
<p>If you intend to follow the steps outlined in this blog post, there are a few prerequisites and configurations that you should have in place beforehand.</p>
<ol>
<li><p>You will need an account on <a href="http://cloud.elastic.co/">Elastic Cloud</a> and a deployed stack and agent. Instructions for deploying a stack on AWS can be found <a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">here</a>. This is necessary for AWS API Gateway logging and analysis.</p></li>
<li><p>You will also need an AWS account with the necessary permissions to pull data from AWS. Details on the required permissions can be found in our <a href="https://docs.elastic.co/en/integrations/aws#aws-permissions">documentation</a>.</p></li>
<li><p>You can monitor API execution by using CloudWatch, which collects and processes raw data from API Gateway into readable, near-real-time metrics and logs. Details on the required steps to enable logging can be found <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html">here</a>.</p></li>
</ol>
<h3 id="step1createanaccountwithelastic">Step 1. Create an account with Elastic</h3>
<p><a href="https://cloud.elastic.co/registration?fromURI=/home">Create an account on Elastic Cloud</a> by following the steps provided.</p>
<h3 id="step2addintegration">Step 2. Add integration</h3>
<ul>
<li>Log in to your Elastic Cloud deployment.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce0f8d0c79e2b4c3/6a85c7941aa1e186d7ff8ce1/elastic-blog-2-signup.png" alt="signup" /></p>
<ul>
<li>Click on <strong>Add integrations</strong>. You will be navigated to a catalog of supported integrations.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2748bf04c31f95d/6a85c79718249c4e3518f71d/elastic-blog-3-welcome-home.png" alt="welcome home dashboard" /></p>
<ul>
<li>Search and select <strong>AWS API Gateway</strong>.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71a4dce7072e7acd/6a85c79a5c27907a22f59a91/elastic-blog-4-integrations.png" alt="Integration " /></p>
<h3 id="step3configureintegration">Step 3. Configure integration</h3>
<ul>
<li>Click on the <strong>Add AWS API Gateway</strong> button and provide the required details.</li>
<li>If this is your first time adding an AWS integration, you’ll need to <a href="https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html">configure and enroll the Elastic Agent</a> on an AWS instance.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f83959deb09faac/6a85c79d18249c1ac518f721/elastic-blog-5-aws-api-gateway.png" alt="aws-api-gateway" /></p>
<ul>
<li>Then complete the “Configure integration” form, providing all the necessary information required for agents to collect the AWS API Gateway metrics and associated CloudWatch logs. Multiple AWS credential methods are supported, including access keys, temporary security credentials, and IAM role ARN. Please see the <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/security-iam.html">IAM security and access documentation</a> for more details. You can choose to collect API Gateway metrics, API Gateway logs via S3, or API Gateway logs via CloudWatch.</li>
<li>Click on the <strong>Save and continue</strong> button at the bottom of the page.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51fd724c34c8dde3/6a85c7a0331d7a1820c316df/elastic-blog-6-add-aws-integration.png" alt="add-aws-integration" /></p>
<h3 id="step4analyzeandmonitor">Step 4. Analyze and monitor</h3>
<p>Explore the data using the out-of-the-box dashboards available for the integration. Select <strong>Discover</strong> from the Elastic Cloud top-level menu.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84a6d07d34eb38fe/6a85c7a399083fff0e40f951/elastic-blog-7-discover-dashboard.png" alt="discover-dashboard" /></p>
<p>Or, create custom dashboards, set up alerts, and gain actionable insights into your API Gateway service performance.</p>
<p>Here are key monitoring metrics collected through this integration across Rest APIs, HTTP APIs, and Websocket APIs:</p>
<ul>
<li><strong>4XXError</strong> – The number of client-side errors captured in a given period</li>
<li><strong>5XXError</strong> – The number of server-side errors captured in a given period</li>
<li><strong>CacheHitCount</strong> – The number of requests served from the API cache in a given period</li>
<li><strong>CacheMissCount</strong> – The number of requests served from the backend in a given period, when API caching is enabled</li>
<li><strong>Count</strong> – The total number of API requests in a given period</li>
<li><strong>IntegrationLatency</strong> – The time between when API Gateway relays a request to the backend and when it receives a response from the backend</li>
<li><strong>Latency</strong> – The time between when API Gateway receives a request from a client and when it returns a response to the client — the latency includes the integration latency and other API Gateway overhead</li>
<li><strong>DataProcessed</strong> – The amount of data processed in bytes</li>
<li><strong>ConnectCount</strong> – The number of messages sent to the $connect route integration<br />
<strong>MessageCount</strong> – The number of messages sent to the WebSocket API, either from or to the client</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94aa193f13f66209/6a85c7a611893c8309a7aaf8/elastic-blog-8-graphs.png" alt="graphs" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>The native integration of AWS API Gateway into Elastic Observability marks a significant advancement in streamlining the monitoring and management of your APIs. With this integration, you gain access to a wealth of insights, real-time visibility, and powerful analytics tools, empowering you to optimize your API performance, enhance security, and troubleshoot with ease. Don't miss out on this opportunity to take your API management to the next level, ensuring your digital assets operate at their best, all while providing a seamless experience for your users. Embrace this integration, and stay at the forefront of API observability in the ever-evolving world of digital technology.</p>
<p>Visit our <a href="https://docs.elastic.co/integrations/aws/apigateway">documentation</a> to learn more about Elastic Observability and the AWS API Gateway integration, or <a href="https://www.elastic.co/contact">contact our sales team</a> to get started!</p>
<h2 id="startafreetrialtoday">Start a free trial today</h2>
<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=5fbc596b-6d2a-433a-8333-0bd1f28e84da&amp;sc_channel=el&amp;ultron=gobig&amp;hulk=regpage&amp;blade=elasticweb&amp;gambit=mp-b">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><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/api-management-aws-api-gateway-integration</link>
    <guid isPermaLink="false">api-management-aws-api-gateway-integration</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Udayasimha Theepireddy,Subhrata Kulshrestha]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c4519a24584392/6a85c7a99829269770583854/illustration-midnight-bg-aws-elastic-1680x980.png" length="0" type="image/png"/>
    <pubDate>Thu, 14 Sep 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[Elastic SQL inputs: A generic solution for database metrics observability]]></title>
    <description><![CDATA[This blog dives into the functionality of generic SQL and provides various use cases for advanced users to ingest custom metrics to Elastic for database observability. We also introduce the fetch from all database new capability released in 8.10.]]></description>
    <content:encoded><![CDATA[<p>Elastic<sup>®</sup> SQL inputs (<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">metricbeat</a> module and <a href="https://docs.elastic.co/integrations/sql">input package</a>) allows the user to execute <a href="https://en.wikipedia.org/wiki/SQL">SQL</a> queries against many supported databases in a flexible way and ingest the resulting metrics to Elasticsearch<sup>®</sup>. This blog dives into the functionality of generic SQL and provides various use cases for <em>advanced users</em> to ingest custom metrics to Elastic<sup>®</sup>, for database observability. The blog also introduces the fetch from all database new capability, released in 8.10.</p>
<h2 id="whygenericsql">Why “Generic SQL”?</h2>
<p>Elastic already has metricbeat and integration packages targeted for specific databases. One example is <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-mysql.html">metricbeat</a> for MySQL — and the corresponding integration <a href="https://docs.elastic.co/en/integrations/mysql">package</a>. These beats modules and integrations are customized for a specific database, and the metrics are extracted using pre-defined queries from the specific database. The queries used in these integrations and the corresponding metrics are <em>not</em> available for modification.</p>
<p>Whereas the <em>Generic SQL inputs</em> (<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">metricbeat</a> or <a href="https://docs.elastic.co/integrations/sql">input package</a>) can be used to scrape metrics from any supported database using the user's SQL queries. The queries are provided by the user depending on specific metrics to be extracted. This enables a much more powerful mechanism for metrics ingestion, where users can choose a specific driver and provide the relevant SQL queries and the results get mapped to one or more Elasticsearch documents, using a structured mapping process (table/variable format explained later).</p>
<p>Generic SQL inputs can be used in conjunction with the existing integration packages, which already extract specific database metrics, to extract additional custom metrics dynamically, making this input very powerful. In this blog, <em>Generic SQL input</em> and <em>Generic SQL</em> are used interchangeably.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt86cd15e65734414c/6a85cad543c0b77eb12f0612/elastic-blog-1-genericSQL.png" alt="Generic SQL database metrics collection" /></p>
<h2 id="functionalitiesdetails">Functionalities details</h2>
<p>This section covers some of the features that would help with the metrics extraction. We provide a brief description of the response format configuration. Then we dive into the merge_results functionality, which is used to combine results from multiple SQL queries into a single document.</p>
<p>The next key functionality users may be interested in is to collect metrics from all the custom databases, which is now possible with the fetch_from_all_databases feature.</p>
<p>Now let's dive into the specific functionalities:</p>
<h3 id="differentdriverssupported">Different drivers supported</h3>
<p>The generic SQL can fetch metrics from the different databases. The current version has the capability to fetch metrics from the following drivers: MySQL, PostgreSQL, Oracle, and Microsoft SQL Server(MSSQL).</p>
<h3 id="responseformat">Response format</h3>
<p>The response format in generic SQL is used to manipulate the data in either table or in variable format. Here’s an overview of the formats and syntax for creating and using the table and variables.</p>
<p>Syntax: <code>response_format: table {{or}} variables</code></p>
<p><strong>Response format table</strong><br />
This mode generates a single event for each row. The table format has no restrictions on the number of columns in the response. This format can have any number of columns.</p>
<p>Example:</p>
<pre><code>driver: "mssql"
sql_queries:
 - query: "SELECT counter_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
   response_format: table
</code></pre>
<p>This query returns a response similar to this:</p>
<pre><code>"sql":{
      "metrics":{
         "counter_name":"User Connections ",
         "cntr_value":7
      },
      "driver":"mssql"
}
</code></pre>
<p>The response generated above adds the counter_name as a key in the document.</p>
<p><strong>Response format variables</strong><br />
The variable format supports key:value pairs. This format expects only two columns to fetch in a query.</p>
<p>Example:</p>
<pre><code>driver: "mssql"
sql_queries:
 - query: "SELECT counter_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
   response_format: variables
</code></pre>
<p>The variable format takes the first variable in the query above as the key:</p>
<pre><code>"sql":{
      "metrics":{
         "user connections ":7
      },
      "driver":"mssql"
}
</code></pre>
<p>In the above response, you can see the value of counter_name is used to generate the key in variable format.</p>
<h3 id="responseoptimizationmerge_results">Response optimization: merge_results</h3>
<p>We are now supporting merging multiple query responses into a single event. By enabling <strong>merge_results</strong> , users can significantly optimize the storage space of the metrics ingested to Elasticsearch. This mode enables an efficient compaction of the document generated, where instead of generating multiple documents, a single merged document is generated wherever applicable. The metrics of a similar kind, generated from multiple queries, are combined into a single event.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt658fd39fac11b6c5/6a85cada18249ce22818f7bf/elastic-blog-2-output-merge-results.png" alt="Output of Merge results" /></p>
<p>Syntax: <code>merge_results: true {{or}} false</code></p>
<p>In the below example, you can see how the data is loaded into Elasticsearch for the below query when the merge_results is disabled.</p>
<p>Example:</p>
<p>In this example, we are using two different queries to fetch metrics from the performance counter.</p>
<pre><code>merge_results: false
driver: "mssql"
sql_queries:
  - query: "SELECT cntr_value As 'user_connections' FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
    response_format: table
  - query: "SELECT cntr_value As 'buffer_cache_hit_ratio' FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio' AND object_name like '%Buffer Manager%'"
    response_format: table
</code></pre>
<p>As you can see, the response for the above example generates a single document for each query.</p>
<p>The resulting document from the first query:</p>
<pre><code>"sql":{
      "metrics":{
         "user_connections":7
      },
      "driver":"mssql"
}
</code></pre>
<p>And resulting document from the second query:</p>
<pre><code>"sql":{
      "metrics":{
         "buffer_cache_hit_ratio":87
      },
      "driver":"mssql"
}
</code></pre>
<p>When we enable the merge_results flag in the query, both the above metrics are combined together and the data gets loaded in a single document.</p>
<p>You can see the merged document in the below example:</p>
<pre><code>"sql":{
      "metrics":{
         "user connections ":7,
         “buffer_cache_hit_ratio”:87
      },
      "driver":"mssql"
}
</code></pre>
<p><em>However, such a merge is possible only if the table queries are merged, and each produces a single row. There is no restriction on variable queries being merged.</em></p>
<h3 id="introducinganewcapabilityfetch_from_all_databases">Introducing a new capability: fetch_from_all_databases</h3>
<p>This is a <a href="https://github.com/elastic/beats/pull/35688">new functionality</a> to fetch all the database metrics automatically from the system and user databases of the Microsoft SQL Server, by enabling the fetch_from_all_databases flag.</p>
<p>Keep an eye out for the <a href="https://www.elastic.co/guide/en/beats/metricbeat/8.10/metricbeat-module-sql.html#_example_execute_given_queries_for_all_databases_present_in_a_server">8.10 release version</a> where you can start using the fetch all database feature. Prior to the 8.10 version, users had to provide the database names manually to fetch metrics from custom/user databases.</p>
<p>Syntax: <code>fetch_from_all_databases: true {{or}} false</code></p>
<p>Below is the sample query with fetch all databases flag as disabled:</p>
<pre><code>fetch_from_all_databases: false
driver: "mssql"
sql_queries:
  - query: "SELECT @@servername AS server_name, @@servicename AS instance_name, name As 'database_name', database_id FROM sys.databases WHERE name='master';"
</code></pre>
<p>The above query fetches metrics only for the provided database name. Here the input database is master, so the metrics are fetched only for the master.</p>
<p>Below is the sample query with the fetch all databases flag as enabled:</p>
<pre><code>fetch_from_all_databases: true
driver: "mssql"
sql_queries:
  - query: SELECT @@servername AS server_name, @@servicename AS instance_name, DB_NAME() AS 'database_name', DB_ID() AS database_id;
    response_format: table
</code></pre>
<p>The above query fetches metrics from all available databases. This is useful when the user wants to get data from all the databases.</p>
<p>Please note: currently this feature is supported only for Microsoft SQL Server and will be used by MS SQL integration internally, to support extracting metrics for <a href="https://github.com/elastic/integrations/issues/4108">all user DBs</a> by default.</p>
<h2 id="usinggenericsqlmetricbeat">Using generic SQL: Metricbeat</h2>
<p>The generic <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">SQL metricbeat module</a> provides flexibility to execute queries against different database drivers. The metricbeat input is available as GA for any production usage. <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">Here</a>, you can find more information on configuring <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">the generic SQL</a> for different drivers with various examples.</p>
<h2 id="usinggenericsqlinputpackage">Using generic SQL: Input package</h2>
<p>The input package provides a flexible solution to advanced users for customizing their ingestion experience in Elastic. Generic SQL is now also available as an SQL<a href="https://docs.elastic.co/integrations/sql">input package</a>. The input package is currently available for early users as a <strong>beta release</strong>. Let's take a walk through how users can use generic SQL via the input package.</p>
<h3 id="configurationsofgenericsqlinputpackage">Configurations of generic SQL input package:</h3>
<p>The configuration options for the generic SQL input package are as below:</p>
<ul>
<li><strong>Driver**</strong> :** This is the SQL database for which you want to use the package. In this case, we will take mysql as an example.</li>
<li><strong>Hosts:</strong> Here the user enters the connection string to connect to the database. It would vary depending on which database/driver is being used. Refer <a href="https://docs.elastic.co/integrations/sql#hosts">here</a> for examples.</li>
<li><strong>SQL Queries:</strong> Here the user writes the SQL queries they want to fire and the response_format is specified.</li>
<li><strong>Data set:</strong> The user specifies a <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#_data_stream_field_details">data set</a> name to which the response fields get mapped.</li>
<li><strong>Merge results**</strong> :** This is an advanced setting, used to merge queries into a single event.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc36880b217c1a2b7/6a85cadd9829266c605838e4/elastic-blog-3-SQL-metrics-inputpackage.png" alt="Configuration parameters for SQL input package" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f349d24b55e602e/6a85cae333f244f11649f506/elastic-blog-4-expanded-document.png" alt="Metrics getting mapped to the index created by the ‘sql_first_dataset’" /></p>
<h3 id="metricsextensibilitywithcustomizedsqlqueries">Metrics extensibility with customized SQL queries</h3>
<p>Let's say a user is using <a href="https://docs.elastic.co/integrations/mysql">MYSQL Integration</a>, which provides a fixed set of metrics. Their requirement now extends to retrieving more metrics from the MYSQL database by firing new customized SQL queries.</p>
<p>This can be achieved by adding an instance of SQL input package, writing the customized queries and specifying a new <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#field-data-stream-dataset">data set</a> name as shown in the screenshot below.</p>
<p>This way users can get any metrics by executing corresponding queries. The resultant metrics of the query will be indexed to the new data set, sql_second_dataset.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0ae6966bbfd68b/6a85cae7ba7acc2b13992146/elastic-blog-5-driver.png" alt="Customization of Ingest Pipelines and Mappings" /></p>
<p>When there are multiple queries, users can club them into a single event by enabling the Merge Results toggle.</p>
<h3 id="customizinguserexperience">Customizing user experience</h3>
<p>Users can customize their data by writing their own <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">ingest pipelines</a> and providing their customized <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mappings</a>. Users can also build their own bespoke dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89913365b437dcf0/6a85caebf61d6e02d29c2b15/elastic-blog-6-ingest-pipeline.png" alt="Customization of Ingest Pipelines and Mappings" /></p>
<p>As we can see above, the SQL input package provides the flexibility to get new metrics by running new queries, which are not supported in the default MYSQL integration (the user gets metrics from a predetermined set of queries).</p>
<p>The SQL input package also supports multiple drivers: mssql, postgresql and oracle. So a single input package can be used to cater to all these databases.</p>
<p>Note: The fetch_from_all_databases feature is not supported in the SQL input package yet.</p>
<h2 id="tryitout">Try it out!</h2>
<p>Now that you know about various use cases and features of generic SQL, get started with <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a> and try using the <a href="https://docs.elastic.co/integrations/sql">SQL input package</a> for your SQL database and get customized experience and metrics. If you are looking for newer metrics for some of our existing SQL based integrations — like <a href="https://docs.elastic.co/en/integrations/microsoft_sqlserver">Microsoft SQL Server</a>, <a href="https://docs.elastic.co/integrations/oracle">Oracle</a>, and more — go ahead and give the SQL input package a swirl.</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/sql-inputs-database-metrics-observability</link>
    <guid isPermaLink="false">sql-inputs-database-metrics-observability</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Lalit Satapathy,Ishleen Kaur,Muthukumar Paramasivam]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83f89ae1d32f8838/6a85caeeabdc295c6b1224f8/patterns-midnight-background-no-logo-observability.png" length="0" type="image/png"/>
    <pubDate>Mon, 11 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Optimizing cloud resources and cost with APM metadata in Elastic Observability]]></title>
    <description><![CDATA[Optimize cloud costs with Elastic APM. Learn how to leverage cloud metadata, calculate pricing, and make smarter decisions for better performance.]]></description>
    <content:encoded><![CDATA[<p>Application performance monitoring (APM) is much more than capturing and tracking errors and stack traces. Today’s cloud-based businesses deploy applications across various regions and even cloud providers. So, harnessing the power of metadata provided by the Elastic APM agents becomes more critical. Leveraging the metadata, including crucial information like cloud region, provider, and machine type, allows us to track costs across the application stack. In this blog post, we look at how we can use cloud metadata to empower businesses to make smarter and cost-effective decisions, all while improving resource utilization and the user experience.</p>
<p>First, we need an example application that allows us to monitor infrastructure changes effectively. We use a Python Flask application with the Elastic Python APM agent. The application is a simple calculator taking the numbers as a REST request. We utilize Locust — a simple load-testing tool to evaluate performance under varying workloads.</p>
<p>The next step includes obtaining the pricing information associated with the cloud services. Every cloud provider is different. Most of them offer an option to retrieve pricing through an API. But today, we will focus on Google Cloud and will leverage their pricing calculator to retrieve relevant cost information.</p>
<h2 id="thecalculatorandgooglecloudpricing">The calculator and Google Cloud pricing</h2>
<p>To perform a cost analysis, we need to know the cost of the machines in use. Google provides a billing <a href="https://cloud.google.com/billing/v1/how-tos/catalog-api">API</a> and <a href="https://cloud.google.com/billing/docs/reference/libraries#client-libraries-install-python">Client Library</a> to fetch the necessary data programmatically. In this blog, we are not covering the API approach. Instead, the <a href="https://cloud.google.com/products/calculator">Google Cloud Pricing Calculator</a> is enough. Select the machine type and region in the calculator and set the count 1 instance. It will then report the total estimated cost for this machine. Doing this for an e2-standard-4 machine type results in 107.7071784 US$ for a runtime of 730 hours.</p>
<p>Now, let’s go to our Kibana® where we will create a new index inside Dev Tools. Since we don’t want to analyze text, we will tell Elasticsearch® to treat every text as a keyword. The index name is cloud-billing. I might want to do the same for Azure and AWS, then I can append it to the same index.</p>
<pre><code>PUT cloud-billing
{
  "mappings": {
    "dynamic_templates": [
      {
        "stringsaskeywords": {
          "match": "*",
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}
</code></pre>
<p>Next up is crafting our billing document:</p>
<pre><code>POST cloud-billing/_doc/e2-standard-4_europe-west4
{
  "machine": {
    "enrichment": "e2-standard-4_europe-west4"
  },
  "cloud": {
    "machine": {
       "type": "e2-standard-4"
    },
    "region": "europe-west4",
    "provider": "google"
  },
  "stats": {
    "cpu": 4,
    "memory": 8
  },
  "price": {
    "minute": 0.002459068,
    "hour": 0.14754408,
    "month": 107.7071784
  }
}
</code></pre>
<p>We create a document and set a custom ID. This ID matches the instance name and the region since the machines' costs may differ in each region. Automatic IDs could be problematic because I might want to update what a machine costs regularly. I could use a timestamped index for that and only ever use the latest document matching. But this way, I can update and don’t have to worry about it. I calculated the price down to minute and hour prices as well. The most important thing is the machine.enrichment field, which is the same as the ID. The same instance type can exist in multiple regions, but our enrichment processor is limited to match or range. We create a matching name that can explicitly match as in e2-standard-4_europe-west4. It’s up to you to decide whether you want the cloud provider in there and make it google_e2-standard-4_europ-west-4.</p>
<h2 id="calculatingthecost">Calculating the cost</h2>
<p>There are multiple ways of achieving this in the Elastic Stack. In this case, we will use an enrich policy, ingest pipeline, and transform.</p>
<p>The enrich policy is rather easy to setup:</p>
<pre><code>PUT _enrich/policy/cloud-billing
{
  "match": {
    "indices": "cloud-billing",
    "match_field": "machine.enrichment",
    "enrich_fields": ["price.minute", "price.hour", "price.month"]
  }
}

POST _enrich/policy/cloud-billing/_execute
</code></pre>
<p>Don’t forget to run the _execute at the end of it. This is necessary to make the internal indices used by the enrichment in the ingest pipeline. The ingest pipeline is rather minimalistic — it calls the enrichment and renames a field. This is where our machine.enrichment field comes in. One caveat around enrichment is that when you add new documents to the cloud-billing index, you need to rerun the _execute statement. The last bit calculates the total cost with the count of unique machines seen.</p>
<pre><code>PUT _ingest/pipeline/cloud-billing
{
  "processors": [
    {
      "set": {
        "field": "_temp.machine_type",
        "value": "{{cloud.machine.type}}_{{cloud.region}}"
      }
    },
    {
      "enrich": {
        "policy_name": "cloud-billing",
        "field": "_temp.machine_type",
        "target_field": "enrichment"
      }
    },
    {
      "rename": {
        "field": "enrichment.price",
        "target_field": "price"
      }
    },
    {
      "remove": {
        "field": [
          "_temp",
          "enrichment"
        ]
      }
    },
    {
      "script": {
        "source": "ctx.total_price=ctx.count_machines*ctx.price.hour"
      }
    }
  ]
}
</code></pre>
<p>Since this is all configured now, we are ready for our Transform. For this, we need a data view that matches the APM data_streams. This is traces-apm*, metrics-apm.*, logs-apm.*. For the Transform, go to the Transform UI in Kibana and configure it in the following way:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18f78e9e834482f/6a85cdeef61d6e83339c2b75/elastic-blog-1-transform-configuration.png" alt="transform configuration" /></p>
<p>We are doing an hourly breakdown, therefore, I get a document per service, per hour, per machine type. The interesting bit is the aggregations. I want to see the average CPU usage and the 75,95,99 percentile, to view the CPU usage on an hourly basis. Allowing me to identify the CPU usage across an hour. At the bottom, give the transform a name and select an index cloud-costs and select the cloud-billing ingest pipeline.</p>
<p>Here is the entire transform as a JSON document:</p>
<pre><code>PUT _transform/cloud-billing
{
  "source": {
    "index": [
      "traces-apm*",
      "metrics-apm.*",
      "logs-apm.*"
    ],
    "query": {
      "bool": {
        "filter": [
          {
            "bool": {
              "should": [
                {
                  "exists": {
                    "field": "cloud.provider"
                  }
                }
              ],
              "minimum_should_match": 1
            }
          }
        ]
      }
    }
  },
  "pivot": {
    "group_by": {
      "@timestamp": {
        "date_histogram": {
          "field": "@timestamp",
          "calendar_interval": "1h"
        }
      },
      "cloud.provider": {
        "terms": {
          "field": "cloud.provider"
        }
      },
      "cloud.region": {
        "terms": {
          "field": "cloud.region"
        }
      },
      "cloud.machine.type": {
        "terms": {
          "field": "cloud.machine.type"
        }
      },
      "service.name": {
        "terms": {
          "field": "service.name"
        }
      }
    },
    "aggregations": {
      "avg_cpu": {
        "avg": {
          "field": "system.cpu.total.norm.pct"
        }
      },
      "percentiles_cpu": {
        "percentiles": {
          "field": "system.cpu.total.norm.pct",
          "percents": [
            75,
            95,
            99
          ]
        }
      },
      "avg_transaction_duration": {
        "avg": {
          "field": "transaction.duration.us"
        }
      },
      "percentiles_transaction_duration": {
        "percentiles": {
          "field": "transaction.duration.us",
          "percents": [
            75,
            95,
            99
          ]
        }
      },
      "count_machines": {
        "cardinality": {
          "field": "cloud.instance.id"
        }
      }
    }
  },
  "dest": {
    "index": "cloud-costs",
    "pipeline": "cloud-costs"
  },
  "sync": {
    "time": {
      "delay": "120s",
      "field": "@timestamp"
    }
  },
  "settings": {
    "max_page_search_size": 1000
  }
}
</code></pre>
<p>Once the transform is created and running, we need a Kibana Data View for the index: cloud-costs. For the transaction, use the custom formatter inside Kibana and set its format to “Duration” in “microseconds.”</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt294ff077415936ae/6a85cdf18c2944544ab890b7/elastic-blog-2-cloud-costs.png" alt="cloud costs" /></p>
<p>With that, everything is arranged and ready to go.</p>
<h2 id="observinginfrastructurechanges">Observing infrastructure changes</h2>
<p>Below I created a dashboard that allows us to identify:</p>
<ul>
<li>How much costs a certain service creates</li>
<li>CPU usage</li>
<li>Memory usage</li>
<li>Transaction duration</li>
<li>Identify cost-saving potential</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d759db22735c588/6a85cdf41aa1e12e81ff8dbb/elastic-blog-3-graphs.png" alt="graphs" /></p>
<p>From left to right, we want to focus on the very first chart. We have the bars representing the CPU as average in green and 95th percentile in blue on top. It goes from 0 to 100% and is normalized, meaning that even with 8 CPU cores, it will still read 100% usage and not 800%. The line graph represents the transaction duration, the average being in red, and the 95th percentile in purple. Last, we have the orange area at the bottom, which is the average memory usage on that host.</p>
<p>We immediately realize that our calculator does not need a lot of memory. Hovering over the graph reveals 2.89% memory usage. The e2-standard-8 machine that we are using has 32 GB of memory. We occasionally spike to 100% CPU in the 95th percentile. When this happens, we see that the average transaction duration spikes to 2.5 milliseconds. However, every hour this machine costs us a rounded 30 cents. Using this information, we can now downsize to a better fit. The average CPU usage is around 11-13%, and the 95th percentile is not that far away.</p>
<p>Because we are using 8 CPUs, one could now say that 12.5% represents a full core, but that is just an assumption on a piece of paper. Nonetheless, we know there is a lot of headroom, and we can downscale quite a bit. In this case, I decided to go to 2 CPUs and 2 GB of RAM, known as e2-highcpu2. This should fit my calculator application better. We barely touched the RAM, 2.89% out of 32GB are roughly 1GB of use. After the change and reboot of the calculator machine, I started the same Locust test to identify my CPU usage and, more importantly, if my transactions get slower, and if so, by how much. Ultimately, I want to decide whether 1 millisecond more latency is worth 10 more cents per hour. I added the change as an annotation in Lens.</p>
<p>After letting it run for a bit, we can now identify the smaller hosts' impact. In this case, we can see that the average did not change. However, the 95th percentile — as in 95% of all transactions are below this value — did spike up. Again, it looks bad at first, but checking in, it went from ~1.5 milliseconds to ~2.10 milliseconds, a ~0.6 millisecond increase. Now, you can decide whether that 0.6 millisecond increase is worth paying ~180$ more per month or if the current latency is good enough.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Observability is more than just collecting logs, metrics, and traces. Linking user experience to cloud costs allows your business to identify areas where you can save money. Having the right tools at your disposal will help you generate those insights quickly. Making informed decisions about how to optimize your cloud cost and ultimately improve the user experience is the bottom-line goal.</p>
<p>The dashboard and data view can be found in my <a href="https://github.com/philippkahr/blogs/tree/main/apm-cost-optimisation">GitHub repository</a>. You can download the .ndjson file and import it using the Saved Objects inside Stack Management in Kibana.</p>
<h2 id="caveats">Caveats</h2>
<p>Pricing is only for base machines without any disk information, static public IP addresses, and any other additional cost, such as licenses for operating systems. Furthermore, it excludes spot pricing, discounts, or free credits. Additionally, data transfer costs between services are also not included. We only calculate it based on the minute rate of the service running — we are not checking billing intervals from Google Cloud. In our case, we would bill per minute, regardless of what Google Cloud has. Using the count for unique instance.ids work as intended. However, if a machine is only running for one minute, we calculate it based on the hourly rate. So, a machine running for one minute, will cost the same as running for 50 minutes — at least how we calculate it. The transform uses calendar hour intervals; therefore, it's 8 am-9 am, 9 am-10 am, and so on.</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/optimize-cloud-resources-apm-observability</link>
    <guid isPermaLink="false">optimize-cloud-resources-apm-observability</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Philipp Kahr,Nathan Smith]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f205658f212e5d5/6a85cdf7bc5bb38ab5f81b49/illustration-out-of-box-data-vis-1680x980.png" length="0" type="image/png"/>
    <pubDate>Wed, 16 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Exploring Nginx metrics with Elastic time series data streams]]></title>
    <description><![CDATA[Elasticsearch recently released time series metrics as GA. In this blog, we dive into details of what a time series metric document is and the mapping used for enabling time series by using an existing OOTB Nginx integration.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch<sup>®</sup> recently released time series data streams for metrics. This not only provides better metrics support in Elastic Observability, but it also helps reduce <a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0">storage costs</a>. We discussed this in a <a href="https://www.elastic.co/blog/elasticsearch-time-series-data-streams-observability-metrics">previous blog</a>.</p>
<p>In this blog, we dive into how to enable and use time series data streams by reviewing what a time series metrics <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/documents-indices.html">document</a> is and the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mapping</a> used for enabling time series. In particular, we will showcase this by using Elastic Observability’s Nginx integration. As Elastic<sup>®</sup> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.8/tsds.html">time series data stream (TSDS)</a> metrics capabilities evolve, some of the scenarios below will change.</p>
<p>Elastic TSDS stores metrics in indices optimized for a time series database (<a href="https://en.wikipedia.org/wiki/Time_series_database">TSDB</a>), which is used to store time series metrics. <a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0">Elastic’s TSDB also got a significant optimization in 8.7</a> by reducing storage costs by upward of 70%.</p>
<h2 id="whatisanelastictimeseriesdatastream">What is an Elastic time series data stream?</h2>
<p>A time series data stream (TSDS) models timestamped metrics data as one or more time series. In a TSDS, each Elasticsearch document represents an observation or data point in a specific time series. Although a TSDS can contain multiple time series, a document can only belong to one time series. A time series can’t span multiple data streams.</p>
<p>A regular <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/data-streams.html">data stream</a> can have different usages including logs. For metrics usage, however, a time series data stream is recommended. A time series data stream is different from a regular data stream in <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html#differences-from-regular-data-stream">multiple ways</a>. A TSDS contains more than one predefined dimension and multiple metrics.</p>
<h2 id="nginxmetricsasanexample">Nginx metrics as an example</h2>
<p><a href="https://www.elastic.co/integrations/data-integrations?solution=observability">Integrations</a> provide an easy way to ingest observability metrics for a large number of services and systems. We use the <a href="https://docs.elastic.co/en/integrations/nginx">Nginx</a> integration <a href="https://docs.elastic.co/en/integrations/nginx#metrics-reference">metrics</a> data set as an example here. This is one of the integrations, on which time series has been recently enabled.</p>
<h2 id="processofenablingtsdsonapackage">Process of enabling TSDS on a package</h2>
<p>Time series is <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html#time-series-mode">enabled</a> on a metrics data stream of an <a href="https://www.elastic.co/integrations/">integration</a> package, after adding the relevant time series <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html#time-series-metric">metrics</a> and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html#time-series-dimension">dimension</a> mappings. Existing integrations with metrics data streams will come with time series metrics enabled, so that users can use them as-is without any additional configuration.</p>
<p>The image below captures a high-level summary of a time series data stream, the corresponding index template, the time series indices and a single document. We will shortly dive into the details of each of the fields in the document.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1941b44b80b21a70/6a7f0e97c2cc0991d524963a/elastic-blog-1-time-series-data-stream-2.png" alt="time series data stream" /></p>
<h2 id="tsdsmetricdocument">TSDS metric document</h2>
<p>Below we provide a snippet of an ingested Elastic document with time series metrics and dimension together.</p>
<pre><code>{
  "@timestamp": "2023-06-29T03:58:12.772Z",

  "nginx": {
    "stubstatus": {
      "accepts": 202,
      "active": 2,
      "current": 3,
      "dropped": 0,
      "handled": 202,
      "hostname": "host.docker.internal:80",
      "reading": 0,
      "requests": 10217,
      "waiting": 1,
      "writing": 1
    }
  }
}
</code></pre>
<p><strong>Multiple metrics per document:</strong><br />
An ingested <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/documents-indices.html">document</a> has a collection of fields, including metrics fields. Multiple related metrics fields can be part of a single document. A document is part of a single <a href="https://www.elastic.co/guide/en/fleet/current/data-streams.html">data stream</a>, and typically all the metrics it contains are related. All the metrics in a document are part of the same time series.</p>
<p><strong>Metric type and dimensions as mapping:</strong><br />
While the document contains the metrics details, the metric types and dimension details are defined as part of the field <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mapping</a>. All the time series relevant field mappings are defined collectively for a given datastream, as part of the package development. All the integrations released with time series data stream, contain all the relevant time series field mappings, as part of the package release. There are two additional mappings needed in particular: <strong>time_series_metric</strong> mapping and <strong>time_series_dimension</strong> mapping.</p>
<h2 id="metricstypesfields">Metrics types fields</h2>
<p>A document contains the metric type fields (as shown above). The mappings for the metric type fields is done using <strong>time_series_metric</strong> mapping in the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html">index templates</a> as given below:</p>
<pre><code>"nginx": {
    "properties": {
       "stubstatus": {
           "properties": {
                "accepts": {
                  "type": "long",
                  "time_series_metric": "counter"
                },
                "active": {
                  "type": "long",
                  "time_series_metric": "gauge"
                },
                "current": {
                  "type": "long",
                  "time_series_metric": "gauge"
                },
                "dropped": {
                  "type": "long",
                  "time_series_metric": "counter"
                },
                "handled": {
                  "type": "long",
                  "time_series_metric": "counter"
                },
                "reading": {
                  "type": "long",
                  "time_series_metric": "gauge"
                },
                "requests": {
                  "type": "long",
                  "time_series_metric": "counter"
                },
                "waiting": {
                  "type": "long",
                  "time_series_metric": "gauge"
                },
                "writing": {
                  "type": "long",
                  "time_series_metric": "gauge"
                }
           }
       }
    }
}
</code></pre>
<h2 id="dimensionfields">Dimension fields</h2>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html#time-series-dimension">Dimensions</a> are field names and values that, in combination, identify a document’s time series.</p>
<p>In Elastic time series, there are some additional considerations for dimensions:</p>
<ul>
<li>Dimension fields need to be defined for each time series. There will be no time series with zero dimension fields.</li>
<li>Keyword (or similar) type fields can be defined as dimensions.</li>
<li>There is a current limit on the number of dimensions that can be defined in a data stream. The limit restrictions will likely be lifted going forward.</li>
</ul>
<p>Dimension is common for all the metrics in a single document, as part of a data stream. Each time series data stream of a package (example: Nginx) already comes with a predefined set of dimension fields as below.</p>
<p>The document would contain more than one dimension field. In the case of Nginx, <em>agend.id</em> and <em>nginx.stubstatus.hostname</em> are some of the dimension fields. The mappings for the dimension fields is done using <strong>time_series_dimension</strong> mapping as below:</p>
<pre><code>"agent": {
   "properties": {
      "id": {
         "type": "keyword",
         "time_series_dimension": true
       }
    }
 },

"nginx": {
   "properties": {
       "stubstatus": {
            "properties": {
                "hostname": {
                  "type": "keyword",
                  "time_series_dimension": true
                },
            }
       }
    }
}
</code></pre>
<h2 id="metafields">Meta fields</h2>
<p>Documents ingested also have additional meta fields apart from the <em>metric</em> and <em>dimension</em> fields explained above. These additional fields provide richer query capabilities for the metrics.</p>
<p><strong>Example Elastic meta fields</strong></p>
<pre><code>"data_stream": {
      "dataset": "nginx.stubstatus",
      "namespace": "default",
      "type": "metrics"
 }
</code></pre>
<h2 id="discoverandvisualizationinkibana">Discover and visualization in Kibana</h2>
<p>Elastic provides comprehensive search and visualization for the time series metrics. Time series metrics can be searched as-is in <a href="https://www.elastic.co/guide/en/kibana/current/discover.html">Discover</a>. In the search below, the counter and gauges metrics are captured as <em>different icons</em>. Below we also provide examples of visualization for the time series metrics using <a href="https://www.elastic.co/kibana/kibana-lens">Lens</a> and OOTB dashboard included as part of the Nginx integration package.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0e8b439bf3f9f92/6a7f0e9abdcff037b5c42ef1/elastic-blog-2-discover-search-tsds.png" alt="Discover search for TSDS metrics" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5242a1da45d3f167/6a7f0e9db4377009764d6d43/elastic-blog-3-lens.png" alt="Maximum of counter field nginx.stubstatus.accepts visualized using Lens" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71c4c54d6de8bf51/6a7f0ea02f00b2ff3fefec08/elastic-blog-4-median-gauge.png" alt="Median of gauge field nginx.stubstatus.active visualized using Lens" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7172bc5ca118f3e3/6a7f0ea33cab1c43a20e490e/elastic-blog-5-multiple-line-graphs.png" alt="OOTB Nginx dashboard with the TSDS metrics visualizations " /></p>
<h2 id="tryitout">Try it out!</h2>
<p>We have provided a detailed example of a time series document ingested by the Elastic Nginx integration. We have walked through how time series metrics are modeled in Elastic and the additional time series mappings with examples. We provided details of dimension requirements for Elastic time series, as well as brief examples of search/visualization/dashboard of TSDS metrics in Kibana<sup>®</sup>.</p>
<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 auto-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>
<blockquote>
  <ul>
  <li><a href="https://www.elastic.co/blog/elasticsearch-time-series-data-streams-observability-metrics">How to use Elasticsearch and Time Series Data Streams for observability metrics</a></li>
  <li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html">Time Series Data Stream in Elastic documentation</a> </li>
  <li><a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0">Efficient storage with Elastic Time Series Database</a><a href="https://www.elastic.co/integrations/">Elastic integrations catalog</a></li>
  <li><a href="https://www.elastic.co/integrations/">Elastic integrations catalog</a></li>
  </ul>
</blockquote>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/nginx-metrics-elastic-time-series-data-streams</link>
    <guid isPermaLink="false">nginx-metrics-elastic-time-series-data-streams</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Lalit Satapathy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt320155e641e1dfc9/6a7f0ea6eab5be716e20a793/time-series-data-streams-blog-720x420-1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Improving the Elastic APM UI performance with continuous rollups and service metrics]]></title>
    <description><![CDATA[We made significant improvements to the UI performance in Elastic APM to make it scale with even the most demanding workloads, by pre-aggregating metrics at the service level, and storing the metrics at different levels of granularity.]]></description>
    <content:encoded><![CDATA[<p>In today's fast-paced digital landscape, the ability to monitor and optimize application performance is crucial for organizations striving to deliver exceptional user experiences. At Elastic, we recognize the significance of providing our user base with a reliable <a href="https://www.elastic.co/observability">observability platform</a> that scales with you as you’re onboarding thousands of services that produce terabytes of data each day. We have been diligently working behind the scenes to enhance our solution to meet the demands of even the largest deployments.</p>
<p>In this blog post, we are excited to share the significant strides we have made in improving the UI performance of Elastic APM. Maintaining a snappy user interface can be a challenge when interactively summarizing the massive amounts of data needed to provide an overview of the performance for an entire enterprise-scale service inventory. We want to assure our customers that we have listened, taken action, and made notable architectural changes to elevate the scalability and maturity of our solution.</p>
<h2 id="architecturalenhancements">Architectural enhancements</h2>
<p>Our journey began back in the 7.x series where we noticed that doing ad-hoc aggregations on raw <a href="https://www.elastic.co/guide/en/apm/guide/current/data-model-transactions.html">transaction</a> data put Elasticsearch<sup>®</sup> under a lot of pressure in large-scale environments. Since then, we’ve begun to pre-aggregate the transactions into transaction metrics during ingestion. This has helped to keep the performance of the UI relatively stable. Regardless of how busy the monitored application is and how many transaction events it is creating, we’re just querying pre-aggregated metrics that are stored at a constant rate. We’ve enabled the metrics-powered UI by default in <a href="https://github.com/elastic/kibana/issues/92024">7.15</a>.</p>
<p>However, when showing an inventory of a large number of services over large time ranges, the number of metric data points that need to be aggregated can still be large enough to cause performance issues. We also create a time series for each distinct set of dimensions. The dimensions include metadata, such as the transaction name and the host name. Our <a href="https://www.elastic.co/guide/en/apm/guide/current/data-model-metrics.html#_transaction_metrics">documentation</a> includes a full list of all available dimensions. If there’s a very high number of unique transaction names, which could be a result of improper instrumentation (see <a href="https://www.elastic.co/guide/en/kibana/current/troubleshooting.html#troubleshooting-too-many-transactions">docs</a> for more details), this will create a lot of individual time series that will need to be aggregated when requesting a summary of the service’s overall performance. Global labels that are added to the APM Agent configuration are also added as dimensions to these metrics, and therefore they can also impact the number of time series. Refer to the FAQs section below for more details.</p>
<p>Within the 8.7 and 8.8 releases, we’ve addressed these challenges with the following architectural enhancements that aim to reduce the number of documents Elasticsearch needs to search and aggregate on-the-fly, resulting in faster response times:</p>
<ul>
<li><strong>Pre-aggregation of transaction metrics into service metrics.</strong> Instead of aggregating all distinct time series that are created for each individual transaction name on-the-fly for every user request, we’re already pre-aggregating a summary time series for each service during data ingestion. Depending on how many unique transaction names the services have, this reduces the number of documents Elasticsearch needs to look up and aggregate by a factor of typically 10–100. This is particularly useful for the <a href="https://www.elastic.co/guide/en/kibana/master/services.html">service inventory</a> and the <a href="https://www.elastic.co/guide/en/kibana/master/service-overview.html">service overview</a> pages.</li>
<li><strong>Pre-aggregation of all metrics into different levels of granularity.</strong> The APM UI chooses the most appropriate level of granularity, depending on the selected time range. In addition to the metrics that are stored at a 1-minute granularity, we’re also summarizing and storing metrics at a 10-minute and 60-minute granularity level. For example, when looking at a 7-day period, the 60-minute data stream is queried instead of the 1-minute one, resulting in 60x fewer documents for Elasticsearch to examine. This makes sure that all graphs are rendered quickly, even when looking at larger time ranges.</li>
<li><strong>Safeguards on the number of unique transactions per service for which we are aggregating metrics.</strong> Our agents are designed to keep the cardinality of the transaction name low. But in the wild, we’ve seen some services that have a huge amount of unique transaction names. This used to cause performance problems in the UI because APM Server would create many time series that the UI needed to aggregate at query time. In order to protect APM Server from running out of memory when aggregating a large number of time series for each unique transaction name, metrics were published without aggregating when limits for the number of time series were reached. This resulted in a lot of individual metric documents that needed to be aggregated at query time. To address the problem, we've introduced a system where we aggregate metrics in a dedicated overflow bucket for each service when limits are reached. Refer to our <a href="https://www.elastic.co/guide/en/kibana/8.8/troubleshooting.html#troubleshooting-too-many-transactions">documentation</a> for more details.</li>
</ul>
<p>The exact factor of the document count reduction depends on various conditions. But to get a feeling for a typical scenario, if your services, on average, have 10 instances, no instance-specific global labels, 100 unique transaction names each, and you’re looking at time ranges that can leverage the 60m granularity, you’d see a reduction of documents that Elasticsearch needs to aggregate by a factor of 180,000 (10 instances x 100 transaction names x 60m x 3 because we’re also collapsing the event.outcome dimension). While the response times of Elasticsearch aggregations isn’t exactly scaling linearly with the number of documents, there is a strong correlation.</p>
<h2 id="faqs">FAQs</h2>
<h3 id="whenupgradingtothelatestversionwillmyolddataalsoloadfaster">When upgrading to the latest version, will my old data also load faster?</h3>
<p>Updating to 8.8 doesn’t immediately make the UI faster. Because the improvements are powered by pre-aggregations that APM Server is doing during ingestion, only new data will benefit from it. For that reason, you should also make sure to update APM Server as well. The UI can still display data that was ingested using an older version of the stack.</p>
<h3 id="iftheuiisbasedonmetricscanistillsliceanddiceusingcustomlabels">If the UI is based on metrics, can I still slice and dice using custom labels?</h3>
<p>High cardinality analysis is a big strength of Elastic Observability, and this focus on pre-aggregated metrics does not compromise that in any way.</p>
<p>The UI implements a sophisticated fallback mechanism that uses service metrics, transaction metrics, or raw transaction events, depending on which filters are applied. We’re not creating metrics for each user.id, for example. But you can still filter the data by user.id and the UI will then use raw transaction events. Chances are that you’re looking at a narrow slice of data when filtering by a dimension that is not available on the pre-aggregated metrics, therefore aggregations on the raw data are typically very fast.</p>
<p>Note that all global labels that are added to the APM agent configuration are part of the dimension of the pre-aggregated metrics, with the exception of RUM (see more details in <a href="https://github.com/elastic/apm-server/issues/11037">this issue</a>).</p>
<h3 id="caniusethepreaggregatedmetricsincustomdashboards">Can I use the pre-aggregated metrics in custom dashboards?</h3>
<p>Yes! If you use <a href="https://www.elastic.co/guide/en/kibana/current/lens.html">Lens</a> and select the "APM" data view, you can filter on either metricset.name:service_transaction or metricset.name:transaction, depending on the level of detail you need. Transaction latency is captured in transaction.duration.histogram, and successful outcomes and failed outcomes are stored in event.success_count. If you don't need a distribution of values, you can also select the transaction.duration.summary field for your metric aggregations, which should be faster. If you want to calculate the failure rate, here's a <a href="https://www.elastic.co/guide/en/kibana/current/lens.html#lens-formulas">Lens formula</a>: 1 - (sum(event.success_count) / count(event.success_count)). Note that the only granularity supported here is 1m.</p>
<h3 id="dotheadditionalmetricshaveanimpactonthestorage">Do the additional metrics have an impact on the storage?</h3>
<p>While we’re storing more metrics than before, and we’re storing all metrics in different levels of granularity, we were able to offset that by enabling <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html#synthetic-source">synthetic source</a> for all metric data streams. We’ve even increased the default retention for the metrics in the coarse-grained granularity levels, so that the 60m rollup data streams are now stored for 390 days. Please consult our <a href="https://www.elastic.co/guide/en/apm/guide/current/apm-data-streams.html">documentation</a> for more information about the different metric data streams.</p>
<h3 id="aretherelimitsontheamountoftimeseriesthatapmservercanaggregate">Are there limits on the amount of time series that APM Server can aggregate?</h3>
<p>APM Server performs pre-aggregations in memory, which is fast, but consumes a considerable amount of memory. There are limits in place to protect APM Server from running out of memory, and from 8.7, most of them scale with available memory by default, meaning that allocating more memory to APM Server will allow it to handle more unique pre-aggregation groups like services and transactions. These limits are described in <a href="https://www.elastic.co/guide/en/apm/guide/current/data-model-metrics.html#_aggregated_metrics_limits_and_overflows">APM Server Data Model docs</a>.</p>
<p>On the APM Server roadmap, we have plans to move to a LSM-based approach where pre-aggregations are performed with the help of disks in order to reduce memory usage. This will enable APM Server to scale better with the input size and cardinality.</p>
<p>A common pitfall when working with pre-aggregations is to add instance-specific global labels to APM agents. This may exhaust the aggregation limits and cause metrics to be aggregated under the overflow bucket instead of the corresponding service. Therefore, make sure to follow the best practice of only adding a limited set of global labels to a particular service.</p>
<h2 id="validation">Validation</h2>
<p>To validate the effectiveness of the new architecture, and to ensure that the accuracy of the data is not negatively affected, we prepared a test environment where we generated 35K+ transactions per minute in a timespan of 14 days resulting in approximately 850 million documents.</p>
<p>We’ve tested the queries that power our service inventory, the service overview, and the transaction details using different time ranges (1d, 7d, 14d). Across the board, we’ve seen orders of magnitude improvements. Particularly, queries across larger time ranges that benefit from using the coarse-grained metrics in addition to the pre-aggregated service metrics saw incredible reductions of the response time.</p>
<p>We’ve also validated that there’s no loss in accuracy when using the more coarse-grained metrics for larger time ranges.</p>
<p>Every environment will behave a bit differently, but we’re confident that the impressive improvements in response time will translate well to setups of even bigger scale.</p>
<h2 id="plannedimprovements">Planned improvements</h2>
<p>As mentioned in the FAQs section, the number of time series for transaction metrics can grow quickly, as it is the product of multiple dimensions. For example, given a service that runs on 100 hosts and has 100 transaction names that each have 4 transaction results, APM Server needs to track 40,000 (100 x 100 x 4) different time series for that service. This would even exceed the maximum per-service limit of 32,000 for APM Servers with 64GB of main memory.</p>
<p>As a result, the UI will show an entry for “Remaining Transactions” in the Service overview page. This tracks the transaction metrics for a service once it hits the limit. As a result, you may not see all transaction names of your service. It may also be that all distinct transaction names are listed, but that the transaction metrics for some of the instances of that service are combined in the “Remaining Transactions” category.</p>
<p>We’re currently considering restructuring the dimensions for the metrics to avoid that the combination of the dimensions for transaction name and service instance-specific dimensions (such as the host name) lead to an explosion of time series. Stay tuned for more details.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The architectural improvements we’ve delivered in the past releases provide a step-function in terms of the scalability and responsiveness of our UI. Instead of having to aggregate massive amounts of data on-the-fly as users are navigating through the user interface, we pre-aggregate the results for the most common queries as data is coming in. This ensures we have the answers ready before users have even asked their most frequently asked questions, while still being able to answer ad-hoc questions.</p>
<p>We are excited to continue supporting our community members as they push boundaries on their growth journey, providing them with a powerful and mature platform that can effortlessly handle the demands of the largest workloads. Elastic is committed to its mission to enable everyone to find the answers that matter. From all data. In real time. At scale.</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/apm-ui-performance-continuous-rollups-service-metrics</link>
    <guid isPermaLink="false">apm-ui-performance-continuous-rollups-service-metrics</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Data Management]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner,Yngrid Coello,Dario Gieselaar,Carson Ip]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt569decf8bd6851af/6a85cbf2342d6985a621b0fd/elastic-blog-header-ui.png" length="0" type="image/png"/>
    <pubDate>Thu, 29 Jun 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[How to use Elasticsearch and Time Series Data Streams for observability metrics]]></title>
    <description><![CDATA[With Time Series Data Streams (TSDS), Elasticsearch introduces optimized storage for metrics time series. Check out how we use it for Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch is used for a wide variety of data types — one of these is metrics. With the introduction of Metricbeat many years ago and later our APM Agents, the metric use case has become more popular. Over the years, Elasticsearch has made many improvements on how to handle things like metrics aggregations and sparse documents. At the same time, <a href="https://www.elastic.co/guide/en/kibana/current/tsvb.html">TSVB visualizations</a> were introduced to make visualizing metrics easier. One concept that was missing that exists for most other metric solutions is the concept of time series with dimensions.</p>
<p>Mid 2021, the Elasticsearch team <a href="https://github.com/elastic/elasticsearch/issues/74660">embarked</a> on making Elasticsearch a much better fit for metrics. The team created <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/tsds.html">Time Series Data Streams (TSDS)</a>, which were released in 8.7 as generally available (GA).</p>
<p>This blog post dives into how TSDS works and how we use it in Elastic Observability, as well as how you can use it for your own metrics.</p>
<h2 id="aquickintroductiontotsds">A quick introduction to TSDS</h2>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/tsds.html">Time Series Data Streams (TSDS)</a> are built on top of data streams in Elasticsearch that are optimized for time series. To create a data stream for metrics, an additional setting on the data stream is needed. As we are using data streams, first an Index Template has to be created:</p>
<pre><code>PUT _index_template/metrics-laptop
{
  "index_patterns": [
    "metrics-laptop-*"
  ],
  "data_stream": {},
  "priority": 200,
  "template": {
    "settings": {
      "index.mode": "time_series"
    },
    "mappings": {
      "properties": {
        "host.name": {
          "type": "keyword",
          "time_series_dimension": true
        },
        "packages.sent": {
          "type": "integer",
          "time_series_metric": "counter"
        },
        "memory.usage": {
          "type": "double",
          "time_series_metric": "gauge"
        }
      }
    }
  }
}
</code></pre>
<p>Let's have a closer look at this template. On the top part, we mark the index pattern with metrics-laptop-*. Any pattern can be selected, but it is recommended to use the <a href="https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme">data stream naming scheme</a> for all your metrics. The next section sets the "index.mode": "time_series" in combination with making sure it is a data_stream: "data_stream": {}.</p>
<h3 id="dimensions">Dimensions</h3>
<p>Each time series data stream needs at least one dimension. In the example above, host.name is set as a dimension field with "time_series_dimension": true. You can have up to 16 dimensions by default. Not every dimension must show up in each document. The dimensions define the time series. The general rule is to pick fields as dimensions that uniquely identify your time series. Often this is a unique description of the host/container, but for some metrics like disk metrics, the disk id is needed in addition. If you are curious about default recommended dimensions, have a look at this <a href="https://github.com/elastic/ecs/pull/2172">ECS contribution</a> with dimension properties.</p>
<h2 id="reducedstorageandincreasedqueryspeed">Reduced storage and increased query speed</h2>
<p>At this point, you already have a functioning time series data stream. Setting the index mode to time series automatically turns on <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html#synthetic-source">synthetic source</a>. By default, Elasticsearch typically duplicates data three times:</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS#Row-oriented_systems">row-oriented storage</a> (_source field)</li>
<li><a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS#Column-oriented_systems">column-oriented storage</a> (doc_values: true for aggregations)</li>
<li>indices (index: true for filtering and search)</li>
</ul>
<p>With synthetic source, the _source field is not persisted; instead, it is reconstructed from the doc values. Especially in the metrics use case, there are little benefits to keeping the source.</p>
<p>Not storing it means a significant reduction in storage. Time series data streams sort the data based on the dimensions and the time stamp. This means data that is usually queried together is stored together, which speeds up query times. It also means that the data points for a single time series are stored alongside each other on disk. This enables further compression of the data as the rate at which a counter increases is often relatively constant.</p>
<h2 id="metrictypes">Metric types</h2>
<p>But to benefit from all the advantages of TSDS, the field properties of the metrics fields must be extended with the <code>time_series_metric: {type}</code>. Several <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/tsds.html#time-series-metric">types are supported</a> — as an example, gauge and counter were used above. Giving Elasticsearch knowledge about the metric type allows Elasticsearch to offer more optimized queries for the different types and reduce storage usage further.</p>
<p>When you create your own templates for data streams under the <a href="https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme">data stream naming scheme</a>, it is important that you set "priority": 200 or higher, as otherwise the built-in default template will apply.</p>
<h2 id="ingestadocument">Ingest a document</h2>
<p>Ingesting a document into a TSDS isn't in any way different from ingesting documents into Elasticsearch. You can use the following commands in Dev Tools to add a document, and then search for it and also check out the mappings. Note: You have to adjust the @timestamp field to be close to your current date and time.</p>
<pre><code># Add a document with `host.name` as the dimension
POST metrics-laptop-default/_doc
{
  # This timestamp neesd to be adjusted to be current
  "@timestamp": "2023-03-30T12:26:23+00:00",
  "host.name": "ruflin.com",
  "packages.sent": 1000,
  "memory.usage": 0.8
}

# Search for the added doc, _source will show up but is reconstructed
GET metrics-laptop-default/_search

# Check out the mappings
GET metrics-laptop-default
</code></pre>
<p>If you do search, it still shows _source but this is reconstructed from the doc values. The additional field added above is @timestamp. This is important as it is a required field for any data stream.</p>
<h2 id="whyisthisallimportantforobservability">Why is this all important for Observability?</h2>
<p>One of the advantages of the Elastic Observability solution is that in a single storage engine, all signals are brought together in a single place. Users can query logs, metrics, and traces together without having to jump from one system to another. Because of this, having a great storage and query engine not only for logs but also metrics is key for us.</p>
<h2 id="usageoftsdsinintegrations">Usage of TSDS in integrations</h2>
<p>With <a href="https://www.elastic.co/integrations/data-integrations">integrations</a>, we give our users an out of the box experience to integrate with their infrastructure and services. If you are using our integrations, eventually you will automatically get all the benefits of TSDS for your metrics assuming you are on version 8.7 or newer.</p>
<p>Currently we are working through the list of our integration packages, add the dimensions, metric type fields and then turn on TSDS for the metrics data streams. What this means is as soon as the package has all properties enabled, the only thing you have to do is upgrade the integration and everything else will happen automatically in the background.</p>
<p>To visualize your time series in Kibana, use <a href="https://www.elastic.co/guide/en/kibana/current/lens.html">Lens</a>, which has native support built in for TSDS.</p>
<h2 id="learnmore">Learn more</h2>
<p>If you switch over to TSDS, you will automatically benefit from all the future improvements Elasticsearch is making for metrics time series, be it more efficient storage, query performance, or new aggregation capabilities. If you want to learn more about how TSDS works under the hood and all available config options, check out the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/tsds.html">TSDS documentation</a>. What Elasticsearch supports in 8.7 is only the first iteration of the metrics time series in Elasticsearch.</p>
<p><a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0">TSDS can be used since 8.7</a> and will be in more and more of our integrations automatically when integrations are upgraded. All you will notice is lower storage usage and faster queries. Enjoy!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/time-series-data-streams-observability-metrics</link>
    <guid isPermaLink="false">time-series-data-streams-observability-metrics</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Nicolas Ruflin]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2ff8f2a6b4ee6f6/6a85cef7331d7aa71cc3184f/ebpf-monitoring.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 04 May 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitoring service performance: An overview of SLA calculation for Elastic Observability]]></title>
    <description><![CDATA[Elastic Stack provides many valuable insights for different users, such as reports on service performance and if the service level agreement (SLA) is met. In this post, we’ll provide an overview of calculating an SLA for Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>Elastic Stack provides many valuable insights for different users. Developers are interested in low-level 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 and identifying where the root cause is. Managers want reports that tell them how good service performance is and if the service level agreement (SLA) is met. In this post, we’ll focus on the service perspective and provide an overview of calculating an SLA.</p>
<p><em>Since version 8.8, we have a built in functionality to calculate SLOs —</em> <a href="https://www.elastic.co/guide/en/observability/current/slo.html"><em>check out our guide</em></a><em>!</em></p>
<h2 id="foundationsofcalculatingansla">Foundations of calculating an SLA</h2>
<p>There are many ways to calculate and measure an SLA. The most important part is the definition of the SLA, and as a consultant, I’ve seen many different ways. Some examples include:</p>
<ul>
<li>Count of HTTP 2xx must be above 98% of all HTTP status</li>
<li>Response time of successful HTTP 2xx requests must be below x milliseconds</li>
<li>Synthetic monitor must be up at least 99%</li>
<li>95% of all batch transactions from the billing service need to complete within 4 seconds</li>
</ul>
<p>Depending on the origin of the data, calculating the SLA can be easier or more difficult. For uptime (Synthetic Monitoring), we automatically provide SLA values and offer out-of-the-box alerts to simply define alert when availability below 98% for the last 1 hour.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2d548426b2de11/6a7f0ed563e95958ae73dea0/blog-elastic-overview-monitor-details.png" alt="overview monitor details" /></p>
<p>I personally recommend using <a href="https://www.elastic.co/blog/new-synthetic-monitoring-observability">Elastic Synthetic Monitoring</a> whenever possible to monitor service performance. Running HTTP requests and verifying the answers from the service, or doing fully fledged browser monitors and clicking through the website as a real user does, ensures a better understanding of the health of your service.</p>
<p>Sometimes this is impossible because you want to calculate the uptime of a specific Windows Service that does not offer any TCP port or HTTP interaction. Here the caveat applies that just because the service is running, it does not necessarily imply that the service is working fine.</p>
<h2 id="transformstotherescue">Transforms to the rescue</h2>
<p>We have identified our important service. In our case, it is the Steam Client Helper. There are two ways to solve this.</p>
<h3 id="lensformula">Lens formula</h3>
<p>You can use Lens and formula (for a deep dive into formulas, <a href="https://www.elastic.co/blog/how-tough-was-your-workout-take-a-closer-look-at-strava-data-through-kibana-lens">check out this blog</a>). Use the Search bar to filter down the data you want. Then use the formula option in Lens. We are dividing all counts of records with Running as a state and dividing it by the overall count of records. This is a nice solution when there is a need to calculate quickly and on the fly.</p>
<pre><code>count(kql='windows.service.state: "Running" ')/count()
</code></pre>
<p>Using the formula posted above as the bar chart's vertical axis calculates the uptime percentage. We use an annotation to mark why there is a dip and why this service was below the threshold. The annotation is set to reboot, which indicates a reboot happening, and thus, the service was down for a moment. Lastly, we add a reference line and set this to our defined threshold at 98%. This ensures that a quick look at the visualization allows our eyes to gauge if we are above or below the threshold.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34a650fc757a72e6/6a7f0ed86693f826b8664001/blog-elastic-visualization.png" alt="visualization" /></p>
<h3 id="transform">Transform</h3>
<p>What if I am not interested in just one service, but there are multiple services needed for your SLA? That is where Transforms can solve this problem. Furthermore, the second issue is that this data is only available inside the Lens. Therefore, we cannot create any alerts on this.</p>
<p>Go to Transforms and create a pivot transform.</p>
<ol>
<li><p>Add the following filter to narrow it to only services data sets: data_stream.dataset: "windows.service". If you are interested in a specific service, you can always add it to the search bar if you want to know if a specific remote management service is up in your entire fleet!</p></li>
<li><p>Select data histogram(@timestamp) and set it to your chosen unit. By default, the Elastic Agent only collects service states every 60 seconds. I am going with 1 hour.</p></li>
<li><p>Select agent.name and windows.service.name as well.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte97db39dd71386e5/6a7f0edbeab5be71a920a7b3/blog-elastic-transform-configuration.png" alt="transform configuration" /></p>
<ol>
<li>Now we need to define an aggregation type. We will use a value_count of windows.service.state. That just counts how many records have this value.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49501f71d3d6f782/6a7f0edd448e4e547f5c07e3/blog-elastic-aggregations.png" alt="aggregations" /></p>
<ol>
<li><p>Rename the value_count to total_count.</p></li>
<li><p>Add value_count for windows.service.state a second time and use the pencil icon to edit it to terms, which aggregates for running.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt798455134fc46058/6a7f0ee01967ea4fa233081f/blog-elastic-aggregations-apply.png" alt="aggregations apply" /></p>
<ol>
<li><p>This opens up a sub-aggregation. Once again, select value_count(windows.service.state) and rename it to values.</p></li>
<li><p>Now, the preview shows us the count of records with any states and the count of running.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9f3761c68fa183d/6a7f0ee333fa8aaa772027ba/blog-elastic-transform-configuration-next.png" alt="transform configuration" /></p>
<ol>
<li><p>Here comes the tricky part. We need to write some custom aggregations to calculate the percentage of uptime. Click on the copy icon next to the edit JSON config.</p></li>
<li><p>In a new tab, go to Dev Tools. Paste what you have in the clipboard.</p></li>
<li><p>Press the play button or use the keyboard shortcut ctrl+enter/cmd+enter and run it. This will create a preview of what the data looks like. It should give you the same information as in the table preview.</p></li>
<li><p>Now, we need to calculate the percentage of up, which means doing a bucket script where we divide running.values by total_count, just like we did in the Lens visualization. Suppose you name the columns differently or use more than a single value. In that case, you will need to adapt accordingly.</p></li>
</ol>
<pre><code>"availability": {
        "bucket_script": {
          "buckets_path": {
            "up": "running&gt;values",
            "total": "total_count"
          },
          "script": "params.up/params.total"
        }
      }
</code></pre>
<ol>
<li>This is the entire transform for me:</li>
</ol>
<pre><code>POST _transform/_preview
{
  "source": {
    "index": [
      "metrics-*"
    ]
  },
  "pivot": {
    "group_by": {
      "@timestamp": {
        "date_histogram": {
          "field": "@timestamp",
          "calendar_interval": "1h"
        }
      },
      "agent.name": {
        "terms": {
          "field": "agent.name"
        }
      },
      "windows.service.name": {
        "terms": {
          "field": "windows.service.name"
        }
      }
    },
    "aggregations": {
      "total_count": {
        "value_count": {
          "field": "windows.service.state"
        }
      },
      "running": {
        "filter": {
          "term": {
            "windows.service.state": "Running"
          }
        },
        "aggs": {
          "values": {
            "value_count": {
              "field": "windows.service.state"
            }
          }
        }
      },
      "availability": {
        "bucket_script": {
          "buckets_path": {
            "up": "running&gt;values",
            "total": "total_count"
          },
          "script": "params.up/params.total"
        }
      }
    }
  }
}
</code></pre>
<ol>
<li>The preview in Dev Tools should work and be complete. Otherwise, you must debug any errors. Most of the time, it is the bucket script and the path to the values. You might have called it up instead of running. This is what the preview looks like for me.</li>
</ol>
<pre><code>{
  "running": {
    "values": 1
  },
  "agent": {
    "name": "AnnalenasMac"
  },
  "@timestamp": "2021-12-07T19:00:00.000Z",
  "total_count": 1,
  "availability": 1,
  "windows": {
    "service": {
      "name": "InstallService"
    }
  }
},
</code></pre>
<ol>
<li>Now we only paste the bucket script into the transform creation UI after selecting Edit JSON. It looks like this:</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d7e576bbffe943b/6a7f0ee7c2cc091689249666/blog-elastic-transform-configuration-pivot-configuration-object.png" alt="transform configuration pivot configuration object" /></p>
<ol>
<li>Give your transform a name, set the destination index, and run it continuously. When selecting this, please also make sure not to use @timestamp. Instead, opt for event.ingested. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/transform-checkpoints.html">Our documentation explains this in detail</a>.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf553c02bfe0a169c/6a7f0eeaeab5be10b120a7b9/blog-elastic-transform-details.png" alt="transform details" /></p>
<ol>
<li>Click next and create and start. This can take a bit, so don’t worry.</li>
</ol>
<p>To summarize, we have now created a pivot transform using a bucket script aggregation to calculate the running time of a service in percentage. There is a caveat because Elastic Agent, per default, only collects the every 60 seconds the services state. It can be that a service is up exactly when collected and down a few seconds later. If it is that important and no other monitoring possibilities, such as <a href="https://www.elastic.co/blog/what-can-elastic-synthetics-tell-us-about-kibana-dashboards">Elastic Synthetics</a> are possible, you might want to reduce the collection time on the Agent side to get the services state every 30 seconds, 45 seconds. Depending on how important your thresholds are, you can create multiple policies having different collection times. This ensures that a super important server might collect the services state every 10 seconds because you need as much granularity and insurance for the correctness of the metric. For normal workstations where you just want to know if your remote access solution is up the majority of the time, you might not mind having a single metric every 60 seconds.</p>
<p>After you have created the transform, one additional feature you get is that the data is stored in an index, similar to in Elasticsearch. When you just do the visualization, the metric is calculated for this visualization only and not available anywhere else. Since this is now data, you can create a threshold alert to your favorite connection (Slack, Teams, Service Now, Mail, and so <a href="https://www.elastic.co/guide/en/kibana/current/action-types.html">many more to choose from</a>).</p>
<h2 id="visualizingthetransformeddata">Visualizing the transformed data</h2>
<p>The transform created a data view called windows-service. The first thing we want to do is change the format of the availability field to a percentage. This automatically tells Lens that this needs to be formatted as a percentage field, so you don’t need to select it manually as well as do calculations. Furthermore, in Discover, instead of seeing 0.5 you see 50%. Isn’t that cool? This is also possible for durations, like event.duration if you have it as nanoseconds! No more calculations on the fly and thinking if you need to divide by 1,000 or 1,000,000.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65c1a7d0833a6894/6a7f0eecbdcff02544c42f1b/blog-elastic-edit-field-availability.png" alt="edit field availability" /></p>
<p>We get this view by using a simple Lens visualization with a timestamp on the vertical axis with the minimum interval for 1 day and an average of availability. Don’t worry — the other data will be populated once the transformation finishes. We can add a reference line using the value 0.98 because our target is 98% uptime of the service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5831bb81c97d83f/6a7f0eefea068d7688f09f54/blog-elastic-line.png" alt="line" /></p>
<h2 id="summary">Summary</h2>
<p>This blog post covered the steps needed to calculate the SLA for a specific data set in Elastic Observability, as well as how to visualize it. Using this calculation method opens the door to a lot of interesting use cases. You can change the bucket script and start calculating the number of sales, and the average basket size. Interested in learning more about Elastic Synthetics? Read <a href="https://www.elastic.co/guide/en/observability/current/monitor-uptime-synthetics.html">our documentation</a> or check out our free <a href="https://www.elastic.co/training/synthetics-quick-start">Synthetic Monitoring Quick Start training</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observability-sla-calculations-transforms</link>
    <guid isPermaLink="false">observability-sla-calculations-transforms</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Philipp Kahr]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd00efad84954bdc1/6a7f0ef2ea068d6a81f09f5a/illustration-analytics-report-1680x980.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 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[Easily analyze AWS VPC Flow Logs with Elastic Observability]]></title>
    <description><![CDATA[Elastic Observability can ingest and help analyze AWS VPC Flow Logs from your application’s VPC. Learn how to ingest AWS VPC Flow Logs through a step-by-step method into Elastic, then analyze it and apply OOTB machine learning for insights.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability provides a full-stack observability solution, by supporting metrics, traces, and logs for applications and infrastructure. In <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">a previous blog</a>, I showed you an <a href="https://www.elastic.co/observability/aws-monitoring">AWS monitoring</a> infrastructure running a three-tier application. Specifically we reviewed metrics ingest and analysis on Elastic Observability for EC2, VPC, ELB, and RDS. In this blog, we will cover how to ingest logs from AWS, and more specifically, we will review how to get VPC Flow Logs into Elastic and what you can do with this data.</p>
<p>Logging is an important part of observability, for which we generally think of metrics and/or tracing. However, the amount of logs an application or the underlying infrastructure output can be significantly daunting.</p>
<p>With Elastic Observability, there are three main mechanisms to ingest logs:</p>
<ul>
<li>The new Elastic Agent pulls metrics and logs from CloudWatch and S3 where logs are generally pushed from a service (for example, EC2, ELB, WAF, Route53, etc ). We reviewed Elastic agent metrics configuration for EC2, RDS (Aurora), ELB, and NAT metrics in this <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">blog</a>.</li>
<li>Using <a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">Elastic’s Serverless Forwarder (runs on Lambda and available in AWS SAR)</a> to send logs from Firehose, S3, CloudWatch, and other AWS services into Elastic.</li>
<li>Beta feature (contact your Elastic account team): Using AWS Firehose to directly insert logs from AWS into Elastic — specifically if you are running the Elastic stack on AWS infrastructure.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt864e9aa3b4bf3d2a/6a7f1c302f00b28cbfefef61/Elastic-Observability-VPC-Flow-Logs.jpg" alt="" /></p>
<p>In this blog we will provide an overview of the second option, Elastic’s serverless forwarder collecting VPC Flow Logs from an application deployed on EC2 instances. Here’s what we'll cover:</p>
<ul>
<li>A walk-through on how to analyze VPC Flow Log info with Elastic’s Discover, dashboard, and ML analysis.</li>
<li>A detailed step-by-step overview and setup of the Elastic serverless forwarder on AWS as a pipeline for VPC Flow Logs into <a href="http://cloud.elastic.co">Elastic Cloud</a>.</li>
</ul>
<h2 id="elasticsserverlessforwarderonawslambda">Elastic’s serverless forwarder on AWS Lambda</h2>
<p>AWS users can quickly ingest logs stored in Amazon S3, CloudWatch, or Kinesis with the Elastic serverless forwarder, an AWS Lambda application, and view them in the Elastic Stack alongside other logs and metrics for centralized analytics. Once the AWS serverless forwarder is configured and deployed from AWS, Serverless Application Registry (SAR) logs will be ingested and available in Elastic for analysis. See the following links for further configuration guidance:</p>
<ul>
<li><a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">Elastic’s serverless forwarder (runs Lambda and available in AWS SAR)</a></li>
<li><a href="https://github.com/elastic/elastic-serverless-forwarder/blob/main/docs/README-AWS.md#s3_config_file">Serverless forwarder GitHub repo</a></li>
</ul>
<p>In our configuration we will ingest VPC Flow Logs into Elastic for the three-tier app deployed in the previous <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">blog</a>.</p>
<p>There are three different configurations with the Elastic serverless forwarder:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt719dbab9506f0a1e/6a7f1c326c6eacb19af145d1/blog-elastic-vpc-flow-logs-3-configurations.png" alt="" /></p>
<p>Logs can be directly ingested from:</p>
<ul>
<li><strong>Amazon CloudWatch:</strong> Elastic serverless forwarder can pull VPC Flow Logs directly from an Amazon CloudWatch log group, which is a commonly used endpoint to store VPC Flow Logs in AWS.</li>
<li><strong>Amazon Kinesis:</strong> Elastic serverless forwarder can pull VPC Flow Logs directly from Kinesis, which is another location to <a href="https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-firehose.html">publish VPC Flow Logs</a>.</li>
<li><strong>Amazon S3:</strong> Elastic serverless forwarder can pull VPC Flow Logs from Amazon S3 via SQS event notifications, which is a common endpoint to publish VPC Flow Logs in AWS.</li>
</ul>
<p>We will review how to utilize a common configuration, which is to send VPC Flow Logs to Amazon S3 and into Elastic Cloud in the second half of this blog.</p>
<p>But first let's review how to analyze VPC Flow Logs on Elastic.</p>
<h2 id="analyzingvpcflowlogsinelastic">Analyzing VPC Flow Logs in Elastic</h2>
<p>Now that you have VPC Flow Logs in Elastic Cloud, how can you analyze them?</p>
<p>There are several analyses you can perform on the VPC Flow Log data:</p>
<ol>
<li>Use Elastic’s Analytics Discover capabilities to manually analyze the data.</li>
<li>Use Elastic Observability’s anomaly feature to identify anomalies in the logs.</li>
<li>Use an out-of-the-box (OOTB) dashboard to further analyze data.</li>
</ol>
<h3 id="usingelasticdiscover">Using Elastic Discover</h3>
<p>In Elastic analytics, you can search and filter your data, get information about the structure of the fields, and display your findings in a visualization. You can also customize and save your searches and place them on a dashboard. With Discover, you can:</p>
<ul>
<li>View logs in bulk, within specific time frames</li>
<li>Look at individual details of each entry (document)</li>
<li>Filter for specific values</li>
<li>Analyze fields</li>
<li>Create and save searches</li>
<li>Build visualizations</li>
</ul>
<p>For a complete understanding of Discover and all of Elastic’s analytics capabilities, look at <a href="https://www.elastic.co/guide/en/kibana/current/discover.html#">Elastic documentation</a>.</p>
<p>For VPC Flow Logs, an important stat is to understand:</p>
<ul>
<li>How many logs were accepted/rejected</li>
<li>Where potential security violations are occur (for example, source IPs from outside the VPC)</li>
<li>What port is generally being queried</li>
</ul>
<p>I’ve filtered the logs on the following:</p>
<ul>
<li>Amazon S3: bshettisartest</li>
<li>VPC Flow Log action: REJECT</li>
<li>VPC Network Interface: Webserver 1</li>
</ul>
<p>We want to see what IP addresses are trying to hit our web servers.</p>
<p>From that, we want to understand which IP addresses we are getting the most REJECTS from, and we simply find the <strong>source</strong>.ip field. Then, we can quickly get a breakdown that shows 185.242.53.156 is the most rejected for the last 3+ hours we’ve turned on VPC Flow Logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3966498d22108ca/6a7f1c36bd21989acc7584e1/blog-elastic-vpc-flow-logs-100-hits.png" alt="" /></p>
<p>Additionally, I can see a visualization by selecting the “Visualize” button. We get the following, which we can add to a dashboard:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt805432c53beff1e7/6a7f1c39eab5be37b020ab38/blog-elastic-vpc-flow-logs-add-to-a-dashboard.png" alt="" /></p>
<p>In addition to IP addresses, we want to also see what port is being hit on our web servers.<br />
We select the destination port field, and the quick pop-up shows us a list of ports being targeted. We can see that port 23 is being targeted (this port is generally used for telnet), port 445 is being targeted (used for Microsoft Active Directory), and port 433 (used for https ssl). We also see these are all REJECT.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a860d66b3ad9a19/6a7f1c3cbd219808a07584e7/blog-elastic-vpc-flow-logs-reject.png" alt="" /></p>
<h3 id="anomalydetectioninelasticobservabilitylogs">Anomaly detection in Elastic Observability logs</h3>
<p>Addition to Discover, Elastic Observability provides the ability to detect anomalies on logs. In Elastic Observability -&gt; logs -&gt; anomalies you can turn on machine learning for:</p>
<ul>
<li>Log rate: automatically detects anomalous log entry rates</li>
<li>Categorization: automatically categorizes log messages</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ba725798202a6a9/6a7f1c3f5967e5f51a5dd6f3/blog-elastic-vpc-flow-logs-anomaly-detection-with-machine-learning.png" alt="" /></p>
<p>For our VPC Flow Log, we turned both on. And when we look at what has been detected for anomalous log entry rates, we see:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2131164c1682ec1/6a7f1c424c4bfb5f14ccd924/blog-elastic-vpc-flow-logs-anomalies.png" alt="" /></p>
<p>Elastic immediately detected a spike in logs when we turned on VPC Flow Logs for our application. The rate change is being detected because we’re also ingesting VPC Flow Logs from another application for a couple of days prior to adding the application in this blog.</p>
<p>We can further drill down into this anomaly with machine learning and analyze further.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab04d37c363e03e3/6a7f1c455967e599895dd6f9/blog-elastic-vpc-flow-logs-anomaly-explorer.png" alt="" /></p>
<p>There is more machine learning analysis you can utilize with your logs — check out <a href="https://www.elastic.co/guide/en/kibana/8.5/xpack-ml.html">Elastic machine learning documentation</a>.</p>
<p>Since we know that a spike exists, we can also use Elastic AIOps Labs Explain Log Rate Spikes capability in Machine Learning. Additionally, we’ve grouped them to see what is causing some of the spikes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac4cc921055c5d85/6a7f1c4877b03481d23ff93d/blog-elastic-vpc-flow-logs-explain-log-rate-spikes.png" alt="" /></p>
<p>As we can see, a specific network interface is sending more VPC log flows than others. We can further drill down into this further in Discover.</p>
<h3 id="vpcflowlogdashboardonelasticobservability">VPC Flow Log dashboard on Elastic Observability</h3>
<p>Finally, Elastic also provides an OOTB dashboard to showing the top IP addresses hitting your VPC, geographically where they are coming from, the time series of the flows, and a summary of VPC Flow Log rejects within the time frame.</p>
<p>This is a baseline dashboard that can be enhanced with visualizations you find in Discover, as we reviewed in option 1 (Using Elastic’s Analytics Discover capabilities) above.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt400e83d9099b4364/6a7f1c4bead8ec4d64baac80/blog-elastic-vpc-flow-logs-action-geolocation.png" alt="" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Let’s walk through the details of configuring Amazon Kinesis Data Firehose and Elastic Observability to ingest data.</p>
<h3 id="prerequisitesandconfig">Prerequisites and config</h3>
<p>If you plan on following steps, 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>) on AWS. Deploying this on AWS is required for Elastic Serverless Forwarder.</li>
<li>Ensure you have an AWS account with permissions to pull the necessary data from AWS. Specifically, ensure you can configure the agent to pull data from AWS as needed. <a href="https://docs.elastic.co/integrations/aws#requirements">Please look at the documentation for details</a>.</li>
<li>We used <a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop">AWS’s three-tier app</a> and installed it as instructed in GitHub. (<a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">See blog on ingesting metrics from the AWS services supporting this app</a>.)</li>
<li>Configure and install Elastic’s Serverless Forwarder.</li>
<li>Ensure you turn on VPC Flow Logs for the VPC where the application is deployed and send logs to AWS Firehose.</li>
</ul>
<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/blt7dce08a8b90bfacf/6a7f1c4e9090b01f8b84ee5f/blog-elastic-vpc-flow-logs-start-cloud-trial.png" alt="" /></p>
<h3 id="step1deployelasticonaws">Step 1: Deploy Elastic on AWS</h3>
<p>Once logged in to Elastic Cloud, create a deployment on AWS. It’s important to ensure that the deployment is on AWS. The Amazon Kinesis Data Firehose connects specifically to an endpoint that needs to be on AWS.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd7a8f13d7eadf6fb/6a7f1c51ead8ec01fbbaac88/blog-elastic-vpc-flow-logs-create-a-deployment.png" alt="" /></p>
<p>Once your deployment is created, make sure you copy the Elasticsearch endpoint.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaef67444a94269ce/6a7f1c55ea068d779af0a314/blog-elastic-vpc-flow-logs-aws-logs.png" alt="" /></p>
<p>The endpoint should be an AWS endpoint, such as:</p>
<pre><code>https://aws-logs.es.us-east-1.aws.found.io
</code></pre>
<h3 id="step2turnonelasticsawsintegrationsonaws">Step 2: Turn on Elastic’s AWS Integrations on AWS</h3>
<p>In your deployment’s Elastic Integration section, go to the AWS integration and select Install AWS assets.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718f23ad192c845a/6a7f1c58448e4ea69d5c0b9b/blog-elastic-vpc-flow-logs-aws-settings.png" alt="" /></p>
<h3 id="step3deployyourapplication">Step 3: Deploy your application</h3>
<p>Follow the instructions listed out in <a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop">AWS’s Three-Tier app</a> and instructions in the workshop link on GitHub. The workshop is listed <a href="https://catalog.us-east-1.prod.workshops.aws/workshops/85cd2bb2-7f79-4e96-bdee-8078e469752a/en-US">here</a>.</p>
<p>Once you’ve installed the app, get credentials from AWS. This will be needed for Elastic’s AWS integration.</p>
<p>There are several options for credentials:</p>
<ul>
<li>Use access keys directly</li>
<li>Use temporary security credentials</li>
<li>Use a shared credentials file</li>
<li>Use an IAM role Amazon Resource Name (ARN)</li>
</ul>
<p>View more details on specifics around necessary <a href="https://docs.elastic.co/en/integrations/aws#aws-credentials">credentials</a> and <a href="https://docs.elastic.co/en/integrations/aws#aws-permissions">permissions</a>.</p>
<h3 id="step4sendvpcflowlogstoamazons3andsetupamazonsqs">Step 4: Send VPC Flow Logs to Amazon S3 and set up Amazon SQS</h3>
<p>In the VPC for the application deployed in Step 3, you will need to configure VPC Flow Logs and point them to an Amazon S3 bucket. Specifically, you will want to keep it as AWS default format.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9a94d515651e20c/6a7f1c5b9090b0415f84ee6b/blog-elastic-vpc-flow-logs-create-flow-log.png" alt="" /></p>
<p>Create the VPC Flow log.</p>
<p>Next:</p>
<ul>
<li><a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-getting-started.html">Set up an Amazon SQS queue</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ways-to-add-notification-config-to-bucket.html">Configure Amazon S3 event notifications</a></li>
</ul>
<h3 id="step5setupelasticserverlessforwarderonaws">Step 5: Set up Elastic Serverless Forwarder on AWS</h3>
<p>Follow instructions listed in <a href="https://www.elastic.co/guide/en/observability/8.5/aws-deploy-elastic-serverless-forwarder.html">Elastic’s documentation</a> and refer to the <a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">previous blog</a> providing an overview. The important bits during the configuration in Lambda’s application repository are to ensure you:</p>
<ul>
<li>Specify the S3 Bucket in ElasticServerlessForwarderS3Buckets where the VPC Flow Logs are being sent. The value is the ARN of the S3 Bucket you created in Step 4.</li>
<li>Specify the configuration file path in ElasticServerlessForwarderS3ConfigFile. The value is the S3 url in the format "s3://bucket-name/config-file-name" pointing to the configuration file (sarconfig.yaml).</li>
<li>Specify the S3 SQS Notifications queue used as the trigger of the Lambda function in ElasticServerlessForwarderS3SQSEvents. The value is the ARN of the SQS Queue you set up in Step 4.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt683de353f5f3d00e/6a7f1c5eeab5bea4c420ab44/blog-elastic-vpc-flow-logs-application-settings.png" alt="" /></p>
<p>Once Amazon CloudFormation finishes setting up Elastic serverless forwarder, you should see two Amazon Lambda functions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a8541ccc1858f9d/6a7f1c61e02fac26945d69f3/blog-elastic-vpc-flow-logs-functions.png" alt="" /></p>
<p>In order to check if logs are coming in, go to the function with “ <strong>ApplicationElasticServer</strong> ” in the name, and go to monitor and look at <strong>logs</strong>. You should see the logs being pulled from S3.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt57ff998a2a6fea3a/6a7f1c64ead8ec5455baac94/blog-elastic-vpc-flow-logs-function-overview.png" alt="" /></p>
<h3 id="step6checkandensureyouhavelogsinelastic">Step 6: Check and ensure you have logs in Elastic</h3>
<p>Now that steps 1–4 are complete, you can go to Elastic’s Discover capability and you should see VPC Flow Logs coming in. In the image below, we’ve filtered by Amazon S3 bucket <strong>bshettisartest</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e7507f7a157ee2a/6a7f1c675967e5b74c5dd705/blog-elastic-vpc-flow-log-dashboard-filter.png" alt="" /></p>
<h2 id="conclusionelasticobservabilityeasilyintegrateswithvpcflowlogsforanalyticsalertingandinsights">Conclusion: Elastic Observability easily integrates with VPC Flow Logs for analytics, alerting, and insights</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you manage AWS VPC Flow Logs. Here’s a quick recap of lessons and what you learned:</p>
<ul>
<li>A walk-through of how Elastic Observability provides enhanced analysis for VPC Flow Logs:</li>
<li>Using Elastic’s Analytics Discover capabilities to manually analyze the data</li>
<li>Leveraging Elastic Observability’s anomaly features to:<ul>
<li>Identify anomalies in the VPC flow logs</li>
<li>Detects anomalous log entry rates</li>
<li>Automatically categorizes log messages</li></ul></li>
<li>Using an OOTB dashboard to further analyze data</li>
<li>A more detailed walk-through of how to set up the Elastic Serverless Forwarder</li>
</ul>
<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>
<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://www.elastic.co/blog/kubernetes-errors-elastic-observability-logs-openai">Using OpenAI to analyze Kubernetes errors</a></li>
<li><a href="https://youtu.be/Li5TJAWbz8Q">PostgreSQL issue analysis with AIOps</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/vpc-flow-logs-monitoring-analytics-observability</link>
    <guid isPermaLink="false">vpc-flow-logs-monitoring-analytics-observability</guid>
    <category><![CDATA[Logs Analytics]]></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/blt29d963458983cce0/6a7f1c6ab4377022bc4d7157/patterns-midnight-background-no-logo-observability.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Jan 2023 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>