Kubernetes v1.37: Native Histograms Graduates to Beta (kubernetes.io)
<p>I'm excited to announce that native histogram support for Kubernetes metrics is graduating to Beta and is enabled by default in Kubernetes v1.37!</p>
<p><em>Native histograms</em> (previously introduced as Alpha in Kubernetes v1.36 under <a href="https://www.kubernetes.dev/resources/keps/5808/">KEP-5808</a>)
bring high-resolution, low-cardinality observability to Kubernetes metrics.
By adopting <a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms</a>, Kubernetes components now expose latency and duration metrics with far greater accuracy while significantly reducing telemetry storage and scraping overhead.</p>
<h2 id="why-move-beyond-classic-histograms">Why move beyond classic histograms?<a class="td-heading-self-link" href="#why-move-beyond-classic-histograms" aria-label="Heading self-link"></a></h2><p>Since the early days of Kubernetes observability, duration and latency metrics (such as API server request latencies or scheduling durations) have relied on <strong>classic Prometheus histograms</strong>.</p>
<p>Classic histograms require metric authors to define a static list of cumulative bucket boundaries (<code>le</code> labels), such as <code>0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10</code>. While familiar, this approach introduces three major challenges:</p>
<ol>
<li><strong>The Bucket Guessing Game</strong>: If a workload's latency profile changes, for example, shifting into microsecond ranges or experiencing long-tail tail latencies beyond the highest bucket, the histogram loses visibility. Specifying bucket boundaries upfront requires knowing the distribution before observing it</li>
<li><strong>High Cardinality & Storage Cost</strong>: With classic histograms, each bucket boundary is exported as a separate time series (<code>_bucket{le="..."}</code>). A histogram with 10 buckets across multiple labels multiplies the number of time series by 10, increasing memory consumption in Prometheus and inflating time series database (TSDB) storage costs</li>
<li><strong>Interpolation Error in Quantiles</strong>: Calculating percentiles using <code>histogram_quantile()</code> relies on linear interpolation between static bucket boundaries. When bucket spans are coarse, quantile calculations can suffer from significant estimation error</li>
</ol>
<h2 id="what-are-prometheus-native-histograms">What are Prometheus native histograms?<a class="td-heading-self-link" href="#what-are-prometheus-native-histograms" aria-label="Heading self-link"></a></h2><p><a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms</a> replace static user-defined buckets with <strong>dynamic, exponential buckets</strong>.</p>
<p>Instead of emitting a separate time series for every single bucket boundary, a <em>native histogram</em> is stored as a single time series containing a rich schema of positive and negative spans, zero thresholds, and exponential scaling factors.</p>
<ul>
<li><strong>High Resolution Automatically</strong>: Exponential buckets dynamically adjust to any value range — from nanoseconds to hours — without requiring pre-configured bucket boundaries</li>
<li><strong>Up to 90% Fewer Time Series</strong>: By consolidating buckets into structured spans within a single time series, scraping and storage overhead are dramatically reduced</li>
<li><strong>Accurate Quantile Calculation</strong>: Quantiles can be calculated with mathematical bounds on error (≃5% worst-case relative error under default settings) across the entire spectrum of observations</li>
</ul>
<h2 id="how-native-histograms-work-in-kubernetes">How native histograms work in Kubernetes<a class="td-heading-self-link" href="#how-native-histograms-work-in-kubernetes" aria-label="Heading self-link"></a></h2><p>In Kubernetes, native histogram support is implemented directly inside the shared metrics subsystem (<code>k8s.io/component-base/metrics</code>).</p>
<p>Figure 1 illustrates how native histogram metrics are processed and exposed across Kubernetes components.</p>
<figure class="diagram-large "><a href="https://kubernetes.io/images/blog/2026-kubernetes-1-37-native-histograms-beta/native-histograms-flow.svg">
<img src="https://kubernetes.io/images/blog/2026-kubernetes-1-37-native-histograms-beta/native-histograms-flow.svg"
alt="Diagram showing native histogram metric registration, exponential options configuration, and dual exposition flow in Kubernetes components"/> </a><figcaption>
<p>Figure 1. Native histogram processing and dual exposition flow in Kubernetes.</p>
</figcaption>
</figure>
<h3 id="1-dual-exposition-for-zero-breaking-changes">1. Dual exposition for zero breaking changes<a class="td-heading-self-link" href="#1-dual-exposition-for-zero-breaking-changes" aria-label="Heading self-link"></a></h3><p>A primary design requirement for KEP-5808 was <strong>zero disruption</strong> for existing observability stacks. When the <code>NativeHistograms</code> feature gate is enabled, Kubernetes components use <strong>dual exposition</strong>:</p>
<ul>
<li><strong>Classic buckets (<code>h.Bucket</code>)</strong> are still emitted alongside native spans. Existing Prometheus servers, dashboards, and alerting rules that rely on traditional text scraping or classic bucket labels continue to work unmodified</li>
<li><strong>Native spans (<code>h.Schema</code>, <code>h.PositiveSpan</code>)</strong> are included in the same Protobuf payload for collectors that understand native histograms</li>
</ul>
<h3 id="2-tuned-default-exponential-configuration">2. Tuned default exponential configuration<a class="td-heading-self-link" href="#2-tuned-default-exponential-configuration" aria-label="Heading self-link"></a></h3><p>When <code>NativeHistograms</code> is enabled, the <code>k8s.io/component-base/metrics</code> package automatically applies standardized exponential options to all histogram metrics:</p>
<ul>
<li><strong><code>BucketFactor: 1.1</code></strong>: Configures exponential buckets where each bucket is at most 10% wider than the preceding one. This guarantees a mathematically bounded worst-case relative error of at most ~5% for quantile calculations regardless of whether an operation takes 1 millisecond or 10 seconds.</li>
<li><strong><code>MaxBucketNumber: 160</code></strong>: Caps the maximum number of buckets per histogram to 160. Following OpenTelemetry SDK recommendations for base-2 exponential histogram aggregation, this limit protects component memory usage even under extreme outlier distributions.</li>
</ul>
<h3 id="3-broad-component-support">3. Broad component support<a class="td-heading-self-link" href="#3-broad-component-support" aria-label="Heading self-link"></a></h3><p>Because native histograms are integrated into <code>component-base/metrics</code>, all major Kubernetes control plane and node components inherit support automatically, including:</p>
<ul>
<li><strong><code>kube-apiserver</code></strong> (e.g., <code>apiserver_request_duration_seconds</code>, authentication/authorization metrics, validation latencies)</li>
<li><strong><code>kube-scheduler</code></strong> (e.g., <code>scheduler_plugin_execution_duration_seconds</code>, <code>scheduler_scheduling_algorithm_duration_seconds</code>)</li>
<li><strong><code>kubelet</code></strong> (node-level container runtime and pod lifecycle metrics)</li>
<li><strong><code>kube-controller-manager</code></strong> and <strong><code>kube-proxy</code></strong></li>
</ul>
<h2 id="how-to-scrape-native-histograms">How to scrape native histograms<a class="td-heading-self-link" href="#how-to-scrape-native-histograms" aria-label="Heading self-link"></a></h2><p>The simple answer: upgrade to Kubernetes v1.37, and it works.</p>
<p>Because Kubernetes v1.37 enables <code>NativeHistograms</code> by default, your cluster is already emitting dual-exposition metrics. How you configure Prometheus to scrape native histograms depends on your Prometheus version:</p>
<h3 id="1-prometheus-scrape-configuration-by-version">1. Prometheus scrape configuration by version<a class="td-heading-self-link" href="#1-prometheus-scrape-configuration-by-version" aria-label="Heading self-link"></a></h3><ul>
<li>
<p><strong>Prometheus 3.0+ (Recommended)</strong>: Use explicit per-job configuration in your <code>scrape_configs</code> rather than global flags (the global <code>--enable-feature=native-histograms</code> flag is deprecated in Prometheus 3.9+):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">scrape_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">'kubernetes-apiservers'</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">scrape_native_histograms</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">always_scrape_classic_histograms</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># Recommended during transition</span><span class="w">
</span></span></span></code></pre></div><p>You must read the caution in <a href="https://kubernetes.io/docs/reference/instrumentation/native-histograms/#migrating-dashboards-and-alerts">Migrating dashboards and alerts</a> in the Native Histograms documentation. In summary: always set <code>always_scrape_classic_histograms: true</code> during your transition period. Without this setting, Prometheus will only ingest the native format and stop ingesting classic <code>_bucket</code>, <code>_count</code>, and <code>_sum</code> series. Setting <code>always_scrape_classic_histograms: true</code> ensures existing dashboards (<code>histogram_quantile(..._bucket...)</code>) and alerts continue to work while you migrate them to native histograms.</p>
</li>
<li>
<p><strong>Prometheus 2.40 – 2.x</strong>: Enable Native Histograms globally by starting Prometheus with the feature flag:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">prometheus --enable-feature<span class="o">=</span>native-histograms
</span></span></code></pre></div><p>Note that in Prometheus 2.x, this is an all-or-nothing setting for all scrape targets.</p>
</li>
</ul>
<h3 id="2-verify-protobuf-dual-exposition">2. Verify Protobuf dual exposition<a class="td-heading-self-link" href="#2-verify-protobuf-dual-exposition" aria-label="Heading self-link"></a></h3><p>Standard Prometheus text scraping (<code>application/openmetrics-text</code> or plain text format) only transfers classic buckets. When <code>scrape_native_histograms</code> is enabled, Prometheus automatically negotiates <strong>Protobuf format</strong> with Kubernetes endpoints.</p>
<p>You can verify that a Kubernetes component is exporting native histograms using <code>curl</code> with an <code>Accept</code> header specifying Protobuf. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">## THIS IS NOT SECURE. ONLY DO THIS IN A TEST CONTEXT.</span>
</span></span><span class="line"><span class="cl">curl --insecure <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -H <span class="s2">"Accept: application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --header <span class="s2">"Authorization: Bearer </span><span class="k">$(</span>cat /var/run/secrets/kubernetes.io/serviceaccount/token<span class="k">)</span><span class="s2">"</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> https://localhost:6443/metrics
</span></span></code></pre></div><p>When decoded, the returned <code>MetricFamily</code> for histogram metrics (like <code>apiserver_request_duration_seconds</code>) will contain both traditional <code>bucket</code> entries and populated <code>schema</code> / <code>positive_span</code> fields.</p>
<h2 id="querying-native-histograms-in-promql">Querying native histograms in PromQL<a class="td-heading-self-link" href="#querying-native-histograms-in-promql" aria-label="Heading self-link"></a></h2><p>Once native histograms are ingested into Prometheus, you can query them using standard PromQL histogram functions without needing static <code>le</code> bucket labels or <code>_bucket</code> suffixes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-promql" data-lang="promql"><span class="line"><span class="cl"><span class="c1"># 1. Calculating P99 latency for a single target:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Classic histogram (requires _bucket suffix):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds_bucket</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Native histogram (operates directly on the metric name):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># 2. Aggregating across multiple instances (e.g., all API servers):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Classic histogram (requires sum by (le) to preserve bucket boundaries):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="k">sum</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="o">(</span><span class="nv">le</span><span class="o">)</span><span class="w"> </span><span class="o">(</span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds_bucket</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Native histogram (no grouping by le required!):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="k">sum</span><span class="o">(</span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">)))</span><span class="w">
</span></span></span></code></pre></div><p>With native histograms, functions like <code>histogram_quantile()</code> operate directly on the dynamic exponential spans inside the time series, producing highly accurate quantiles without static bucket interpolation error.</p>
<p>For official documentation on querying Native Histograms in PromQL, see:</p>
<ul>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile">PromQL <code>histogram_quantile</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_fraction">PromQL <code>histogram_fraction</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count-and-histogram_sum">PromQL <code>histogram_sum</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count-and-histogram_sum">PromQL <code>histogram_count</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count">PromQL <code>histogram_count</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_stddev-and-histogram_stdvar">PromQL <code>histogram_stddev</code> and <code>histogram_stdvar</code> documentation</a></li>
</ul>
<h2 id="dashboard-migration-rollback-strategy">Dashboard migration & rollback strategy<a class="td-heading-self-link" href="#dashboard-migration-rollback-strategy" aria-label="Heading self-link"></a></h2><h3 id="recommended-migration-workflow">Recommended migration workflow<a class="td-heading-self-link" href="#recommended-migration-workflow" aria-label="Heading self-link"></a></h3><p>To safely transition your monitoring infrastructure to Native Histograms without breaking existing alerts or dashboards, I recommend a four-step migration workflow:</p>
<ol>
<li><strong>Enable Both Formats</strong>: In your Prometheus 3.x scrape config, set <code>scrape_native_histograms: true</code> AND <code>always_scrape_classic_histograms: true</code> so both formats are collected safely during transition</li>
<li><strong>Migrate Queries</strong>: Update your Grafana dashboards and Prometheus alerting rules from classic quantile queries (<code>histogram_quantile(..._bucket...)</code>) to native histogram queries (<code>histogram_quantile(...)</code>), and replace references to classic <code>_count</code> and <code>_sum</code> series with <code>histogram_count(...)</code> and <code>histogram_sum(...)</code></li>
<li><strong>Verify in Staging/Production</strong>: Validate that all dashboards and SLO alerts fire and graph correctly using the new native histogram queries</li>
<li><strong>Unlock ~10x Storage Savings</strong>: Once migration is complete, set <code>always_scrape_classic_histograms: false</code>. Prometheus will stop ingesting the static <code>_bucket</code>, <code>_count</code>, and <code>_sum</code> time series, reducing your histogram time series count by up to 90%!</li>
</ol>
<h3 id="opt-out-and-rollback-flexibility">Opt-out and rollback flexibility<a class="td-heading-self-link" href="#opt-out-and-rollback-flexibility" aria-label="Heading self-link"></a></h3><p>Because native histograms are dual-exposed, using them is entirely opt-in from a collector perspective:</p>
<ul>
<li><strong>Instant Collector Rollback</strong>: If you need to stop ingesting native histograms, simply set <code>scrape_native_histograms: false</code> in your Prometheus job configuration. No Kubernetes restart is required, and Prometheus will immediately resume scraping only the classic format without data loss</li>
<li><strong>Component Feature Gate Rollback</strong>: Administrators can also disable the feature gate on Kubernetes components using <code>--feature-gates=NativeHistograms=false</code> (requires component restart)</li>
</ul>
<h2 id="what-s-next-how-to-get-involved">What's next & how to get involved<a class="td-heading-self-link" href="#what-s-next-how-to-get-involved" aria-label="Heading self-link"></a></h2><p>As native histograms progress toward General Availability (GA) in future Kubernetes releases, SIG Instrumentation will continue evaluating ecosystem readiness, performance characteristics, and long-term plans for eventually deprecating static classic buckets once native histogram adoption becomes ubiquitous across the monitoring community.</p>
<ul>
<li>Read the <a href="https://www.kubernetes.dev/resources/keps/5808/">KEP-5808 page</a> or the <a href="https://kep.k8s.io/5808">KEP GitHub issue</a> to learn more.</li>
<li>Read the <a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms specification</a> and <a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile">PromQL querying functions documentation</a></li>
<li>Get involved with <a href="https://github.com/kubernetes/community/tree/main/sig-instrumentation">SIG Instrumentation</a> on Slack in <strong>#sig-instrumentation</strong> or join the weekly SIG meetings</li>
</ul>
<h2 id="acknowledgements">Acknowledgements<a class="td-heading-self-link" href="#acknowledgements" aria-label="Heading self-link"></a></h2><p>A huge thank you to contributors across <strong>SIG Instrumentation</strong> and component owners who collaborated on the design, implementation, testing, and review of native histograms in Kubernetes!</p>
<p><em>Native histograms</em> (previously introduced as Alpha in Kubernetes v1.36 under <a href="https://www.kubernetes.dev/resources/keps/5808/">KEP-5808</a>)
bring high-resolution, low-cardinality observability to Kubernetes metrics.
By adopting <a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms</a>, Kubernetes components now expose latency and duration metrics with far greater accuracy while significantly reducing telemetry storage and scraping overhead.</p>
<h2 id="why-move-beyond-classic-histograms">Why move beyond classic histograms?<a class="td-heading-self-link" href="#why-move-beyond-classic-histograms" aria-label="Heading self-link"></a></h2><p>Since the early days of Kubernetes observability, duration and latency metrics (such as API server request latencies or scheduling durations) have relied on <strong>classic Prometheus histograms</strong>.</p>
<p>Classic histograms require metric authors to define a static list of cumulative bucket boundaries (<code>le</code> labels), such as <code>0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10</code>. While familiar, this approach introduces three major challenges:</p>
<ol>
<li><strong>The Bucket Guessing Game</strong>: If a workload's latency profile changes, for example, shifting into microsecond ranges or experiencing long-tail tail latencies beyond the highest bucket, the histogram loses visibility. Specifying bucket boundaries upfront requires knowing the distribution before observing it</li>
<li><strong>High Cardinality & Storage Cost</strong>: With classic histograms, each bucket boundary is exported as a separate time series (<code>_bucket{le="..."}</code>). A histogram with 10 buckets across multiple labels multiplies the number of time series by 10, increasing memory consumption in Prometheus and inflating time series database (TSDB) storage costs</li>
<li><strong>Interpolation Error in Quantiles</strong>: Calculating percentiles using <code>histogram_quantile()</code> relies on linear interpolation between static bucket boundaries. When bucket spans are coarse, quantile calculations can suffer from significant estimation error</li>
</ol>
<h2 id="what-are-prometheus-native-histograms">What are Prometheus native histograms?<a class="td-heading-self-link" href="#what-are-prometheus-native-histograms" aria-label="Heading self-link"></a></h2><p><a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms</a> replace static user-defined buckets with <strong>dynamic, exponential buckets</strong>.</p>
<p>Instead of emitting a separate time series for every single bucket boundary, a <em>native histogram</em> is stored as a single time series containing a rich schema of positive and negative spans, zero thresholds, and exponential scaling factors.</p>
<ul>
<li><strong>High Resolution Automatically</strong>: Exponential buckets dynamically adjust to any value range — from nanoseconds to hours — without requiring pre-configured bucket boundaries</li>
<li><strong>Up to 90% Fewer Time Series</strong>: By consolidating buckets into structured spans within a single time series, scraping and storage overhead are dramatically reduced</li>
<li><strong>Accurate Quantile Calculation</strong>: Quantiles can be calculated with mathematical bounds on error (≃5% worst-case relative error under default settings) across the entire spectrum of observations</li>
</ul>
<h2 id="how-native-histograms-work-in-kubernetes">How native histograms work in Kubernetes<a class="td-heading-self-link" href="#how-native-histograms-work-in-kubernetes" aria-label="Heading self-link"></a></h2><p>In Kubernetes, native histogram support is implemented directly inside the shared metrics subsystem (<code>k8s.io/component-base/metrics</code>).</p>
<p>Figure 1 illustrates how native histogram metrics are processed and exposed across Kubernetes components.</p>
<figure class="diagram-large "><a href="https://kubernetes.io/images/blog/2026-kubernetes-1-37-native-histograms-beta/native-histograms-flow.svg">
<img src="https://kubernetes.io/images/blog/2026-kubernetes-1-37-native-histograms-beta/native-histograms-flow.svg"
alt="Diagram showing native histogram metric registration, exponential options configuration, and dual exposition flow in Kubernetes components"/> </a><figcaption>
<p>Figure 1. Native histogram processing and dual exposition flow in Kubernetes.</p>
</figcaption>
</figure>
<h3 id="1-dual-exposition-for-zero-breaking-changes">1. Dual exposition for zero breaking changes<a class="td-heading-self-link" href="#1-dual-exposition-for-zero-breaking-changes" aria-label="Heading self-link"></a></h3><p>A primary design requirement for KEP-5808 was <strong>zero disruption</strong> for existing observability stacks. When the <code>NativeHistograms</code> feature gate is enabled, Kubernetes components use <strong>dual exposition</strong>:</p>
<ul>
<li><strong>Classic buckets (<code>h.Bucket</code>)</strong> are still emitted alongside native spans. Existing Prometheus servers, dashboards, and alerting rules that rely on traditional text scraping or classic bucket labels continue to work unmodified</li>
<li><strong>Native spans (<code>h.Schema</code>, <code>h.PositiveSpan</code>)</strong> are included in the same Protobuf payload for collectors that understand native histograms</li>
</ul>
<h3 id="2-tuned-default-exponential-configuration">2. Tuned default exponential configuration<a class="td-heading-self-link" href="#2-tuned-default-exponential-configuration" aria-label="Heading self-link"></a></h3><p>When <code>NativeHistograms</code> is enabled, the <code>k8s.io/component-base/metrics</code> package automatically applies standardized exponential options to all histogram metrics:</p>
<ul>
<li><strong><code>BucketFactor: 1.1</code></strong>: Configures exponential buckets where each bucket is at most 10% wider than the preceding one. This guarantees a mathematically bounded worst-case relative error of at most ~5% for quantile calculations regardless of whether an operation takes 1 millisecond or 10 seconds.</li>
<li><strong><code>MaxBucketNumber: 160</code></strong>: Caps the maximum number of buckets per histogram to 160. Following OpenTelemetry SDK recommendations for base-2 exponential histogram aggregation, this limit protects component memory usage even under extreme outlier distributions.</li>
</ul>
<h3 id="3-broad-component-support">3. Broad component support<a class="td-heading-self-link" href="#3-broad-component-support" aria-label="Heading self-link"></a></h3><p>Because native histograms are integrated into <code>component-base/metrics</code>, all major Kubernetes control plane and node components inherit support automatically, including:</p>
<ul>
<li><strong><code>kube-apiserver</code></strong> (e.g., <code>apiserver_request_duration_seconds</code>, authentication/authorization metrics, validation latencies)</li>
<li><strong><code>kube-scheduler</code></strong> (e.g., <code>scheduler_plugin_execution_duration_seconds</code>, <code>scheduler_scheduling_algorithm_duration_seconds</code>)</li>
<li><strong><code>kubelet</code></strong> (node-level container runtime and pod lifecycle metrics)</li>
<li><strong><code>kube-controller-manager</code></strong> and <strong><code>kube-proxy</code></strong></li>
</ul>
<h2 id="how-to-scrape-native-histograms">How to scrape native histograms<a class="td-heading-self-link" href="#how-to-scrape-native-histograms" aria-label="Heading self-link"></a></h2><p>The simple answer: upgrade to Kubernetes v1.37, and it works.</p>
<p>Because Kubernetes v1.37 enables <code>NativeHistograms</code> by default, your cluster is already emitting dual-exposition metrics. How you configure Prometheus to scrape native histograms depends on your Prometheus version:</p>
<h3 id="1-prometheus-scrape-configuration-by-version">1. Prometheus scrape configuration by version<a class="td-heading-self-link" href="#1-prometheus-scrape-configuration-by-version" aria-label="Heading self-link"></a></h3><ul>
<li>
<p><strong>Prometheus 3.0+ (Recommended)</strong>: Use explicit per-job configuration in your <code>scrape_configs</code> rather than global flags (the global <code>--enable-feature=native-histograms</code> flag is deprecated in Prometheus 3.9+):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">scrape_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">'kubernetes-apiservers'</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">scrape_native_histograms</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">always_scrape_classic_histograms</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># Recommended during transition</span><span class="w">
</span></span></span></code></pre></div><p>You must read the caution in <a href="https://kubernetes.io/docs/reference/instrumentation/native-histograms/#migrating-dashboards-and-alerts">Migrating dashboards and alerts</a> in the Native Histograms documentation. In summary: always set <code>always_scrape_classic_histograms: true</code> during your transition period. Without this setting, Prometheus will only ingest the native format and stop ingesting classic <code>_bucket</code>, <code>_count</code>, and <code>_sum</code> series. Setting <code>always_scrape_classic_histograms: true</code> ensures existing dashboards (<code>histogram_quantile(..._bucket...)</code>) and alerts continue to work while you migrate them to native histograms.</p>
</li>
<li>
<p><strong>Prometheus 2.40 – 2.x</strong>: Enable Native Histograms globally by starting Prometheus with the feature flag:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">prometheus --enable-feature<span class="o">=</span>native-histograms
</span></span></code></pre></div><p>Note that in Prometheus 2.x, this is an all-or-nothing setting for all scrape targets.</p>
</li>
</ul>
<h3 id="2-verify-protobuf-dual-exposition">2. Verify Protobuf dual exposition<a class="td-heading-self-link" href="#2-verify-protobuf-dual-exposition" aria-label="Heading self-link"></a></h3><p>Standard Prometheus text scraping (<code>application/openmetrics-text</code> or plain text format) only transfers classic buckets. When <code>scrape_native_histograms</code> is enabled, Prometheus automatically negotiates <strong>Protobuf format</strong> with Kubernetes endpoints.</p>
<p>You can verify that a Kubernetes component is exporting native histograms using <code>curl</code> with an <code>Accept</code> header specifying Protobuf. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">## THIS IS NOT SECURE. ONLY DO THIS IN A TEST CONTEXT.</span>
</span></span><span class="line"><span class="cl">curl --insecure <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -H <span class="s2">"Accept: application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --header <span class="s2">"Authorization: Bearer </span><span class="k">$(</span>cat /var/run/secrets/kubernetes.io/serviceaccount/token<span class="k">)</span><span class="s2">"</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> https://localhost:6443/metrics
</span></span></code></pre></div><p>When decoded, the returned <code>MetricFamily</code> for histogram metrics (like <code>apiserver_request_duration_seconds</code>) will contain both traditional <code>bucket</code> entries and populated <code>schema</code> / <code>positive_span</code> fields.</p>
<h2 id="querying-native-histograms-in-promql">Querying native histograms in PromQL<a class="td-heading-self-link" href="#querying-native-histograms-in-promql" aria-label="Heading self-link"></a></h2><p>Once native histograms are ingested into Prometheus, you can query them using standard PromQL histogram functions without needing static <code>le</code> bucket labels or <code>_bucket</code> suffixes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-promql" data-lang="promql"><span class="line"><span class="cl"><span class="c1"># 1. Calculating P99 latency for a single target:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Classic histogram (requires _bucket suffix):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds_bucket</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Native histogram (operates directly on the metric name):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># 2. Aggregating across multiple instances (e.g., all API servers):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Classic histogram (requires sum by (le) to preserve bucket boundaries):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="k">sum</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="o">(</span><span class="nv">le</span><span class="o">)</span><span class="w"> </span><span class="o">(</span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds_bucket</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1"># Native histogram (no grouping by le required!):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kr">histogram_quantile</span><span class="o">(</span><span class="mf">0.99</span><span class="p">,</span><span class="w"> </span><span class="k">sum</span><span class="o">(</span><span class="kr">rate</span><span class="o">(</span><span class="nv">apiserver_request_duration_seconds</span><span class="p">[</span><span class="s">5m</span><span class="p">]</span><span class="o">)))</span><span class="w">
</span></span></span></code></pre></div><p>With native histograms, functions like <code>histogram_quantile()</code> operate directly on the dynamic exponential spans inside the time series, producing highly accurate quantiles without static bucket interpolation error.</p>
<p>For official documentation on querying Native Histograms in PromQL, see:</p>
<ul>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile">PromQL <code>histogram_quantile</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_fraction">PromQL <code>histogram_fraction</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count-and-histogram_sum">PromQL <code>histogram_sum</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count-and-histogram_sum">PromQL <code>histogram_count</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_count">PromQL <code>histogram_count</code> documentation</a></li>
<li><a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_stddev-and-histogram_stdvar">PromQL <code>histogram_stddev</code> and <code>histogram_stdvar</code> documentation</a></li>
</ul>
<h2 id="dashboard-migration-rollback-strategy">Dashboard migration & rollback strategy<a class="td-heading-self-link" href="#dashboard-migration-rollback-strategy" aria-label="Heading self-link"></a></h2><h3 id="recommended-migration-workflow">Recommended migration workflow<a class="td-heading-self-link" href="#recommended-migration-workflow" aria-label="Heading self-link"></a></h3><p>To safely transition your monitoring infrastructure to Native Histograms without breaking existing alerts or dashboards, I recommend a four-step migration workflow:</p>
<ol>
<li><strong>Enable Both Formats</strong>: In your Prometheus 3.x scrape config, set <code>scrape_native_histograms: true</code> AND <code>always_scrape_classic_histograms: true</code> so both formats are collected safely during transition</li>
<li><strong>Migrate Queries</strong>: Update your Grafana dashboards and Prometheus alerting rules from classic quantile queries (<code>histogram_quantile(..._bucket...)</code>) to native histogram queries (<code>histogram_quantile(...)</code>), and replace references to classic <code>_count</code> and <code>_sum</code> series with <code>histogram_count(...)</code> and <code>histogram_sum(...)</code></li>
<li><strong>Verify in Staging/Production</strong>: Validate that all dashboards and SLO alerts fire and graph correctly using the new native histogram queries</li>
<li><strong>Unlock ~10x Storage Savings</strong>: Once migration is complete, set <code>always_scrape_classic_histograms: false</code>. Prometheus will stop ingesting the static <code>_bucket</code>, <code>_count</code>, and <code>_sum</code> time series, reducing your histogram time series count by up to 90%!</li>
</ol>
<h3 id="opt-out-and-rollback-flexibility">Opt-out and rollback flexibility<a class="td-heading-self-link" href="#opt-out-and-rollback-flexibility" aria-label="Heading self-link"></a></h3><p>Because native histograms are dual-exposed, using them is entirely opt-in from a collector perspective:</p>
<ul>
<li><strong>Instant Collector Rollback</strong>: If you need to stop ingesting native histograms, simply set <code>scrape_native_histograms: false</code> in your Prometheus job configuration. No Kubernetes restart is required, and Prometheus will immediately resume scraping only the classic format without data loss</li>
<li><strong>Component Feature Gate Rollback</strong>: Administrators can also disable the feature gate on Kubernetes components using <code>--feature-gates=NativeHistograms=false</code> (requires component restart)</li>
</ul>
<h2 id="what-s-next-how-to-get-involved">What's next & how to get involved<a class="td-heading-self-link" href="#what-s-next-how-to-get-involved" aria-label="Heading self-link"></a></h2><p>As native histograms progress toward General Availability (GA) in future Kubernetes releases, SIG Instrumentation will continue evaluating ecosystem readiness, performance characteristics, and long-term plans for eventually deprecating static classic buckets once native histogram adoption becomes ubiquitous across the monitoring community.</p>
<ul>
<li>Read the <a href="https://www.kubernetes.dev/resources/keps/5808/">KEP-5808 page</a> or the <a href="https://kep.k8s.io/5808">KEP GitHub issue</a> to learn more.</li>
<li>Read the <a href="https://prometheus.io/docs/specs/native_histograms/">Prometheus Native Histograms specification</a> and <a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile">PromQL querying functions documentation</a></li>
<li>Get involved with <a href="https://github.com/kubernetes/community/tree/main/sig-instrumentation">SIG Instrumentation</a> on Slack in <strong>#sig-instrumentation</strong> or join the weekly SIG meetings</li>
</ul>
<h2 id="acknowledgements">Acknowledgements<a class="td-heading-self-link" href="#acknowledgements" aria-label="Heading self-link"></a></h2><p>A huge thank you to contributors across <strong>SIG Instrumentation</strong> and component owners who collaborated on the design, implementation, testing, and review of native histograms in Kubernetes!</p>
Comments