<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Prometheus-Operator on inetshell</title><link>https://inet.sh/en/tags/prometheus-operator/</link><description>Recent content in Prometheus-Operator on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/prometheus-operator/index.xml" rel="self" type="application/rss+xml"/><item><title>Consistent Prometheus backups: snapshot through the admin API (enableAdminAPI on the operator), copy it out, restore it and delete series</title><link>https://inet.sh/en/posts/monitoring/prometheus-snapshot-admin-api-backup/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/monitoring/prometheus-snapshot-admin-api-backup/</guid><description>Copying /prometheus while Prometheus is running gives an inconsistent backup. The TSDB has an admin API, off by default, that creates an atomic snapshot with hardlinks under snapshots/&amp;lt;date&amp;gt;-&amp;lt;id&amp;gt;: how to enable it (--web.enable-admin-api, or enableAdminAPI: true in the operator&amp;#39;s CRD), the POST to /api/v1/admin/tsdb/snapshot, how to get the snapshot out of a pod without kubectl cp taking an hour, restoring it on another instance, and the other two endpoints: delete_series and clean_tombstones to free space.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Prometheus Operator / kube-prometheus-stack</span>
</span></span><span class="line"><span class="cl">kubectl -n monitoring patch prometheus kube-prometheus-stack-prometheus --type merge <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -p <span class="s1">&#39;{&#34;spec&#34;:{&#34;enableAdminAPI&#34;:true}}&#39;</span>
</span></span><span class="line"><span class="cl">kubectl -n monitoring port-forward svc/prometheus-operated <span class="m">9090</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># create the snapshot (the path is /api/v1, not /api/v2)</span>
</span></span><span class="line"><span class="cl">curl -s -XPOST http://localhost:9090/api/v1/admin/tsdb/snapshot
</span></span><span class="line"><span class="cl"><span class="c1"># {&#34;status&#34;:&#34;success&#34;,&#34;data&#34;:{&#34;name&#34;:&#34;20260926T101500Z-3f2b1c9a7d0e4b11&#34;}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># get it out of the pod with tar over stdout, much faster than kubectl cp</span>
</span></span><span class="line"><span class="cl"><span class="nv">SNAP</span><span class="o">=</span>20260926T101500Z-3f2b1c9a7d0e4b11
</span></span><span class="line"><span class="cl">kubectl -n monitoring <span class="nb">exec</span> prometheus-kube-prometheus-stack-prometheus-0 -c prometheus -- <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  tar cf - -C /prometheus/snapshots <span class="nv">$SNAP</span> <span class="p">|</span> tar xf - -C ./backup/
</span></span></code></pre></td></tr></table>
</div>
</div><p>Outside Kubernetes it is the same with the <code>--web.enable-admin-api</code> flag on the Prometheus command line and the snapshot under <code>&lt;storage.tsdb.path&gt;/snapshots/</code>.</p>
<h2 id="why-copying-the-directory-is-not-enough">Why copying the directory is not enough</h2>
<p>The TSDB writes to the <em>head</em> (WAL plus the in-memory block) while it compacts two-hour blocks into larger ones and deletes the old ones. A <code>cp -r</code> or an <code>rsync</code> of a running <code>/prometheus</code> catches half-compacted blocks and a WAL that no longer matches its checkpoints; with luck it starts, with less luck you get <code>opening storage failed: … corrupted</code>.</p>
<p>The snapshot is made by Prometheus from the inside: it creates <code>snapshots/&lt;date&gt;-&lt;id&gt;/</code> with <strong>hardlinks</strong> to the already compacted blocks (it takes almost no space and a few seconds) and dumps the <em>head</em> as one more block, so the copy is consistent up to the instant of the request. With <code>?skip_head=true</code> it skips the head and only includes what is compacted, which is enough if you snapshot daily and do not care about the last two hours.</p>
<h2 id="restoring">Restoring</h2>
<p>A snapshot is a directory of blocks (<code>01HXYZ…/</code>, each with <code>chunks/</code>, <code>index</code>, <code>meta.json</code>, <code>tombstones</code>). Restoring means <strong>placing them in the root of the data directory</strong> of the target instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># new instance (StatefulSet or binary) stopped, or at least with an empty data dir</span>
</span></span><span class="line"><span class="cl">kubectl -n newprom <span class="nb">exec</span> prometheus-new-0 -c prometheus -- rm -rf /prometheus/wal /prometheus/01*
</span></span><span class="line"><span class="cl">tar cf - -C ./backup/<span class="nv">$SNAP</span> . <span class="p">|</span> kubectl -n newprom <span class="nb">exec</span> -i prometheus-new-0 -c prometheus -- tar xf - -C /prometheus/
</span></span><span class="line"><span class="cl">kubectl -n newprom delete pod prometheus-new-0     <span class="c1"># restart so it reads the blocks</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Prometheus loads the blocks at startup and compacts them together with new ones. Blocks with different <strong>external labels</strong> (another <code>cluster</code>, another <code>replica</code>) mix without trouble; what you cannot do is restore blocks that overlap in time with data that already exists and expect them to merge: they stay duplicated until the next compaction, and queries see both series.</p>
<p><code>promtool tsdb list /prometheus</code> and <code>promtool tsdb analyze /prometheus &lt;block&gt;</code> tell you which range every block covers and which series weigh the most.</p>
<h2 id="deleting-series-and-freeing-space">Deleting series and freeing space</h2>
<p>The same API removes metrics that should not be there (an exporter with runaway cardinality, data from a test):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -s -XPOST -g <span class="s1">&#39;http://localhost:9090/api/v1/admin/tsdb/delete_series?match[]={job=&#34;noisy-exporter&#34;}&amp;start=2026-09-01T00:00:00Z&amp;end=2026-09-20T00:00:00Z&#39;</span>
</span></span><span class="line"><span class="cl">curl -s -XPOST http://localhost:9090/api/v1/admin/tsdb/clean_tombstones
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>delete_series</code> only marks (tombstones); the space is not freed until <code>clean_tombstones</code> or the next compaction. Both return <code>204</code> with no body.</p>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>The admin API has no authentication</strong>: anyone who reaches port 9090 can wipe the whole database. Enable it, take the snapshot over <code>port-forward</code>, and set it back to <code>false</code>. Never behind an Ingress without auth.</li>
<li><strong><code>kubectl cp</code> with thousands of files</strong> takes forever and sometimes breaks; the <code>tar</code> over stdout above is a single stream. For snapshots of tens of GB, mount a backup PVC or use a sidecar with <code>mc</code>/<code>rclone</code> to S3.</li>
<li><strong>Delete old snapshots</strong> from <code>/prometheus/snapshots/</code>: they are hardlinks, but they keep alive blocks that retention would already have removed, and the disk grows in a way retention does not explain.</li>
<li><strong>Thanos or Mimir</strong> with a <code>sidecar</code> already upload every block to S3: there the snapshot only matters for the head. The backup is the buckets.</li>
<li><strong>kube-prometheus-stack</strong>: <code>prometheus.prometheusSpec.enableAdminAPI: true</code> in the values if you want the patch to survive the next <code>helm upgrade</code>.</li>
</ul>
<p>References: <a href="https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-admin-apis">TSDB Admin APIs</a> in the Prometheus documentation.</p>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="/en/posts/monitoring/grafana-sso-disableautologin-roles-provisioning/">Grafana with SSO</a> — the other half of the stack.</li>
<li><a href="/en/posts/zfs/hold-protect-snapshot/">ZFS: protect snapshots with hold</a> — if the Prometheus data dir lives on ZFS, filesystem snapshots are another consistent option.</li>
</ul>
]]></content:encoded></item></channel></rss>