<?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[Peter Simkins - 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[Peter Simkins - 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/author/peter-simkins</link>
    </image>
    <link>https://www.elastic.co/observability-labs/author/peter-simkins</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/author/peter-simkins.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 09:00:02 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Cut log storage costs with two Elasticsearch data tiers instead of four]]></title>
    <description><![CDATA[Elastic benchmarked one day of logs on SSD with everything older moved to frozen searchable snapshots, and it came out 16x cheaper than keeping all of it hot. The ILM policy and the cost model are both in the post.]]></description>
    <content:encoded><![CDATA[<p>For years, the standard Elastic storage story moved data through four tiers: hot, warm, cold, and frozen. Each step traded performance for cost. That model works, but at petabyte scale it adds operational overhead you do not need.</p>
<p>The simpler path is hot → frozen: one day on SSD for live troubleshooting, then <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">Index Lifecycle Management (ILM)</a> moves everything else to the frozen tier as <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">searchable snapshots</a> on blob storage. No warm tier. No cold tier. Frozen indices are partially mounted and read-only. You do not keep indexing into them. You do search them in Kibana and ES|QL, without a restore-then-query workflow.</p>
<p>Elasticsearch maps fields dynamically at ingest, so you do not need a rigid schema before you send logs, metrics, traces, or security logs. The same two-tier ILM policy can cover those data streams. Layer <a href="https://www.elastic.co/observability-labs/blog/elastic-streams-processing">Elastic Streams</a> on top when you need per-stream processing, drops, aggregation, downsampling, or <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">retention</a>. For the companion cost walkthrough, see <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Not every log deserves 90 days: per-stream retention in Elastic Streams</a>.</p>
<h2 id="whenfourdatatiersismorethanlogstorageneeds">When four data tiers is more than log storage needs</h2>
<p>Traditional Elastic Cloud Hosted deployments often follow a ladder like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8677545f66b3e8cb/6ab38c2f97bf871d82b23a63/hot-to-frozen-flow.png" alt="Increased telemetry retention with hot, warm, cold, and frozen data tiers and a snapshot repositor" /></p>
<p>Each transition requires ILM policy tuning, node sizing for warm and cold hardware profiles, and replica management across tiers. Frozen still depends on a snapshot repository, but you pass through warm and cold first.</p>
<p>At hundreds of terabytes or multiple petabytes per day, those middle tiers become expensive to operate and hard to explain to finance teams. You pay for spinning disks and replica shards on data that nobody queries at second-level latency anymore.</p>
<h2 id="howhottofrozenlogstorageworks">How hot-to-frozen log storage works</h2>
<p>A two-tier <a href="https://www.elastic.co/docs/deploy-manage/reference-architectures/hotfrozen-high-availability">hot/frozen architecture</a> collapses the ladder:</p>
<p>| Phase | Storage | Purpose |
| --- | --- | --- |
| <strong>Hot (1 day)</strong> | SSD, high IO | Active incidents, alerting, SLO burn rates, ongoing ingest |
| <strong>Frozen</strong> | Blob plus cache nodes | Audit, compliance, historical search (read-only searchable snapshots) |</p>
<p>After the first 24 hours, ILM converts indices to searchable snapshots on the <code>found-snapshots</code> repository that Elastic Cloud Hosted provisions against cloud object storage. Data stays queryable. You omit warm and cold entirely.</p>
<p>The hot-to-frozen pattern matches how Elastic documents large time-series Observability and Security deployments that do not require updates after ingest. New events continue to land on hot. Aged backing indices move to frozen.</p>
<p>Elastic benchmarked a related hot-frozen pattern on ECH with 105 TB of logs over 90 days. Frozen-tier Discover queries stayed within single-digit seconds at p99.9 for most tasks, and holding one day in hot plus the rest in frozen cost roughly 16x less than keeping the full dataset on hot nodes alone. See <a href="https://www.elastic.co/search-labs/blog/searchable-snapshots-benchmark">Measuring searchable snapshots performance</a>.</p>
<h2 id="whatpetabytescalelogstoragecostsonelasticcloudhosted">What petabyte-scale log storage costs on Elastic Cloud Hosted</h2>
<p>The figures below are an illustrative example based on a telco-scale workload model, not a quote or pricing commitment from Elastic. Your costs will vary with ingest volume, retention, query patterns, cloud region, hardware profile, and licensing tier. Use <a href="https://www.elastic.co/pricing/cloud-hosted">Elastic Cloud Hosted pricing</a> and your account team for project-specific estimates.</p>
<h3 id="howdoeselasticcloudhostedpricingdifferfromserverless">How does Elastic Cloud Hosted pricing differ from Serverless?</h3>
<p>Elastic Cloud Hosted (ECH) bills primarily on the memory footprint of the resources you run: hot and frozen tier node capacity (GB RAM-hour), plus snapshot storage and data transfer. Even with a two-tier hot-to-frozen ILM policy, you still size and pay for the Elasticsearch nodes and frozen cache tier that execute queries against blob-backed data.</p>
<p>Elastic Cloud Serverless bills on ingest volume and stored data, not provisioned cluster RAM. That model removes node sizing, capacity planning, and most lifecycle administration. You pay for what you send and what you keep.</p>
<p>This example models ECH only. Serverless is not included in the tables below because Observability Serverless does not yet support writing aged data out to S3 or blob storage for long-term frozen retention. When that capability ships, the same hot-to-blob pattern on Serverless should reduce administration further while keeping ingest-plus-storage pricing. Until then, ECH with hot-to-frozen ILM is the path for petabyte-scale audit and compliance on blob.</p>
<p>The example assumes 1-day hot, then ILM to blob (frozen searchable snapshots) for long-term retention. Treat the per-PB figures as a planning block for mixed telemetry, not as a claim that 1 PB of raw logs occupies the same disk as 1 PB of raw metrics. <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">Time series data streams</a> store metrics far more efficiently than a regular data stream. Calculations use published ECH capacity, snapshot storage, and data transfer rates with Enterprise licensing.</p>
<h3 id="logstoragecostat1pbperdaywithoutstreams">Log storage cost at 1 PB per day without Streams</h3>
<p>At 1 PB/day ingest in this example, ECH alone is modeled at $178K/month ($2.14M/year) using published rates from <a href="https://www.elastic.co/pricing/cloud-hosted">elastic.co/pricing/cloud-hosted</a>.</p>
<p>Scaling linearly:</p>
<p>| Daily ingest | ECH monthly | ECH annual |
| --- | --- | --- |
| 1 PB/day | $178K | $2.14M |
| 2 PB/day | $357K | $4.28M |
| 3 PB/day | $535K | $6.42M |
| 4 PB/day | $713K | $8.56M |
| 5 PB/day | $891K | $10.70M |
| 6.5 PB/day | $1.16M | $13.90M |
| 8.45 PB/day | $1.51M | $18.07M |</p>
<p>Even without Streams optimization, the two-tier model is built for petabyte-per-day scale: short hot retention, blob-backed frozen search, and no warm or cold hardware profiles. Competitor TCO depends on ingest mix, retention length, and whether other vendors bill long-term searchability as premium hot storage. Compare published ECH rates to your current quotes rather than treating any single percentage as a universal saving.</p>
<h3 id="howelasticstreamscutslogretentioncostsby28">How Elastic Streams cuts log retention costs by 28%</h3>
<p>Adding <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Elastic Streams</a> controls (drop, aggregate, downsample, retention) on top of the same hot-to-frozen ILM foundation reduces the example run rate by roughly 28%:</p>
<p>| Daily ingest | Streams optimized monthly | Streams optimized annual |
| --- | --- | --- |
| 1 PB/day | $127K | $1.52M |
| 2 PB/day | $254K | $3.04M |
| 3 PB/day | $380K | $4.56M |
| 4 PB/day | $507K | $6.08M |
| 5 PB/day | $634K | $7.60M |
| 6.5 PB/day | $824K | $9.88M |
| 8.45 PB/day | $1.07M | $12.85M |</p>
<p>At <strong>5 PB/day</strong> with <strong>30% year-over-year growth</strong>, the three-year comparison looked like this:</p>
<p>| Year | Daily volume | ECH annual | Streams optimized annual |
| --- | --- | --- | --- |
| Year 1 | 5 PB/day | $10.70M | $7.60M |
| Year 2 | 6.5 PB/day | $13.90M | $9.88M |
| Year 3 | 8.45 PB/day | $18.07M | $12.85M |</p>
<p>In this example, that is <strong>$12.3M lower over three years</strong> with Streams optimization compared to ECH alone. Use that delta as a sensitivity check, then adjust for telemetry mix. Metrics in a TSDS, downsampled series, and verbose logs do not share one storage multiplier.</p>
<p>When Serverless adds blob-tier retention for observability data, rerun this comparison on <a href="https://www.elastic.co/pricing/serverless-observability">Serverless pricing</a>. Ingest-plus-storage billing plus zero node administration should make Serverless the simpler operational choice for long-retention workloads at scale.</p>
<h2 id="ilmpolicyexampleonedayhotfrozenretentionforcompliance">ILM policy example: one day hot, frozen retention for compliance</h2>
<p>On ECH, attach a policy that rolls daily on hot and transitions to frozen searchable snapshots. Omit warm, cold, and delete phases when audit requirements call for indefinite retention on blob.</p>
<pre><code>PUT _ilm/policy/obs-hot-frozen
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "1d",
            "max_primary_shard_size": "50gb"
          }
        }
      },
      "frozen": {
        "min_age": "1d",
        "actions": {
          "searchable_snapshot": {
            "snapshot_repository": "found-snapshots"
          }
        }
      }
    }
  }
}
</code></pre>
<p>The <code>searchable_snapshot</code> action in the frozen phase mounts a read-only, partially mounted snapshot (<code>partial-*</code>). Keep ingest on hot. Point historical Discover and ES|QL at frozen.</p>
<p>Apply the policy through index templates, Kibana Index Management, or the <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">Streams Retention tab</a>. Frozen tier nodes cache frequently queried blob regions locally. First access to a new time range may be slower; repeat queries hit cache and perform closer to hot-tier latency.</p>
<p>For regulated workloads, pair indefinite frozen retention with Streams AI Partitioning so compliance-heavy streams (payments audit, security events) inherit longer lifetimes while verbose debug streams get shorter DSL delete policies on child streams.</p>
<h2 id="whyelasticsearchdatatiersworkforpetabytescalelogstorage">Why Elasticsearch data tiers work for petabyte-scale log storage</h2>
<p>Among managed observability platforms, Elastic Cloud Hosted is the only option we are aware of that combines:</p>
<ul>
<li><strong>Petabyte-scale telemetry ingest</strong> on one platform for logs, metrics, traces, security logs, and more.</li>
<li><strong>Two-tier ILM</strong> from hot to frozen without warm or cold hardware profiles.</li>
<li><strong>Searchable snapshots</strong> that query blob storage directly, with no rehydration step.</li>
<li><strong>Published-rate TCO modeling</strong> you can run against <a href="https://www.elastic.co/pricing/cloud-hosted">ECH pricing</a> before you talk to finance.</li>
</ul>
<p>Competing observability SaaS tools often charge premium rates for long hot retention, bill per telemetry type, or move aged data to archives that are not searchable with the same query tools you use for live incidents. At petabyte-per-day ingest, those pricing models widen the gap. Elastic built tiering into the search engine itself, which is why Discover, ES|QL, and APM views work across hot and frozen data transparently.</p>
<h2 id="howtosizehotandfrozennodesforpetabytescalelogstorage">How to size hot and frozen nodes for petabyte-scale log storage</h2>
<p>Exact node counts depend on daily ingest, query concurrency, and hardware profile. A practical starting ratio from Elastic benchmarks:</p>
<ul>
<li>Hot nodes sized for one day of ingest plus indexing headroom.</li>
<li>Frozen cache nodes sized for concurrent query patterns, not total blob volume.</li>
</ul>
<p>Before production rollout:</p>
<ol>
<li>Measure daily telemetry ingest (logs, metrics, traces, security logs).</li>
<li>Pilot the one-day hot policy on a representative data stream.</li>
<li>Run Discover and ES|QL queries against frozen data to validate cache sizing.</li>
<li>Apply Streams drops and partitioning to reduce volume entering hot.</li>
</ol>
<p>Keep alert evaluation and SLO burn-rate queries on hot. Plan incident workflows that start on the last 24 hours, then expand into frozen for historical context. Frozen queries can show higher latency on first access after cache eviction.</p>
<p>Searchable snapshots and the frozen tier require appropriate Elastic Cloud capabilities. Review <a href="https://www.elastic.co/pricing/">Elastic Cloud pricing</a> and your deployment tier before enabling frozen at scale.</p>
<h2 id="howtomoveyourlogretentionpolicytohottofrozen">How to move your log retention policy to hot-to-frozen</h2>
<ul>
<li>Replace warm and cold phases with a hot-to-frozen ILM policy on observability data streams.</li>
<li>Model TCO at your ingest rate using published ECH pricing, then add Streams optimization scenarios.</li>
<li>Send OpenTelemetry telemetry to wired streams and apply <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Streams TCO controls</a>.</li>
<li>Read the <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">searchable snapshots documentation</a> and <a href="https://www.elastic.co/docs/manage-data/lifecycle/data-tiers">data tiers guide</a>.</li>
</ul>
<p>Petabyte observability does not require four storage tiers or choosing between cost and searchability. One day on hot, everything else in frozen searchable snapshots on blob, and Streams dials on top: that is how Elastic Cloud Hosted keeps full history queryable at a TCO finance teams can approve.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/log-storage-petabyte-scale-hot-frozen-ilm</link>
    <guid isPermaLink="false">log-storage-petabyte-scale-hot-frozen-ilm</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3aad0dcdb96aff20/6ab38abfa8762889f85d6d6d/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Not every log deserves 90 days: per-stream retention in Elastic Streams]]></title>
    <description><![CDATA[AI Partitioning reads your data and proposes child streams. Retention, drops and downsampling then become per-stream settings, so the noisy ones expire on their own schedule.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/observability/streams/streams">Elastic Streams</a> lets you set observability data retention per stream, so a payments API stream can keep 90 days while a notifications worker keeps 7. AI Partitioning reads the incoming firehose and proposes the child streams. Drops, retention and downsampling are then settings in the Streams UI, not routing rules you maintain somewhere else. Teams moving off a single 30-day policy typically cut 20–40% of log volume at ingest and downsample metrics after the first few days.</p>
<p>The controls work the same on Elastic Cloud Serverless, Elastic Cloud Hosted (ECH), and self-managed clusters. Elasticsearch uses <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">dynamic mapping</a> (schema-on-write with automatic type detection), so the same Streams controls apply to logs, metrics, traces, and any other signal you ingest over OpenTelemetry or native shippers, with no fixed schema required up front.</p>
<blockquote>
  <p><strong>UI note (Stack 9.5+):</strong> the Streams <strong>Retention</strong> tab was renamed <strong>Data lifecycle</strong>. This article uses <strong>Data lifecycle</strong> for current GA. Screenshots captured on earlier builds may still show the old label; the controls themselves are unchanged. See <a href="https://www.elastic.co/docs/solutions/observability/streams/configure-retention">Configure data lifecycle with Streams</a>.</p>
</blockquote>
<h2 id="whyobservabilitydatastoragecostsgetoutofhand">Why observability data storage costs get out of hand</h2>
<p>Most teams start with one retention policy for everything. That is simple until:</p>
<ul>
<li>Debug logs from a load generator sit next to payment audit events in the same data stream.</li>
<li>High-cardinality metrics keep growing while only a handful of dimensions matter after a week.</li>
<li>Trace volume spikes during deploys, but you still pay to store every span at full resolution for 90 days.</li>
</ul>
<p>The usual fixes (sampling everything globally, turning off logging, or maintaining brittle routing rules in ingest pipelines) trade away visibility for cost. Streams takes a different path: identify patterns first, then apply the right storage policy to each pattern.</p>
<h2 id="observabilitydataretentioncontrolsinelasticstreams">Observability data retention controls in Elastic Streams</h2>
<p>Streams sits on top of Elasticsearch data streams. Each stream gets its own Data lifecycle tab, processing pipeline, and (for wired streams) a place in a parent/child tree. That structure is what turns generic "reduce observability spend" goals into concrete settings you can audit.</p>
<p><strong>Streams-native controls</strong> (configure these in the Streams UI):</p>
<p>| Control | What it does | Typical signal |
| --- | --- | --- |
| <strong>Drop</strong> | Stop indexing matched documents before they hit storage | Noisy debug logs, health checks |
| <strong>Retention (DSL or ILM)</strong> | Delete or tier data after a defined age via the Data lifecycle tab | Short-lived app logs vs compliance logs |
| <strong>Downsample</strong> | Replace high-resolution metrics with fixed intervals (from Elastic 9.4) | Infrastructure metrics after 7 days |</p>
<p><strong>Adjacent Elasticsearch capabilities</strong> that compound the savings (not separate Streams actions, but important for TCO):</p>
<p>| Capability | What it does | Typical signal |
| --- | --- | --- |
| <strong>TSDS storage efficiency</strong> | Dimension-based routing, <code>_tsid</code>/<code>@timestamp</code> sorting, and synthetic <code>_source</code> improve compression; identical <code>_tsid</code> + timestamp data points are rejected | OTel or Prometheus metrics |
| <strong>Ingest processors / transforms</strong> | Processors reshape or drop individual documents before indexing; <a href="https://www.elastic.co/docs/explore-analyze/transforms/transform-overview">transforms</a> pivot already-indexed data into summary destinations on a schedule | High-volume counters, enrichment |
| <strong>ILM tiering</strong> | Move older data to warm, cold, or frozen storage (exposed in Streams Data lifecycle on ECH/on-prem) | Long-lived logs and metrics |</p>
<p>On Elastic Cloud Serverless, you also benefit from storage optimizations built into the platform (for example, <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings">LogsDB index mode for logs</a> and efficient TSDB storage for metrics). Streams helps you reduce what you store by routing and trimming data before Serverless bills reflect the volume.</p>
<p>On ECH and self-managed deployments, you get the full Elasticsearch lifecycle toolkit: ILM phases, data stream lifecycle (DSL), searchable snapshots, and downsampling. Streams exposes those policies in the UI so you are not hunting through JSON policy files.</p>
<h3 id="whichcontroltouse">Which control to use</h3>
<p>Drop, retention and downsampling solve different problems, and the cost of each is paid at a different point.</p>
<p>| Control | What you still pay for | What you give up | Best fit |
| --- | --- | --- | --- |
| <strong>Drop</strong> | Nothing. Matched documents are never indexed. | The data, permanently. No reindex, no recovery. | Health checks, load generator output, debug namespaces |
| <strong>Retention (DSL)</strong> | Full storage until the age limit, then nothing. | Any query beyond the retention window. | Short-lived application logs, anything with a compliance floor |
| <strong>Retention (ILM tiering)</strong> | A lower cost per GB as data moves to warm, cold, or frozen. | Query speed on older data. Frozen needs searchable snapshots. | Long-lived logs and metrics on ECH or self-managed |
| <strong>Downsample</strong> | One data point per interval instead of every sample. | Resolution. Original samples are replaced, not kept alongside. | Infrastructure metrics and other high cardinality metrics after the first week |</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta6cdaac2a6941ffd/6ab2b4e2a8762834575d6980/retention-view.png" alt="Data lifecycle tab in Elastic Streams showing storage size, ingestion rate, and lifecycle phases" /></p>
<h2 id="step1findlogpatternswithaipartitioning">Step 1: Find log patterns with AI Partitioning</h2>
<p>You cannot right-size retention until you know what is in the firehose. <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning">Streams AI Partitioning</a> analyzes incoming documents and proposes child streams based on field patterns, service names, log shapes, or plain-English instructions.</p>
<p>A common starting point is the wired <code>logs.otel</code> stream created when you send OpenTelemetry logs to the <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-for-opentelemetry">Managed OTLP endpoint</a>. The same idea applies to metrics and traces wired streams as your deployment ingests them.</p>
<h3 id="howtorunaipartitioning">How to run AI Partitioning</h3>
<ol>
<li>Open <strong>Observability &gt; Streams</strong> and select a parent wired stream.</li>
<li>Run <strong>AI Partitioning</strong> on a representative time range.</li>
<li>Review suggested child streams (for example per team, service, or log level).</li>
<li>Refine partitions in natural language if the first pass merges things you want separated.</li>
<li>Apply partitions so each child stream inherits processing and retention from the parent, with per-child overrides.</li>
</ol>
<p>Once partitioned, a payments API stream can keep 90 days while a notifications worker keeps 7. You set that in the UI instead of maintaining reroute processors in Logstash or the OTel Collector.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b0954b0b672c3a5/6ab2b4faa8762824225d6984/ai-partitioning-retention.png" alt="Per-team retention after Streams AI Partitioning replaces a single shared retention policy" /></p>
<p>For a full walkthrough, see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-streams-ai-partitioning-log-routing">One OTLP endpoint, three teams, zero routing rules</a>.</p>
<h2 id="step2logfilteringinstreamsinsteadofyourtelemetrypipeline">Step 2: Log filtering in Streams instead of your telemetry pipeline</h2>
<p>Partitioning tells you <em>who</em> the data belongs to. Processing tells you <em>what to keep</em>.</p>
<p>In the Streams <strong>Processing</strong> tab you can:</p>
<ul>
<li>Add <strong>conditional blocks</strong> scoped to one child stream or log shape.</li>
<li>Use <strong>AI-generated Grok patterns</strong> to parse samples instead of hand-writing regex.</li>
<li>Add a <strong>drop processor</strong> for documents that should never be indexed.</li>
</ul>
<p>This preview-first workflow matters for TCO. You inspect matched, parsed, and dropped samples before anything goes live. That is safer than a pipeline change that silently discards the wrong field.</p>
<p>Example: drop load-generator logs inside a Kubernetes containers stream while keeping application errors. The <a href="https://www.elastic.co/observability-labs/blog/kubernetes-logs-elastic-streams-processing">Kubernetes logs processing walkthrough</a> shows the full flow with the Dropped tab preview.</p>
<p>Drops are the most direct control when the data has zero long-term value. You pay nothing to store what never gets indexed.</p>
<h2 id="step3setlogretentionperstreaminthedatalifecycletab">Step 3: Set log retention per stream in the Data lifecycle tab</h2>
<p>After partitioning, open the <strong>Data lifecycle</strong> tab (formerly <strong>Retention</strong> prior to Stack 9.5) on each child stream.</p>
<p>You will see:</p>
<ul>
<li>Current <strong>storage size</strong> and projected ingestion rates.</li>
<li><strong>Lifecycle phase breakdown</strong> when ILM is active (hot, warm, cold, frozen).</li>
<li>Options to switch between <strong>DSL</strong> (simple age-based retention) and <strong>ILM</strong> (tiered storage).</li>
</ul>
<p>Wired streams support <strong>inheritance</strong>: set retention on a parent stream and let children inherit it, or override specific children that need longer or shorter lifetimes. Classic streams can follow existing index template policies while still giving you Streams visibility.</p>
<p>For DSL and ILM editing in the UI, see <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">How Streams simplifies retention management</a> and <a href="https://www.elastic.co/docs/solutions/observability/streams/configure-retention">Configure data lifecycle with Streams</a>.</p>
<h2 id="step4downsamplehighcardinalitymetricsandrolluptimeseries">Step 4: Downsample high cardinality metrics and roll up time series</h2>
<p>Logs are often about retention length and drops. Metrics are often about resolution over time.</p>
<p>Starting in Elastic 9.4, you can configure downsampling directly in the Streams Data lifecycle tab for both ILM-backed and DSL-managed streams. Pick a phase, set a <code>fixed_interval</code>, and preview the timeline before you save.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt588176e9c1003450/6ab2b5127e66d4d5420163e1/downsampling-flyout.png" alt="ILM phase flyout with downsample interval and live timeline preview in Elastic Streams" /></p>
<h3 id="exampleilmpolicywithhotandcolddownsampling">Example ILM policy with hot and cold downsampling</h3>
<pre><code>{
  "phases": {
    "hot": {
      "min_age": "0ms",
      "actions": {
        "rollover": { "max_age": "1d" },
        "downsample": {
          "fixed_interval": "5m",
          "wait_timeout": "1d"
        }
      }
    },
    "cold": {
      "min_age": "4d",
      "actions": {
        "downsample": {
          "fixed_interval": "10m",
          "wait_timeout": "1d"
        }
      }
    },
    "delete": {
      "min_age": "30d",
      "actions": { "delete": {} }
    }
  }
}
</code></pre>
<h3 id="howtsdsreducesmetricsstorage">How TSDS reduces metrics storage</h3>
<p>Elasticsearch <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">Time Series Data Streams (TSDS)</a> cuts storage for OTel and Prometheus metrics through dimension-based routing (each time series lands on one shard), index sorting by <code>_tsid</code> and <code>@timestamp</code> that co-locates repeated values for better compression, and synthetic <code>_source</code>. TSDS also rejects duplicate data points with an identical <code>_tsid</code> and timestamp. Pair TSDS with downsampling for metrics that only need minute-level granularity after the first week.</p>
<p>See <a href="https://www.elastic.co/observability-labs/blog/configure-downsampling-elastic-streams">Configure downsampling directly in Elastic Streams</a> for UI steps and shared-policy warnings.</p>
<h2 id="storagetiersbydeploymenttype">Storage tiers by deployment type</h2>
<h3 id="observabilitydataretentiononelasticcloudserverless">Observability data retention on Elastic Cloud Serverless</h3>
<p>Serverless removes cluster sizing work, but you still control what lands in storage. Streams partitioning, drops, and per-stream retention directly affect ingest volume and retention duration, which drives Serverless observability costs.</p>
<p>Platform-level optimizations (LogsDB for logs, columnar TSDB improvements for metrics) stack on top of the policies you set in Streams. You get efficient storage and intentional data lifecycle management.</p>
<p>If you only need log analytics at a lower price point, <a href="https://www.elastic.co/observability-labs/blog/log-analytics-elastic-serverless-logs-essentials">Logs Essentials on Serverless</a> is another option. Streams TCO controls still apply when you need per-team retention on Complete.</p>
<h3 id="storagetiersonelasticcloudhostedandselfmanaged">Storage tiers on Elastic Cloud Hosted and self-managed</h3>
<p>ECH and on-prem deployments add storage tiering: hot SSD, warm, cold, frozen, and searchable snapshots. ILM moves streams across tiers automatically. DSL offers a simpler delete-after-N-days model when you do not need tiering.</p>
<p>The same Streams UI and APIs apply. The difference is you also choose hardware profiles and snapshot repositories behind the policies.</p>
<h2 id="howretentiondiffersforlogsmetricsandtraces">How retention differs for logs, metrics and traces</h2>
<p>Elasticsearch does not require a fixed schema at ingest. OpenTelemetry logs, metrics, and traces can land in wired streams, get partitioned by AI on whatever fields appear in your data, and pick up stream-specific retention.</p>
<p>That matters for TCO because observability signals are not isolated cost lines:</p>
<ul>
<li><strong>Logs</strong>: drop noise, shorten retention on verbose services, keep audit trails longer.</li>
<li><strong>Metrics</strong>: use TSDS storage efficiency, downsample after incident windows pass.</li>
<li><strong>Traces</strong>: partition by service or environment, align retention with sampling policies.</li>
</ul>
<p>You are not maintaining three different cost systems. You are applying the same Streams primitives to each signal type.</p>
<h2 id="suggestedobservabilitydataretentionworkflow">Suggested observability data retention workflow</h2>
<ol>
<li><strong>Ingest to wired streams</strong> (for example <code>logs.otel</code>, metrics, or traces OTel endpoints).</li>
<li><strong>Partition with AI</strong> so each team or service gets its own stream.</li>
<li><strong>Drop</strong> known noise (health checks, load tests, debug namespaces).</li>
<li><strong>Set retention</strong> per stream based on compliance and debugging needs.</li>
<li><strong>Downsample or tier</strong> metrics and high-volume logs on ECH/on-prem.</li>
<li><strong>Review the Data lifecycle tab monthly</strong> using ingestion graphs to catch drift.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1017908443d6f216/6ab2b915f3b126049a6506b0/tco-control-flow.png" alt="Elastic Streams TCO control flow from ingest to AI partition to per-stream drop, retention, and downsample, then to storage tier or delete" /></p>
<h2 id="howmuchstoragecanyouactuallysave">How much storage can you actually save?</h2>
<p>Exact savings depend on your workload. Teams that previously kept everything for 30 days often see the largest wins from:</p>
<ul>
<li>Dropping 20–40% of debug or duplicate logs at ingest.</li>
<li>Shortening retention on high-volume, low-value streams.</li>
<li>Downsampling metrics after the first few days.</li>
</ul>
<p>Combine those controls with Elasticsearch storage modes (LogsDB, TSDS) for compounding effects. Measure before and after using the storage size metric on each stream's Data lifecycle tab and cluster-level <code>_stats</code> where you have access.</p>
<h2 id="tryperstreamretentionononenoisystream">Try per-stream retention on one noisy stream</h2>
<ul>
<li>Enable wired streams and send sample OTel data to <code>logs.otel</code>.</li>
<li>Run <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning">AI Partitioning</a> on a busy parent stream.</li>
<li>Set one aggressive retention policy on a noisy child stream and compare storage after 48 hours.</li>
<li>Read the <a href="https://www.elastic.co/docs/solutions/observability/streams/streams">Streams documentation</a> for API options (<code>Upsert stream</code>, ingest settings) when you want to automate policies in GitOps.</li>
</ul>
<p>Observability TCO is not about collecting less data by default. It is about storing the right data for the right duration. Streams gives you the map, the scissors, and the dial.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams</link>
    <guid isPermaLink="false">observability-data-retention-elastic-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe7a23471d082529/6ab2b49c7c5f02bd966f3147/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 22 Sep 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>
  </channel>
</rss>