<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Containerization on Charlie Chiang's blog</title><link>https://charlie0129.github.io/blog/categories/containerization/</link><description>Recent content in Containerization on Charlie Chiang's blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 16 Aug 2025 20:49:00 +0800</lastBuildDate><atom:link href="https://charlie0129.github.io/blog/categories/containerization/index.xml" rel="self" type="application/rss+xml"/><item><title>Proxmox VE Shared GPU Installation Guide</title><link>https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/</link><pubDate>Sat, 16 Aug 2025 20:49:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/</guid><description>&lt;h2 id="introduction">Introduction
&lt;/h2>&lt;p>Most schools and universities in China have a limited number of GPU servers (often only one or two, or even none). This makes the management of GPU development machines quite different than corporation which have virtually unlimited GPU servers.&lt;/p>
&lt;p>Let&amp;rsquo;s consider a normal lab environment with 12 students, each needing a GPU server for their projects, and we have one GPU server with 8 GPUs available.&lt;/p>
&lt;ul>
&lt;li>If we give each student a dedicated GPU server, we would need 12 servers, this is impractical and costly.&lt;/li>
&lt;li>If we let all the students share a single GPU server (all students have access to all resources), it would be very difficult to manage, as each student would need to install their own software and dependencies, leading to conflicts. Soon the GPU server will become a hot mess. Making each student use a different user account would not solve the problem, as they would still share the same OS and software environment, and will potentially break each other&amp;rsquo;s environment.&lt;/li>
&lt;li>If we make use of IOMMU and PCIe passthrough, we can assign a virtual machine with one dedicated passthrough&amp;rsquo;d GPU to each student. This approach has almost no interference between students, as each student has their own OS and software environment, even with it&amp;rsquo;s own GPU. However, this leads to a lot of wasted resources, because most of the time the GPU is idle, and each student will only have access to one GPU at a time, even if all of the GPUs are idle.&lt;/li>
&lt;li>If we use GPU virtualization, we can assign a virtual machine with a shared GPU to each student. This way, each student can use the GPU resources as needed, and the GPU can be shared among multiple students. This approach is more efficient and cost-effective, as it allows for better resource utilization. However, it requires a GPU that supports virtualization, such as NVIDIA&amp;rsquo;s vGPU or AMD&amp;rsquo;s MxGPU. Most consumer GPUs do not support virtualization, so this approach is not feasible for most schools and universities.&lt;/li>
&lt;/ul>
&lt;p>This is where containerization comes in. By using containerization, we can create a shared GPU development server that runs a container for each student. Each container can have its own software environment, and all (or some of) of the GPUs can be shared among multiple containers. This approach is more efficient and cost-effective, as it allows for better resource utilization, and does not require a GPU that supports virtualization. However, it should be noted that this approach is not as isolated as virtual machines, as all containers share the same kernel and GPU resources. Therefore, it is important to ensure that the containers are properly configured, and that the students are aware of the limitations and potential issues that may arise from sharing the same GPU resources (for example, if one student runs a GPU-intensive task, it may affect the performance of other students&amp;rsquo; containers).&lt;/p>
&lt;p>We will be using the CT (LXC Containers) in Proxmox VE to achieve this. Why not use Docker? Because Docker is meant for running applications, not for running full Linux distros. Although you can use something like sysbox to run a full Linux distro in Docker, i would still prefer to use LXC Containers, as they are built for this purpose and are already built into Proxmox VE, making it easier to manage and deploy.&lt;/p>
&lt;h2 id="proxmox-ve-installation">Proxmox VE Installation
&lt;/h2>&lt;p>Download the latest Proxmox VE ISO from the &lt;a class="link" href="https://www.proxmox.com/en/downloads" target="_blank" rel="noopener"
>official website&lt;/a>. At the time of writing, the latest version is Proxmox VE 9.0. Use whatever method you prefer to install Proxmox VE, such as using a USB drive.&lt;/p>
&lt;p>I will use IPMI of the server to install Proxmox VE, as it is the most convenient method for me. You can also use a monitor and keyboard to install Proxmox VE if you prefer.&lt;/p>
&lt;p>Mount the Proxmox VE installation ISO as virtual media so we can boot into.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188.png"
width="2560"
height="1600"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_fc3819d67899781d.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_9d957dfbafccb232.jpg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_f638cbe753e8df54.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_96242ea226abd1df.jpg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_bb01bd1f795bfa17.webp 1536w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_a80d7d3f160c23f3.jpg 1536w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_2406379d72ef1f1b.webp 2048w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_d73dbdf56d3ff481.jpg 2048w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_6f21ae6ec6e3868b.webp 2560w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250816213214188_hu_7b94c32487992fd7.jpg 2560w"
loading="lazy"
alt="image-20250816213214188"
class="gallery-image"
data-flex-grow="160"
data-flex-basis="384px"
>
&lt;/p>
&lt;p>Now reboot the server into the virtual media. Supermicro motherboards lets you invode the boot menu using F11. Your motherboard may have a different shortcut.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm1.jpeg"
width="1024"
height="768"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm1_hu_8ba3cfc0a697fe04.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm1_hu_9c46554a70e3f68b.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm1_hu_c7fc7a1ae7b4113d.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm1_hu_1b4cb758cf2fff8a.jpeg 1024w"
loading="lazy"
alt="kvm1"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;p>Choose our CDROM virtual media.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm2.jpeg"
width="800"
height="600"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm2_hu_9017089d67a44bb4.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm2_hu_e0cae96a987e1778.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm2_hu_209d9180b54cd190.webp 800w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm2_hu_bb993296d9dc2ad2.jpeg 800w"
loading="lazy"
alt="kvm2"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;p>Boot into the Proxmox install menu (I prefer terminal UI over GUI ones).&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm5.jpeg"
width="1024"
height="768"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm5_hu_bf96deb29f237104.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm5_hu_fbc78cf467a77c00.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm5_hu_9225fe3a3bdcfe99.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm5_hu_d4626b04625ebb28.jpeg 1024w"
loading="lazy"
alt="kvm5"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;p>After you accepted the license, you will need to choose the target installtion disk. You should choose the boot SSDs on your server, not data drives. The drive in picture is 3*1.92TB SSDs in RAID 5.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6.jpeg"
width="1280"
height="1024"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_dbb426d48c671cc8.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_6064335627e5139e.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_7f1d8d51d68f2aa4.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_208bd54bae9f396f.jpeg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_736c681430510399.webp 1280w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm6_hu_e7bd64cc7009b50f.jpeg 1280w"
loading="lazy"
alt="kvm6"
class="gallery-image"
data-flex-grow="125"
data-flex-basis="300px"
>
&lt;/p>
&lt;p>Toggle &lt;code>Advanced options&lt;/code>. Keep ext4, we will not be using ZFS. I know ZFS has CoW, snapshotting, compressing, checksums, and a ton of other features. But we will not use ZFS, because it will potentially cause some problems, e.g., high IO load when using RAID-Z zvols, extremely slow container start times when using ZFS storage driver in Docker, SSD write amplifications, low SSD random read/write performance, ZFS ARC not being given back to the OS as fast as needed on high memory pressure systems leading to OOM, and a ton of other issues that I previously encountered. To save me some trouble, I will use the battle-tested ext4.&lt;/p>
&lt;ul>
&lt;li>Total size: keep as-is&lt;/li>
&lt;li>Swap size: 0, we don&amp;rsquo;t need it, our memory is large enought (1TiB) and we will later use ZRAM as swap&lt;/li>
&lt;li>Maximum root volume size: 100 (GiB), make it slightly larger so we can install things into the root volume, but not too large to occupy the data volume space.&lt;/li>
&lt;/ul>
&lt;p>Other options can be left empty.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7.jpeg"
width="1280"
height="1024"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_2ff35877d03636be.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_378d3db78e6dd1f1.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_dc35289a6d315b63.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_5a57bbb82297cfbc.jpeg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_3de3e3022862cd88.webp 1280w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm7_hu_9f57d55dfbd59de8.jpeg 1280w"
loading="lazy"
alt="kvm7"
class="gallery-image"
data-flex-grow="125"
data-flex-basis="300px"
>
&lt;/p>
&lt;p>Other install steps (Keyboard setup, root password) can proceed as you normally would do. Remember to set a complex root password.&lt;/p>
&lt;p>Regarding to IP addresses:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>If you are going to use a static IP address like I do, you can just set it here and forget about it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If you are going to use a dynamic (DHCP) addresses, you should keep what&amp;rsquo;s already in here (static IP). You don&amp;rsquo;t need to change anything. The valid IP address should already be automatically discovered from DHCP servers and filled in. After the installation, you can change the static IP mode to DHCP mode by running &lt;code>vi /etc/network/interfaces&lt;/code> and make the following edits:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl"> auto vmbr0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- iface vmbr0 inet static
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ iface vmbr0 inet dhcp # Enable DHCP and remove static IP.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gd">- address 10.112.154.220/16
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- gateway 10.112.0.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span> bridge-ports eno1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-stp off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-fd 0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>After you enabled DHCP in the config, apply it by &lt;code>ifreload -a&lt;/code>. Check if it takes effect by &lt;code>ip a show vmbr0&lt;/code>, you should see something like &lt;code>valid_lft 7160sec preferred_lft 7160sec&lt;/code>. If there is no &lt;code>xx sec&lt;/code> inside it, your DHCP is malfunctioning.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="go">root@z8:/etc/network# ip a show vmbr0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">4: vmbr0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> link/ether 30:13:8b:6d:3a:a6 brd ff:ff:ff:ff:ff:ff
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> inet 10.112.154.220/16 brd 10.112.255.255 scope global dynamic vmbr0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> valid_lft 7160sec preferred_lft 7160sec
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> inet6 fe80::3213:8bff:fe6d:3aa6/64 scope link
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> valid_lft forever preferred_lft forever
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8.jpeg"
width="1280"
height="1024"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_f1e09d0126385e40.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_cf2d14b24937eaa4.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_f8717c460e0c3fb.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_c48c549747fce87b.jpeg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_bf0bf072e6823127.webp 1280w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm8_hu_52691de358776305.jpeg 1280w"
loading="lazy"
alt="kvm8"
class="gallery-image"
data-flex-grow="125"
data-flex-basis="300px"
>
&lt;/p>
&lt;h2 id="proxmox-ve-post-installtion">Proxmox VE Post-Installtion
&lt;/h2>&lt;p>Log in as root as we will make a few modifications.&lt;/p>
&lt;h3 id="change-apt-sources">Change APT Sources
&lt;/h3>&lt;p>In China, we don&amp;rsquo;t really have a good global Internet connection, so we will change APT source to mirrors in China.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Change APT source mirrors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/deb.debian.org/mirrors.ustc.edu.cn/g&amp;#39;&lt;/span> /etc/apt/sources.list.d/debian.sources
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/security.debian.org/mirrors.ustc.edu.cn/g&amp;#39;&lt;/span> /etc/apt/sources.list.d/debian.sources
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Change Ceph source mirrors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -f /etc/apt/sources.list.d/ceph.sources &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">CEPH_CODENAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>ceph -v &lt;span class="p">|&lt;/span> grep ceph &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $(NF-1)}&amp;#39;&lt;/span>&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">source&lt;/span> /etc/os-release
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cat &amp;gt; /etc/apt/sources.list.d/ceph.sources &lt;span class="s">&amp;lt;&amp;lt;EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Types: deb
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">URIs: https://mirrors.ustc.edu.cn/proxmox/debian/ceph-$CEPH_CODENAME
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Suites: $VERSION_CODENAME
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Components: no-subscription
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Remove enterprise repo and add no-subscription repo.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Remove enterprise sources&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm /etc/apt/sources.list.d/pve-enterprise.sources
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Add no-subscription sources&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &amp;gt; /etc/apt/sources.list.d/pve-no-subscription.sources &lt;span class="s">&amp;lt;&amp;lt;EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Types: deb
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">URIs: https://mirrors.ustc.edu.cn/proxmox/debian/pve
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Suites: trixie
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Components: pve-no-subscription
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="change-ct-template-sources">Change CT Template Sources
&lt;/h3>&lt;p>Use mirrors in China.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sed -i.bak &lt;span class="s1">&amp;#39;s|http://download.proxmox.com|https://mirrors.ustc.edu.cn/proxmox|g&amp;#39;&lt;/span> /usr/share/perl5/PVE/APLInfo.pm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl restart pvedaemon
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="stop-cluster-services">Stop Cluster Services
&lt;/h3>&lt;p>We are not using PVE clusters. Disable them. If you are using PVE clusters, you probably will not be reading this guide :P&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">systemctl disable --now pve-ha-crm.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl disable --now pve-ha-lrm.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl disable --now corosync.service
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="install-common-tools">Install Common Tools
&lt;/h3>&lt;p>Useful tools that will be used regularly.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">apt install htop sysstat vim sudo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="configure-shell">Configure Shell
&lt;/h3>&lt;p>I like ZSH and my dotfiles, so I will use them.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">apt install git zsh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone --depth&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> https://github.com/charlie0129/dotfiles.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./bootstrap.sh -f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chsh -s /usr/bin/zsh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Run zsh and follow the instructions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">zsh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="configure-zram-swap">Configure ZRAM Swap
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone --depth&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> https://github.com/foundObjects/zram-swap.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> zram-swap
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./install.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -rf zram-swap
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The default lz4 is a good balance between speed and compression ratio. If you want to sacrifice speed for better compression, you can change the config to use zstd as the compression method.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/_zram_algorithm=.*/_zram_algorithm=&amp;#34;zstd&amp;#34;/g&amp;#39;&lt;/span> /etc/default/zram-swap
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Apply&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">systemctl restart zram-swap
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Tune kernel parameters to make better use of ZRAM&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat &lt;span class="s">&amp;lt;&amp;lt;EOF &amp;gt; /etc/sysctl.d/zram.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">vm.swappiness = 180
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">vm.watermark_boost_factor = 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">vm.watermark_scale_factor = 125
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">vm.page-cluster = 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Apply&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sysctl --system
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="enable-ipv6-slaac">Enable IPv6 SLAAC
&lt;/h3>&lt;p>Proxmox disables IPv6 by default. To enable IPv6 SLAAC:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat &lt;span class="s">&amp;lt;&amp;lt;EOF &amp;gt; /etc/sysctl.d/ipv6.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.default.accept_ra = 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.all.accept_ra = 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.default.forwarding = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.all.forwarding = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.default.proxy_ndp = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">net.ipv6.conf.all.proxy_ndp = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Apply it&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sysctl --system
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Note: the following command may break your internet connection.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl restart networking
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="trim-optimizations">TRIM Optimizations
&lt;/h3>&lt;p>TRIM will help prolong SSD lifespan and maintain performance. Enable it on the LVM thin pool.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/.*issue_discards = .*/\tissue_discards = 1/g&amp;#39;&lt;/span> /etc/lvm/lvm.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Since LXCs are just on the host, it will automatically issue discards if the underlying storage supports it, so no further action is needed. If you are using VM, be sure to use SCSI disks (VirtIO SCSI Single) with &lt;code>discard=on&lt;/code> option. I&amp;rsquo;ve heard that VirtIO Block devices will not work with dicards but I haven&amp;rsquo;t tested it yet. Correct me if I&amp;rsquo;m wrong.&lt;/p>
&lt;h3 id="limit-journal-size">Limit Journal Size
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/.*SystemMaxUse.*/SystemMaxUse=32M/g&amp;#39;&lt;/span> /etc/systemd/journald.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Apply&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">systemctl daemon-reload
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl restart systemd-journald
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="nvidia-p2p-driver-installation">NVIDIA P2P Driver Installation
&lt;/h2>&lt;p>Instead of regular NVIDIA drivers, we will be installing P2P-enabled drivers to force enable PCIe P2P capabilities on consumer cards (like GeForce RTX 4090). This will bring performance boost (~10%) across multiple scenarios. For details, refer to my blog post: &lt;a class="link" href="https://charlie0129.github.io/blog/p/rtx4090-p2p-unlocked/" >Enabling PCIe P2P on NVIDIA RTX 4090s&lt;/a>&lt;/p>
&lt;h3 id="disable-iommu">Disable IOMMU
&lt;/h3>&lt;p>Since PCIe P2P in Linux doesn&amp;rsquo;t work so well with IOMMU-enabled systems (there are many potential issues you may run into), you may as well just disable IOMMU. Note that if you have more than 255 CPU cores, you will only have access to 255 CPUs due to APIC fallback.&lt;/p>
&lt;p>To do disable IOMMU, you can either&lt;/p>
&lt;ul>
&lt;li>Disable Intel VT-d (AMD is on by default) in BIOS&lt;/li>
&lt;li>Disable &lt;code>intel_iommu&lt;/code> or &lt;code>amd_iommu&lt;/code> in Linux kernel parameters&lt;/li>
&lt;/ul>
&lt;p>To disable &lt;code>intel_iommu&lt;/code> or &lt;code>amd_iommu&lt;/code> in Linux kernel parameters, use the following command to add &lt;code>intel_iommu=off&lt;/code> and &lt;code>amd_iommu=off&lt;/code> to your &lt;code>GRUB_CMDLINE_LINUX_DEFAULT&lt;/code>. BTW: I also reduce the screen resolution to 1024x768 because it&amp;rsquo;s a server&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=&amp;#34;intel_iommu=off amd_iommu=off video=1024x768@60&amp;#34;/g&amp;#39;&lt;/span> /etc/default/grub
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Reboot your system to see the effect.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">reboot
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Make sure the follow command produces NO entries. If there are output, it means IOMMU is not correctly disabled.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ls /sys/class/iommu
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="disable-acs">Disable ACS
&lt;/h3>&lt;p>You may need to disable ACS to get PCIe P2P to work, refer to &lt;a class="link" href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/troubleshooting.html" target="_blank" rel="noopener"
>https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/troubleshooting.html&lt;/a> (PCI Access Control Services)&lt;/p>
&lt;h3 id="enable-above-4g-decoding">Enable Above 4G Decoding
&lt;/h3>&lt;p>PCIe P2P will need to access each GPU&amp;rsquo;s memory, so the PCIe BAR should be large enough to cover GPU&amp;rsquo;s memory. You must enable &lt;code>Above 4G Decoding&lt;/code> in your motherboard settings to make the PCIe BAR large enough to work. Enable it in your motherboard&amp;rsquo;s BIOS.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm3.jpeg"
width="800"
height="600"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm3_hu_7e15f21db3b3c17e.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm3_hu_9e3424f688b5b212.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm3_hu_c195e513c0a13d42.webp 800w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm3_hu_7d51eb80b8d4fadf.jpeg 800w"
loading="lazy"
alt="kvm3"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;h3 id="install-official-driver">Install Official Driver
&lt;/h3>&lt;p>Before we install the P2P kernel module, we need to install the official drivers.&lt;/p>
&lt;p>Since we are installing unofficial P2P kernel modules, there is a limited number of driver version that will work. The version of the P2P kernel modules MUST be the same as the official driver we are going to install.&lt;/p>
&lt;p>Refer to &lt;a class="link" href="https://github.com/tinygrad/open-gpu-kernel-modules" target="_blank" rel="noopener"
>https://github.com/tinygrad/open-gpu-kernel-modules&lt;/a> to see what versions are available. I also have my patches that works with much newer driver versions (&lt;code>575.57.08&lt;/code>) (at the time of writing) &lt;a class="link" href="https://github.com/charlie0129/open-gpu-kernel-modules" target="_blank" rel="noopener"
>https://github.com/charlie0129/open-gpu-kernel-modules&lt;/a> .&lt;/p>
&lt;p>I will be using version &lt;code>575.57.08&lt;/code>, so we should download the official driver of the same version (575.57.08). You should download the &lt;code>.run&lt;/code> file, e.g., &lt;code>NVIDIA-Linux-x86_64-575.57.08.run&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Assume you download the installer as NVIDIA-Linux-x86_64-575.57.08.run&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">installer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;NVIDIA-Linux-x86_64-575.57.08.run&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x &lt;span class="nv">$installer&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Skip kernel modules because we will install P2P-patched version later.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./&lt;span class="nv">$installer&lt;/span> --no-kernel-modules
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Choose whatever you want, doesn&amp;rsquo;t matter.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967.png"
width="1332"
height="936"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_98208fee79b653b6.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_8c48fb6ddb80aa09.jpg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_961f2daf7702c60.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_c210bf19507e4f39.jpg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_b2550ba7a4857e9c.webp 1332w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000515967_hu_e39d158f30e89b6a.jpg 1332w"
loading="lazy"
alt="image-20250817000515967"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="341px"
>
&lt;/p>
&lt;p>Yes, disable nouveau, we will be using NVIDIA drivers.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521.png"
width="1332"
height="936"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_c04467322fc6b9b5.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_6960c0b2e9c6b7b7.jpg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_3b938860c46b37b2.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_166683d5e7642037.jpg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_6cffe724c88436b4.webp 1332w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000549521_hu_74c62616de8a0c23.jpg 1332w"
loading="lazy"
alt="image-20250817000549521"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="341px"
>
&lt;/p>
&lt;p>Do not abort, just continue. Nouveau will be disabled on next boot.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879.png"
width="1332"
height="936"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_d23455096df2bfa2.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_6b7514f546ecd5da.jpg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_849962d25bf10f61.webp 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_d5e6cd44b8bf36e5.jpg 1024w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_7936dd81616107f0.webp 1332w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/image-20250817000652879_hu_a5cd53c580d81314.jpg 1332w"
loading="lazy"
alt="image-20250817000652879"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="341px"
>
&lt;/p>
&lt;p>Just choose the default option for every step that comes later.&lt;/p>
&lt;p>Remove the driver installation file because we will not be using it later.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">rm &lt;span class="nv">$installer&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You can skip reboot now. We will reboot after we installed the kernel modules.&lt;/p>
&lt;h3 id="install-p2p-enabled-kernel-modules">Install P2P-Enabled Kernel Modules
&lt;/h3>&lt;p>Make sure the version of the P2P-enabled kernel modules match the version of the driver. I will use veriosn 575.57.08.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone --depth&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> -b 575.57.08-p2p https://github.com/charlie0129/open-gpu-kernel-modules.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>To build kernel modules, install build dependencies and kernel source. &lt;code>sudo&lt;/code> installed because the install script uses &lt;code>sudo&lt;/code> but we don&amp;rsquo;t have it now.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">apt install sudo build-essential proxmox-headers-&lt;span class="k">$(&lt;/span>uname -r&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Build and install the kernel module&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./install.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You can safely ignore the NVIDIA-SMI failure as long as the build succeeds (you should see a &lt;code>DEPMOD /lib/modules/6.14.8-2-pve&lt;/code> line) because we haven&amp;rsquo;t rebooted yet (so NVIDIA-SMI can&amp;rsquo;t be used).&lt;/p>
&lt;p>Remove the source after installing&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -rf open-gpu-kernel-modules
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Reboot your system and you should see &lt;code>nvidia-smi&lt;/code> running fine.&lt;/p>
&lt;p>To tell if P2P is enabled, we will use a simple method (performance testing will be done later):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-smi -q &lt;span class="p">|&lt;/span> grep -i bar -A &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You should see &lt;code>&amp;gt; 2048 MiB&lt;/code> BAR1 Total memory, &lt;code>32768 MiB&lt;/code> in my case.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"> BAR1 Memory Usage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Total : 32768 MiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Used : 2 MiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Free : 32766 MiB
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="enable-persistence-mode">Enable Persistence Mode
&lt;/h3>&lt;p>Persistence mode will:&lt;/p>
&lt;ul>
&lt;li>Keep the GPU driver running so program can start faster&lt;/li>
&lt;li>Lower GPU power mode when it&amp;rsquo;s idle to save power. For RTX 4090s, it can drop from ~70W to ~10W.&lt;/li>
&lt;li>Make sure &lt;code>/dev/nvidia*&lt;/code> device nodes are ready. This is useful because we are passing the GPU devices &lt;code>/dev/nvidia*&lt;/code> to CTs (LXC Containers) later, it requires the device nodes to be present on start up for auto-started CTs.&lt;/li>
&lt;/ul>
&lt;p>So you really should enable it.&lt;/p>
&lt;p>To enable persistence mode:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">/usr/bin/nvidia-smi -pm &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>To make it persistent across reboots, we will use a cron job.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">crontab -e
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Add a line to run &lt;code>nvidia-smi&lt;/code> on boot:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">@reboot /usr/bin/nvidia-smi -pm &lt;span class="m">1&lt;/span> &amp;gt;/dev/null 2&amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;blockquote>
&lt;p>Why not use &lt;code>nvidia-persistenced&lt;/code> systemd service? Because for whatever reason, it does not initialize the device nodes &lt;code>/dev/nvidia*&lt;/code> correctly, so auto-started CTs will encounter &lt;code>Cuda failure 'unknown error'&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /usr/share/doc/NVIDIA_GLX-1.0/samples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar jxf nvidia-persistenced-init.tar.bz2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> nvidia-persistenced-init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./install.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/blockquote>
&lt;p>You should feel &lt;code>nvidia-smi&lt;/code> runs much faster than before.&lt;/p>
&lt;h2 id="build-ct-templates">Build CT Templates
&lt;/h2>&lt;p>We will build a CT Template that has everything a student will need:&lt;/p>
&lt;ul>
&lt;li>Common build dependencies&lt;/li>
&lt;li>CUDA&lt;/li>
&lt;li>Docker&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;/ul>
&lt;p>Download the Linux distro you want. I chose Ubuntu 24.04, not because I like Ubuntu (I use Debian), but because most students only know Ubuntu.&lt;/p>
&lt;p>Create an unprivileged CT just like you normally would. Just remember to give it a bit more disk space (32GB or more) because we will install CUDA later and CUDA is really large.&lt;/p>
&lt;h3 id="install-docker">Install Docker
&lt;/h3>&lt;p>After you created the CT, shut it down. Add additional settings to allow Docker to use overlayfs driver, otherwise Docker images will be extremely inefficient in CTs.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This command should be run on the host, not CT.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Assume 8000 is the ID of your template CT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &lt;span class="s">&amp;lt;&amp;lt;EOF &amp;gt;&amp;gt; /etc/pve/local/lxc/8000.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.apparmor.profile: unconfined
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.cgroup.devices.allow: a
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.cap.drop:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>All following commands should be run in the CT unless specified otherwise.&lt;/p>
&lt;p>Add Docker configuration. Systemd journal is used as log driver to make use of previously configured journal size limit. By writing logs to a centralized location, it&amp;rsquo;s also useful if you want to write logs to memory to reduce disk writes (e.g. log2ram).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mkdir -p /etc/docker/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &lt;span class="s">&amp;lt;&amp;lt;EOF &amp;gt; /etc/docker/daemon.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;#34;live-restore&amp;#34;: true,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;#34;experimental&amp;#34;: true,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;#34;log-driver&amp;#34;: &amp;#34;journald&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Install Docker&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">DOWNLOAD_URL&lt;/span>&lt;span class="o">=&lt;/span>https://mirrors.ustc.edu.cn/docker-ce
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -fsSL https://get.docker.io &lt;span class="p">|&lt;/span> sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="revert-containerd-config">Revert Containerd Config
&lt;/h3>&lt;p>Docker modified containerd config. We will revert it to the default config.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">containerd config default &amp;gt;/etc/containerd/config.toml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="add-gpus-to-ct">Add GPUs to CT
&lt;/h3>&lt;p>Choose which GPU you need to passthrough by looking for the index in &lt;code>nvidia-smi&lt;/code>, or by &lt;code>ls -l /dev/nvidia*&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This command should be run on the host, not CT.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ls -l /dev/nvidia*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">0&lt;/span> 2025-08-17 01:47:58 /dev/nvidia0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">1&lt;/span> 2025-08-17 01:48:00 /dev/nvidia1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">2&lt;/span> 2025-08-17 01:48:01 /dev/nvidia2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">3&lt;/span> 2025-08-17 01:48:02 /dev/nvidia3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">4&lt;/span> 2025-08-17 01:48:03 /dev/nvidia4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">5&lt;/span> 2025-08-17 01:48:05 /dev/nvidia5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">6&lt;/span> 2025-08-17 01:48:06 /dev/nvidia6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">crw-rw-rw- &lt;span class="m">1&lt;/span> root root 195, &lt;span class="m">7&lt;/span> 2025-08-17 01:48:07 /dev/nvidia7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">... &lt;span class="o">(&lt;/span>omitted&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For example, if want to add all 8 GPUs (0, 1, 2, 3, 4, 5, 6, 7) to CT, I will need to run:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This command should be run on the host, not CT.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Assume 8000 is the ID of your template CT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &lt;span class="s">&amp;lt;&amp;lt;EOF &amp;gt;&amp;gt; /etc/pve/local/lxc/8000.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia1 dev/nvidia1 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia2 dev/nvidia2 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia3 dev/nvidia3 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia4 dev/nvidia4 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia5 dev/nvidia5 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia6 dev/nvidia6 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia7 dev/nvidia7 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia-modeset dev/nvidia-modeset none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Note the &lt;code>lxc.mount.entry: /dev/nvidiaX dev/nvidiaX none bind,optional,create=file&lt;/code> lines. Each line represents a single GPU to add to the CT. I will add 8 GPUs, so I add 8 lines from &lt;code>nvidia0&lt;/code> through &lt;code>nvidia7&lt;/code>. Remove or add them as needed.&lt;/p>
&lt;p>Check if the GPUs are there inside CT:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ls -l /dev/nvidia*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">00&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">modeset&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">43&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">uvm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">43&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">uvm&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">tools&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">47&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">58&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">00&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">01&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">02&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">03&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">05&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">06&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia6&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">48&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">07&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidia7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rw&lt;/span>&lt;span class="o">-&lt;/span> &lt;span class="n">nobody&lt;/span> &lt;span class="n">nogroup&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">17&lt;/span> &lt;span class="mi">01&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">47&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">58&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">dev&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">nvidiactl&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We haven&amp;rsquo;t installed drivers yet, so nvidia-smi is not available.&lt;/p>
&lt;h3 id="install-gpu-driver">Install GPU Driver
&lt;/h3>&lt;p>Important: you should use the same driver version as the host (&lt;code>575.57.08&lt;/code> in my case).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wget &amp;lt;driver-runfile-url&amp;gt; -O driver.run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x driver.run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">driver.run --no-kernel-modules
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm driver.run
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Just install as normal.&lt;/p>
&lt;p>Now you should be able to use &lt;code>nvidia-smi&lt;/code> and see the GPUs you added to the CT (8 GPUs in my case).&lt;/p>
&lt;h3 id="install-cuda">Install CUDA
&lt;/h3>&lt;p>Important: current driver version MUST be greater than (or equal to) the driver required by CUDA. For example, CUDA 12.9.0 will work on my machine because CUDA 12.9.0 requires &lt;code>575.00&lt;/code>, and my current driver version is &lt;code>575.57.08&lt;/code> (greater than &lt;code>575.00&lt;/code>).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wget &amp;lt;cuda-runfile-url&amp;gt; -O cuda.run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x cuda.run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cuda.run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm cuda.run
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Remember to deselect the driver (we already installed the correct driver):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ CUDA Installer │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ - [ ] Driver &amp;lt;== deselect this │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [ ] 575.51.03 &amp;lt;== deselect this │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ + [X] CUDA Toolkit 12.9 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [ ] CUDA Demo Suite 12.9 &amp;lt;== deselect this (not useful, takes up space) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [ ] CUDA Documentation 12.9 &amp;lt;== deselect this (not useful, takes up space) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ - [ ] Kernel Objects │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [ ] nvidia-fs │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Options │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Install │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Up/Down: Move | Left/Right: Expand | &amp;#39;Enter&amp;#39;: Select | &amp;#39;A&amp;#39;: Advanced options │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>After CUDA installation, configure &lt;code>PATH&lt;/code> and &lt;code>LD_LIBRARY_PATH&lt;/code> per printed instructions.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">... (omitted)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Please make sure that
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - PATH includes /usr/local/cuda-12.9/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - LD_LIBRARY_PATH includes /usr/local/cuda-12.9/lib64, or, add /usr/local/cuda-12.9/lib64 to /etc/ld.so.conf and run ldconfig as root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">... (omitted)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Note that you can use &lt;code>/usr/local/cuda&lt;/code> instead of &lt;code>/usr/local/cuda-XX.X&lt;/code> so it&amp;rsquo;s independent of CUDA versions.&lt;/p>
&lt;p>Also add &lt;code>export CUDA_HOME=/usr/local/cuda&lt;/code> to your shell rc file.&lt;/p>
&lt;p>nvcc should be available after a shell reload:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># nvcc -V&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvcc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">NVIDIA&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">R&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">Cuda&lt;/span> &lt;span class="n">compiler&lt;/span> &lt;span class="n">driver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Copyright&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">2005&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">2025&lt;/span> &lt;span class="n">NVIDIA&lt;/span> &lt;span class="n">Corporation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Built&lt;/span> &lt;span class="n">on&lt;/span> &lt;span class="n">Wed_Apr__9_19&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">24&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">57&lt;/span>&lt;span class="n">_PDT_2025&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Cuda&lt;/span> &lt;span class="n">compilation&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">release&lt;/span> &lt;span class="mf">12.9&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">V12&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mf">9.41&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Build&lt;/span> &lt;span class="n">cuda_12&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mf">9.&lt;/span>&lt;span class="n">r12&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">compiler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">35813241&lt;/span>&lt;span class="n">_0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="install-nccl">Install NCCL
&lt;/h3>&lt;p>You will need NCCL for most GPU-related communications.&lt;/p>
&lt;p>You should find a NCCL that&amp;rsquo;s compatible with your CUDA version. For example, NCCL &lt;code>2.27.3&lt;/code> is compatible with my CUDA version &lt;code>12.9&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Download NCCL package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget https://xxx/nccl_2.27.3-1+cuda12.9_x86_64.txz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar Jxf nccl_2.27.3-1+cuda12.9_x86_64.txz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Set some variables&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_VERSION&lt;/span>&lt;span class="o">=&lt;/span>2.27
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p /usr/local/nccl-&lt;span class="nv">$NCCL_VERSION&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cp -vRf nccl_2.27.3-1+cuda12.9_x86_64/* /usr/local/nccl-&lt;span class="nv">$NCCL_VERSION&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -rf nccl_2.27.3-1+cuda12.9_x86_64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ln -s /usr/local/nccl-&lt;span class="nv">$NCCL_VERSION&lt;/span> /usr/local/nccl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;/usr/local/nccl/lib&amp;#34;&lt;/span> &amp;gt;&amp;gt;/etc/ld.so.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ldconfig
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ln -s /usr/local/nccl/include/nccl.h /usr/local/include/nccl.h
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Remove NCCL package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm https://xxx/nccl_2.27.3-1+cuda12.9_x86_64.txz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Also add &lt;code>export NCCL_HOME=/usr/local/nccl&lt;/code> in your shell rc file,&lt;/p>
&lt;h3 id="install-nvidia-container-toolkit">Install NVIDIA Container Toolkit
&lt;/h3>&lt;p>To run Docker containers with GPUs.&lt;/p>
&lt;p>Note that the URLs below are mirrors in China. You can use original URL if you prefer.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -fsSL https://mirrors.ustc.edu.cn/libnvidia-container/gpgkey &lt;span class="p">|&lt;/span> sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -s -L https://mirrors.ustc.edu.cn/libnvidia-container/stable/deb/nvidia-container-toolkit.list &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> sed &lt;span class="s1">&amp;#39;s#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed -i &lt;span class="s1">&amp;#39;s/nvidia.github.io/mirrors.ustc.edu.cn/g&amp;#39;&lt;/span> /etc/apt/sources.list.d/nvidia-container-toolkit.list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get install -y nvidia-container-toolkit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Configure Docker and Containerd to use NVIDIA Container Toolkit&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-ctk runtime configure --runtime&lt;span class="o">=&lt;/span>docker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-ctk runtime configure --runtime&lt;span class="o">=&lt;/span>containerd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Since we are running inside unprivileged containers, we need to set &lt;code>no-cgroups&lt;/code> to true&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="install-nvidia-nsight-systems">Install NVIDIA Nsight Systems
&lt;/h3>&lt;p>So we can profile applications running on the GPU.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">wget https://xxx/NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dpkg -i NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -f NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="functionality-tests">Functionality Tests
&lt;/h3>&lt;h4 id="nccl">NCCL
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/NVIDIA/nccl-tests.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> nccl-tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git reset --hard 903918f &lt;span class="c1"># I only tested this commit.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>P2P ON: &lt;code>NCCL_P2P_LEVEL=sys ./build/all_reduce_perf --minbytes 8 --maxbytes 128M --stepfactor 2 --ngpus 8&lt;/code> (change &lt;code>--ngpus&lt;/code> accordingly)&lt;/p>
&lt;p>P2P OFF: &lt;code>NCCL_P2P_DISABLE=1 ./build/all_reduce_perf --minbytes 8 --maxbytes 128M --stepfactor 2 --ngpus 8&lt;/code> (change &lt;code>--ngpus&lt;/code> accordingly)&lt;/p>
&lt;p>You should see a bandwidth increase after P2P is on.&lt;/p>
&lt;h4 id="cuda-p2p">CUDA P2P
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/NVIDIA/cuda-samples.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git reset --hard 9c688d7 &lt;span class="c1"># I only tested this commit.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> cuda-samples/Samples/5_Domain_Specific/p2pBandwidthLatencyTest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./p2pBandwidthLatencyTest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You should see lower GPU-GPU latency, higher bandwidth if P2P is on.&lt;/p>
&lt;h3 id="prepare-for-templating">Prepare for Templating
&lt;/h3>&lt;p>Check if there are unnecessary files and remove them.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ls -l
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Remove history&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">unset&lt;/span> HISTFILE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm .*_history
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> -n &amp;gt;/var/log/lastlog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> -n &amp;gt;/var/log/wtmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> -n &amp;gt;/var/log/btmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">journalctl --vacuum-time&lt;span class="o">=&lt;/span>1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -f /var/log/*.log
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now, it&amp;rsquo;s ready for converting to a template. To give each student a new development container, just clone this template.&lt;/p>
&lt;h2 id="other-minor-settings">Other Minor Settings
&lt;/h2>&lt;h3 id="raid-card-write-back">RAID Card Write Back
&lt;/h3>&lt;p>To achieve maximum write speed, you can enable &lt;code>write back&lt;/code> mode on your RAID card, so data is written to cache first. This will significantly (yes, by A LOT, sometimes over 100x) improve random write speed on HDDs (SSDs are already very fast). If your RAID card has a battery, you don&amp;rsquo;t need to worry about data integrity in case a power failure.&lt;/p>
&lt;p>Write cache policy:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Write Throuh&lt;/code>: no cache&lt;/li>
&lt;li>&lt;code>Write Back&lt;/code>: cached if battery backup is present on the RAID card&lt;/li>
&lt;li>&lt;code>Always Write Back&lt;/code>: always cached&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm4.jpeg"
width="800"
height="600"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm4_hu_cf6cf63f2dd5b21c.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm4_hu_e57234e7f2dfb528.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm4_hu_8b6806be98b2182f.webp 800w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm4_hu_c76764e8bd8ebf46.jpeg 800w"
loading="lazy"
alt="kvm4"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;h2 id="obstacles">Obstacles
&lt;/h2>&lt;h3 id="only-255-cores-are-recognized---x2apic">Only 255 Cores are Recognized - x2APIC
&lt;/h3>&lt;p>I noticed that I have one CPU offline. It should recognize 256 CPUs but I only have 255 CPUs.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># lscpu
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Architecture: x86_64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CPU op-mode(s): 32-bit, 64-bit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Address sizes: 48 bits physical, 48 bits virtual
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Byte Order: Little Endian
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CPU(s): 256
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> On-line CPU(s) list: 0-254
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Off-line CPU(s) list: 255 &amp;lt;--------------- one CPU is offline -----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Vendor ID: AuthenticAMD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Model name: AMD EPYC 7763 64-Core Processor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CPU family: 25
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Model: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Thread(s) per core: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Core(s) per socket: 64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Socket(s): 2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>After some digging, I learned that I need to enable &lt;code>x2APIC&lt;/code> on both the motherboard and kernel to have more than 255 CPU cores recognized.&lt;/p>
&lt;p>In BIOS &lt;code>Local APIC Mode&lt;/code> should already be &lt;code>x2APIC&lt;/code> .&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm9.jpeg"
width="800"
height="600"
srcset="https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm9_hu_2d8789994fa6a9a2.webp 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm9_hu_21d37ab5a4ab49.jpeg 480w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm9_hu_fe3ad68b7055c380.webp 800w, https://charlie0129.github.io/blog/p/proxmox-ve-shared-gpu-installation-guide/images/kvm9_hu_7ce9a82287fa5f2a.jpeg 800w"
loading="lazy"
alt="kvm9"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;p>Now I realized it can be because I disabled IOMMU for PCIe P2P before. It turns out that disableing IOMMU can limit the number of available logical cores to 255. The reason is that the Linux kernel disables x2APIC in this case and falls back to APIC, which can only enumerate a maximum of 255 (logical) cores.&lt;/p>
&lt;p>I will keep IOMMU disabled because I need PCIe P2P to function (I don&amp;rsquo;t want to deal with P2P with IOMMU on). Just forget about the 256th core :p.&lt;/p></description></item><item><title>gVisor Killed My Node - A Debug Process</title><link>https://charlie0129.github.io/blog/p/gvisor-oom-debug/</link><pubDate>Mon, 15 Apr 2024 23:38:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/gvisor-oom-debug/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Recent Update&lt;/strong>: One month later, after we opened our issue &lt;a class="link" href="https://github.com/google/gvisor/issues/10264" target="_blank" rel="noopener"
>#10264&lt;/a>, it turns out that the gVisor developers are also aware of tha lacking of documentation about the &lt;code>systemd-cgroup&lt;/code> option. Finally:&lt;/p>
&lt;ul>
&lt;li>They have added the &lt;code>systemd-cgroup&lt;/code> option to the documentation in &lt;a class="link" href="https://github.com/google/gvisor/commit/8c3abba8000496c06341653f158dc34a4318565a" target="_blank" rel="noopener"
>8c3abba&lt;/a>, although the documentation website doesn&amp;rsquo;t seem to be updated as of now.&lt;/li>
&lt;li>&lt;code>runsc&lt;/code> will throw a warning if it detects a systemd-like path but &lt;code>systemd-cgroup&lt;/code> is not used to tell you about the possible misconfiguration.&lt;/li>
&lt;/ul>
&lt;p>So if you are using gVisor with systemd cgroup, you should add the &lt;code>systemd-cgroup&lt;/code> option to the &lt;code>runsc&lt;/code> configuration, if not already.&lt;/p>&lt;/blockquote>
&lt;h2 id="the-problem">The Problem
&lt;/h2>&lt;p>Recently we found some of our Kubernetes nodes were constant going down, completely dead, with no luck connecting to it. Taints like &lt;code>node.kubernetes.io/unreachable:NoSchedule&lt;/code> and &lt;code>node.kubernetes.io/unreachable:NoExecute&lt;/code> were automatically added to the nodes because the &lt;code>kubelet&lt;/code> was not able to communicate with the API server. The only way to bring the node back was to restart it. After some debugging, we found out that some bad gVisor-created (&lt;code>runsc&lt;/code> runtime) Pods was eating all the memory of the node and killing it. Traditional Pods with &lt;code>runc&lt;/code> runtime were running fine.&lt;/p>
&lt;p>You may say, &amp;ldquo;But there are memory limit on a Pod. If it uses more than the limit it should be killed by the OOM killer, right&amp;rdquo;？ That&amp;rsquo;s what I thought too, so I applied the following Deployment to intentionally consume some memory and see if it will be killed.&lt;/p>
&lt;details>
&lt;summary>Click to see full yaml&lt;/summary>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- -&lt;span class="l">c&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">big_var=data; while true; do big_var=&amp;#34;$big_var$big_var&amp;#34;; done; sleep 2d&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">python:3.12-bookworm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readOnlyRootFilesystem&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsNonRoot&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsUser&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">256Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">200Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dnsPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hostNetwork&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restartPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runtimeClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gvisor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/details>
&lt;p>I applied 256MiB memory limit on it, hoping it will be kill after it uses more than 256MiB memory. But it didn&amp;rsquo;t. &lt;em>It kept eating all the memory until the node was dead&lt;/em>, bypass OOM protection provided by Kubernetes. It ate over 500GiB of memory, which is the total memory of the node, and we were no longer able to connect to the node. Luckily, we eviction process kicked in and existing Pods on the node are moved to other nodes, so the service was not affected by much.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage.png"
width="1106"
height="538"
srcset="https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_b90a3efcf7392c28.webp 480w, https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_7ba58d493ffed2f9.jpg 480w, https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_6bec7790d7fb5582.webp 1024w, https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_9e613324adbe9273.jpg 1024w, https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_c2cb55ef979de6f8.webp 1106w, https://charlie0129.github.io/blog/p/gvisor-oom-debug/images/bad-pod-memory-usage_hu_68b662a4e2e32556.jpg 1106w"
loading="lazy"
alt="Bad Pod Memory Usage"
class="gallery-image"
data-flex-grow="205"
data-flex-basis="493px"
>
&lt;/p>
&lt;h2 id="the-debug-process">The Debug Process
&lt;/h2>&lt;p>Of course, we need to apply the above memory-eater Deployment to reproduce the issue. But killing a node every time is not a good idea 😂 , especially when we need to manually restart it. So I used another Deployment to consume &lt;em>some&lt;/em> (1GiB) memory, but not all of it.&lt;/p>
&lt;details>
&lt;summary>Click to see full yaml&lt;/summary>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">memory-eater-python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- -&lt;span class="l">c&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">big_list = []; print(&amp;#39;Allocating 1GB of memory...&amp;#39;); [big_list.append(&amp;#39; &amp;#39; * 10**6) for _ in range(1000)]; import time; time.sleep(100000)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">python:3.12-bookworm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readOnlyRootFilesystem&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsNonRoot&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsUser&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">999&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">256Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">200Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dnsPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hostNetwork&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restartPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runtimeClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gvisor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/details>
&lt;p>Just as I expected, the memory limit did not work. Time to debug.&lt;/p>
&lt;h3 id="trying-to-reproduce">Trying to reproduce
&lt;/h3>&lt;p>Before I trying to do anything on the production cluster, I tried to reproduce the issue on my local machine. Just to be safe not to kill the production cluster and get fired :P (just kidding. It&amp;rsquo;s because debugging is a lot easier on my local machine).&lt;/p>
&lt;p>I used Docker with gVisor as per the &lt;a class="link" href="https://gvisor.dev/docs/user_guide/quick_start/docker/" target="_blank" rel="noopener"
>official documentation&lt;/a>. It was working fine. Our production cluster is using &lt;code>containerd&lt;/code>, so was it a &lt;code>containerd&lt;/code> issue? I was in a hurry so I used &lt;code>minikube&lt;/code> with &lt;code>gVisor&lt;/code> addon enabled, which should provide a Kubernetes cluster with &lt;code>containerd&lt;/code> and gVisor (and a &lt;code>containerd&lt;/code> shim for &lt;code>runsc&lt;/code>). At the time of writing, it was Kubernetes &lt;code>v1.28.3&lt;/code>, containerd &lt;code>v1.6.24&lt;/code>, and gVisor &lt;code>release-20240401.0&lt;/code>.&lt;/p>
&lt;blockquote>
&lt;p>I know, I know. We should keep versions of the software same as the production cluster. But I was in a hurry and I was not sure if the issue was with &lt;code>containerd&lt;/code> or &lt;code>gVisor&lt;/code>. So I just went with it. Spoiler: yes, it has something to do with versions. If I were to do it again, I would use the same versions as the production cluster.&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">minikube start --addons&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gvisor&amp;#34;&lt;/span> --container-runtime&lt;span class="o">=&lt;/span>containerd --driver&lt;span class="o">=&lt;/span>docker --cpus&lt;span class="o">=&lt;/span>&lt;span class="m">6&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I applied the memory-eater Deployment and waited some surprising results. But the 256MiB memory limit was working as expected. The Pod was killed after it used more than 256MiB memory. I smelled some fishy things going on in the production cluster.&lt;/p>
&lt;h3 id="debugging-on-the-production-cluster">Debugging on the production cluster
&lt;/h3>&lt;p>Again, I applied the memory-eater Deployment, but on the production cluster this time. Hoping it will not kill the node. To my relief, it did not kill the node. But it did not kill the Pod with its memory (969MiB) above limits (256MiB) either (which is expected).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> kubectl top pod memory-eater-python-67cc4dd4f7-sp2r6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">NAME CPU(cores) MEMORY(bytes)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">memory-eater-python-67cc4dd4f7-sp2r6 0m 969Mi
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Before I dig anything deeper (like with some gVisor debugging tools), I checked the sandbox created by &lt;code>runsc&lt;/code> (gVisor) to see if there is anything suspicious.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">#&lt;/span> On production node
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nv">PID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>ps aux &lt;span class="p">|&lt;/span> grep memory-eater-python &lt;span class="p">|&lt;/span> grep runsc-sandbox &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> cat /proc/&lt;span class="nv">$PID&lt;/span>/cmdline &lt;span class="p">|&lt;/span> tr &lt;span class="s1">&amp;#39;\0&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">runsc-sandbox
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log-format=json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--panic-log=/var/log/pods/default_memory-eater-python-74975bf59f-7fflh_1cf55082-d769-4a51-a424-2312c23eae82/gvisor_panic.log
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--root=/run/containerd/runsc/k8s.io
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log=/run/containerd/io.containerd.runtime.v2.task/k8s.io/6a541285fa39d743dc20bd8b3169c4a8f5cd4d77542144fe82d3f6f7af469c2c/log.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log-fd=3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--panic-log-fd=4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">boot
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--apply-caps=false
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--bundle=/run/containerd/io.containerd.runtime.v2.task/k8s.io/6a541285fa39d743dc20bd8b3169c4a8f5cd4d77542144fe82d3f6f7af469c2c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--controller-fd=10
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--cpu-num=96
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--io-fds=5,6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--mounts-fd=7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--overlay-mediums=0,0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--setup-root=false
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--spec-fd=11
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--start-sync-fd=8
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--stdio-fds=12,13,14
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--total-host-memory=540886331392
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--total-memory=540886331392 # 503GiB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--user-log-fd=9
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--product-name=AS
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">-4124GS-TNR
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--proc-mount-sync-fd=22
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">6a541285fa39d743dc20bd8b3169c4a8f5cd4d77542144fe82d3f6f7af469c2c
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Hmm, something caught my eye.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">--total-host-memory=540886331392
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--total-memory=540886331392 # 503GiB
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Why is the &lt;code>total-memory&lt;/code> and &lt;code>total-host-memory&lt;/code> set to 503GiB? That&amp;rsquo;s the total memory of the node. I assume it should be set to the memory limit of the Pod, which is 256MiB. To verify my assumption, I checked the sandbox created by &lt;code>runsc&lt;/code> on my local machine and it indeed was set to 256MiB.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">#&lt;/span> On my &lt;span class="nb">local&lt;/span> machine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nv">PID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>ps aux &lt;span class="p">|&lt;/span> grep memory-eater-python &lt;span class="p">|&lt;/span> grep runsc-sandbox &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> cat /proc/&lt;span class="nv">$PID&lt;/span>/cmdline &lt;span class="p">|&lt;/span> tr &lt;span class="s1">&amp;#39;\0&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">runsc-sandbox
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--root=/run/containerd/runsc/k8s.io
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log=/run/containerd/io.containerd.runtime.v2.task/k8s.io/f11a26742777348875e6c7f182d9f8cc5c4a1c3a7f726ad5e8cb956b0c2dea96/log.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log-format=json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--panic-log=/var/log/pods/default_memory-eater-python-fc75975c8-vncwz_480a4dd9-22ad-4e43-9ecb-b797735e990a/gvisor_panic.log
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--log-fd=3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--panic-log-fd=4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">boot
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--apply-caps=false
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--bundle=/run/containerd/io.containerd.runtime.v2.task/k8s.io/f11a26742777348875e6c7f182d9f8cc5c4a1c3a7f726ad5e8cb956b0c2dea96
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--controller-fd=10
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--cpu-num=6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--dev-io-fd=-1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--gofer-mount-confs=lisafs:none,lisafs:none
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--io-fds=5,6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--mounts-fd=7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--setup-root=false
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--spec-fd=11
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--start-sync-fd=8
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--stdio-fds=12,13,14
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--total-host-memory=2061119488
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--total-memory=268435456 # 256MiB
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--user-log-fd=9
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">--proc-mount-sync-fd=22
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">f11a26742777348875e6c7f182d9f8cc5c4a1c3a7f726ad5e8cb956b0c2dea96
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>It has the correct limits on my local machine.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">--total-host-memory=2061119488
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--total-memory=268435456 # 256MiB
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So the issue was with this &lt;code>--total-memory&lt;/code> argument. But why is it incorrect on the production cluster?&lt;/p>
&lt;h3 id="checking-the-versions">Checking the versions
&lt;/h3>&lt;p>Seeing the difference, I checked the versions on my local cluster and the production cluster.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tool&lt;/th>
&lt;th>Local&lt;/th>
&lt;th>Production&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Kubernetes&lt;/td>
&lt;td>1.28.3&lt;/td>
&lt;td>1.26.2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>containerd&lt;/td>
&lt;td>v1.6.24&lt;/td>
&lt;td>v1.7.0-rc.1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>runsc&lt;/td>
&lt;td>20240401.0&lt;/td>
&lt;td>20231009.0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Both are running Ubuntu Server 22.04 LTS, using systemd+cgroupsv2.&lt;/p>
&lt;p>I assume the problem was with runsc (gVisor).&lt;/p>
&lt;h2 id="whats-wrong-with-gvisor">What&amp;rsquo;s wrong with gVisor?
&lt;/h2>&lt;h3 id="cgroups-v2-support">Cgroups v2 support
&lt;/h3>&lt;p>After I know the issue is with the &lt;code>--total-memory&lt;/code> flag. I found there is a similar issue reported &lt;a class="link" href="https://github.com/google/gvisor/issues/9580" target="_blank" rel="noopener"
>google/gvisor #9580&lt;/a>. The author had great explanation of the issue:&lt;/p>
&lt;blockquote>
&lt;p>Under Kubernetes + cgroups v2 + systemd, gVisor launches all processes into the container subgroup associated with the pause container. This makes some sense given that cgroups v2 specifies that processes can only exist at leaf nodes, and the pod&amp;rsquo;s cgroup is registered as a slice (an intermediate unit which cannot have its own processes) with systemd. When the sandbox is launched gVisor needs a container subgroup and the pause container is the first to be launched. The pause container is a child of the pod cgroup and therefore inherits the limits of the parent pod cgroup, BUT the child&amp;rsquo;s controllers reflect the default max value. This in turn means that this code which reads the memory limit and cpu quota reads these as unlimited.&lt;/p>&lt;/blockquote>
&lt;p>Let me explain a bit more. So a normal cgroup v2 hierarchy for a Pod looks like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/sys/fs/cgroup/kubepods.slice/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kubepods-burstable.slice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── kubepods-burstable-pod&amp;lt;pod_id&amp;gt;.slice # has memory limit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── cri-containerd-&amp;lt;container_id&amp;gt;.scope # no memory limit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The pod slice has a memory limit (cat &lt;code>memory.max&lt;/code> gives &lt;code>xxx&lt;/code>), but the container scope does not (cat &lt;code>memory.max&lt;/code> gives &lt;code>max&lt;/code>). The container scope is where the container is running. The container scope is a child of the pod slice. So the container should inherit the memory limit of the pod slice. This is how cgroups works.&lt;/p>
&lt;p>But gVisor only checks the container scope and says &amp;ldquo;oh, it has no memory limit. Let&amp;rsquo;s set the total memory to max (the total memory of the node)&amp;rdquo;. And that&amp;rsquo;s why the &lt;code>--total-memory&lt;/code> is set to 503GiB on the production cluster.&lt;/p>
&lt;p>This issue was fixed in &lt;a class="link" href="https://github.com/google/gvisor/pull/9631" target="_blank" rel="noopener"
>google/gvisor #9631&lt;/a>. The code below is from the PR which fixed the issue (after the comments). So it checks the Pos slice (parent) if the memory limit is not set in the container slice (leaf node).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: runsc/cgroup/cgroup_v2.go&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/google/gvisor/blob/43c2c00c5006e7d21ce9cd431692622031891231/runsc/cgroup/cgroup_v2.go#L333-L346&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// MemoryLimit returns the memory limit.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">cgroupV2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">MemoryLimit&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">limStr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">getMemoryLimit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MakePath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// In cgroupv2+systemd, limits are set in the parent slice rather&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// than the leaf node. Check the parent to see if this is the case.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">limStr&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">maxLimitStr&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">parentLimStr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">getMemoryLimit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filepath&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MakePath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ErrNotExist&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">parentLimStr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">limStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">parentLimStr&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">limStr&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">maxLimitStr&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MaxUint64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ParseUint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">limStr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>runsc&lt;/code> binary on our production cluster was built before the fix was merged. So I assume the issue was fixed in the &lt;code>runsc&lt;/code> binary after the merge.&lt;/p>
&lt;h3 id="systemd-cgroup-configuration">Systemd cgroup configuration
&lt;/h3>&lt;p>So if the issue was fixed in the latest release, simply upgrading the &lt;code>runsc&lt;/code> binary should fix the issue. No. The issue was still there. I checked the &lt;code>runsc&lt;/code> binary on the production cluster and it was built after the fix was merged. So what&amp;rsquo;s wrong?&lt;/p>
&lt;p>Remember what a normal cgroup v2 hierarchy looks like in the previous section? Although the cgroups created by &lt;code>runc&lt;/code> are just like that, that&amp;rsquo;s not how the ones created by &lt;code>runsc&lt;/code> looks like on our production cluster. Instead, they look like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/sys/fs/cgroup/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kubepods.slice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── kubepods-burstable.slice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── kubepods-burstable-pod&amp;lt;pod_id&amp;gt;.slice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── system.slice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── kubepods-burstable-pod&amp;lt;pod_id&amp;gt;.slice:cri-containerd:&amp;lt;container_id&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Why are the container cgroups not under the pod cgroups? Why are they in system.slice? If that was the case, the fix above won&amp;rsquo;t do anything because the parent slice is not the pod slice. The parent slice is system.slice, it has no memory limit. So the container will have no memory limit even after the fix. OOM&amp;rsquo;in the node.&lt;/p>
&lt;p>After some issue-searching on the gVisor GitHub repo, I found that runsc initially has no support for systemd cgroups &lt;a class="link" href="https://github.com/google/gvisor/issues/193" target="_blank" rel="noopener"
>google/gvisor #193&lt;/a>. Support for systemd cgroup was added in &lt;a class="link" href="https://github.com/google/gvisor/pull/7287" target="_blank" rel="noopener"
>google/gvisor #7287&lt;/a>. It required a specific configuration in the &lt;code>runsc&lt;/code> configuration:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">// File: runsc/config/config.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Link: https://github.com/google/gvisor/blob/bf86207401cab99d859b25acd4911038608f0d33/runsc/config/config.go#L229-L230
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ // Use systemd to configure cgroups.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ SystemdCgroup bool `flag:&amp;#34;systemd-cgroup&amp;#34;`
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Hmm, looks like there was nothing like that in the &lt;code>runsc&lt;/code> configuration on the production cluster. Go! Fix it! Find one of the cordoned node, drain it, upgrade the &lt;code>runsc&lt;/code> binary, and change the configuration.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">/etc/containerd/config.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [plugins.&amp;#34;io.containerd.grpc.v1.cri&amp;#34;.containerd.runtimes.runsc]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> runtime_type = &amp;#34;io.containerd.runsc.v1&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ [plugins.&amp;#34;io.containerd.grpc.v1.cri&amp;#34;.containerd.runtimes.runsc.options]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ TypeUrl = &amp;#34;io.containerd.runsc.v1.options&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ ConfigPath = &amp;#34;/etc/containerd/runsc.toml&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/etc/containerd/runsc.toml&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ [runsc_config]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ systemd-cgroup = &amp;#34;true&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Restart containerd &lt;code>systemctl restart containerd&lt;/code>. Apply a new Pod with &lt;em>toleration&lt;/em> and &lt;em>nodeSelector&lt;/em> to schedule it to a cordoned node. And it worked! The container cgroups are now under the pod cgroups. The &lt;code>--total-memory&lt;/code> is set to 256MiB. The Pod was killed after it used more than 256MiB memory. The node was not killed. The issue was fixed.&lt;/p>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>So the issue was a combination of old gVisor version and incorrect configuration. The gVisor version on the production cluster was built before the fix was merged. The configuration was missing the &lt;code>systemd-cgroup&lt;/code> option. The issue was fixed by upgrading the &lt;code>runsc&lt;/code> binary and adding the &lt;code>systemd-cgroup&lt;/code> option to the configuration.&lt;/p></description></item><item><title>Docker and ZFS - A Tough Pair</title><link>https://charlie0129.github.io/blog/p/docker-and-zfs-a-tough-pair/</link><pubDate>Sun, 07 Aug 2022 08:54:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/docker-and-zfs-a-tough-pair/</guid><description>&lt;p>Outline:&lt;/p>
&lt;ul>
&lt;li>Know ZFS filesystem and Docker&amp;rsquo;s &lt;code>zfs&lt;/code> storage driver&lt;/li>
&lt;li>How Docker&amp;rsquo;s &lt;code>zfs&lt;/code> driver works and Why it is slow&lt;/li>
&lt;li>Why we can&amp;rsquo;t directly use &lt;code>overlayfs&lt;/code> on ZFS
&lt;ul>
&lt;li>Why &lt;code>overlayfs&lt;/code> don&amp;rsquo;t accept remote filesystems&lt;/li>
&lt;li>Why ZFS is identified as a remote filesystem&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How to actually solve the problem&lt;/li>
&lt;/ul>
&lt;p>We will dive into the source code of &lt;a class="link" href="https://github.com/moby/moby" target="_blank" rel="noopener"
>moby&lt;/a>, &lt;a class="link" href="https://github.com/openzfs/zfs" target="_blank" rel="noopener"
>OpenZFS&lt;/a>, and Linux &lt;a class="link" href="https://github.com/torvalds/linux" target="_blank" rel="noopener"
>kernel&lt;/a> to find out.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> I admit this blog is not so beginner-friendly, which requires some prerequisites, otherwise you may have a hard time reading it though. I will give some questions or concepts after each prerequisite to help you know your understanding is enough on this topic.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>general computer/unix concepts&lt;/strong> (&lt;a class="link" href="https://en.wikipedia.org/wiki/Device_file#Block_devices" target="_blank" rel="noopener"
>block devices&lt;/a>, &lt;a class="link" href="https://en.wikipedia.org/wiki/Copy-on-write" target="_blank" rel="noopener"
>copy-on-write&lt;/a>, &lt;a class="link" href="https://en.wikipedia.org/wiki/Mount_%28computing%29" target="_blank" rel="noopener"
>mount points&lt;/a>)&lt;/li>
&lt;li>&lt;strong>basic filesystem concepts&lt;/strong> (difference between block devices and filesystems, common filesystems, &lt;a class="link" href="https://www.starlab.io/blog/introduction-to-the-linux-virtual-filesystem-vfs-part-i-a-high-level-tour" target="_blank" rel="noopener"
>basic understanding of Linux Virtual Filesystem&lt;/a>)&lt;/li>
&lt;li>&lt;strong>the basics of ZFS filesystem&lt;/strong> (&lt;a class="link" href="https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html" target="_blank" rel="noopener"
>terminologies like datasets and snapshots&lt;/a>, &lt;a class="link" href="https://manpages.ubuntu.com/manpages/jammy/man8/zfs-rollback.8.htmlunionfs" target="_blank" rel="noopener"
>what are rollbacks&lt;/a>)&lt;/li>
&lt;li>&lt;strong>Docker images&lt;/strong> (&lt;a class="link" href="https://docs.docker.com/storage/storagedriver/#images-and-layers" target="_blank" rel="noopener"
>what are image layers&lt;/a>, when they are created/deleted, how it works with UnionFS)&lt;/li>
&lt;/ul>
&lt;h2 id="background">Background
&lt;/h2>&lt;h3 id="what-is-zfs">What is ZFS
&lt;/h3>&lt;p>Described as &lt;em>The last word in filesystems&lt;/em>, ZFS is scalable, and includes extensive protection against data corruption, support for high storage capacities, efficient data compression, integration of the concepts of filesystem and volume management, snapshots and copy-on-write clones, continuous integrity checking and automatic repair, RAID-Z, native NFSv4 ACLs, and can be very precisely configured. &lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;p>By saying ZFS, I am referring to OpenZFS on Linux and FreeBSD: &lt;a class="link" href="https://openzfs.github.io/openzfs-docs/index.html" target="_blank" rel="noopener"
>OpenZFS Documentation&lt;/a>&lt;/p>
&lt;p>ZFS is a great and sophisticated filesystem, really robust and stable. It never failed my expectations. I personally use ZFS on my personal devices, whenever possible, e.g. laptops (Ubuntu Desktop - for its built-in support for ZFS), NAS (TrueNAS SCALE), and servers (Proxmox VE and Ubuntu Server).&lt;/p>
&lt;h3 id="what-is-a-docker-storage-driver">What is a Docker storage driver
&lt;/h3>&lt;p>Docker uses storage drivers to store image layers, and to store data in the writable layer of a container. The container’s writable layer does not persist after the container is deleted, but is suitable for storing ephemeral data that is generated at runtime. Storage drivers are optimized for space efficiency, but (depending on the storage driver) write speeds are lower than native file system performance, especially for storage drivers that use a copy-on-write filesystem. Write-intensive applications, such as database storage, are impacted by a performance overhead, particularly if pre-existing data exists in the read-only layer. &lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;p>By default, Docker will use &lt;code>overlay2&lt;/code> whenever possible for all Linux distributions.&lt;/p>
&lt;h3 id="zfs-and-docker-storage-driver">ZFS and Docker storage driver?
&lt;/h3>&lt;p>The Docker Engine provides a &lt;code>zfs&lt;/code> storage drivers on Linux, which requires a ZFS filesystem, allowing for advanced options, such as creating snapshots, but require more maintenance and setup.&lt;/p>
&lt;p>According to Docker docs, the &lt;code>zfs&lt;/code> storage driver has the following advantages &lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> :&lt;/p>
&lt;ul>
&lt;li>Avoids the container’s writable layer grow too large in write-heavy workloads.&lt;/li>
&lt;li>Performs better for write-heavy workloads (though not as well as Docker volumes).&lt;/li>
&lt;li>A good choice for high-density workloads such as PaaS.&lt;/li>
&lt;/ul>
&lt;p>Hmm, sounds good, right? Well, keep reading. If it is that good, this blog wouldn&amp;rsquo;t exist at the first place.&lt;/p>
&lt;blockquote>
&lt;p>In this blog, &lt;code>zfs&lt;/code> refers to Docker&amp;rsquo;s &lt;code>zfs&lt;/code> storage driver, mostly, but may also refer to ZFS filesystem. You should be able to distinguish them by context.&lt;/p>&lt;/blockquote>
&lt;h2 id="whats-the-problem">What&amp;rsquo;s the problem?
&lt;/h2>&lt;p>There is one single problem with ZFS that bothered me since the very beginning: &lt;strong>Docker&lt;/strong>.&lt;/p>
&lt;p>When using Docker on ZFS, one can only use its &lt;code>zfs&lt;/code> driver (no, you cannot use &lt;code>overlay2&lt;/code> directly. We will see why later.). Although Docker docs &lt;em>proudly&lt;/em> advertise its &lt;code>zfs&lt;/code> driver as something high-performance:&lt;/p>
&lt;blockquote>
&lt;p>zfs is a good choice for high-density workloads such as PaaS. &lt;sup id="fnref1:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>&lt;/p>&lt;/blockquote>
&lt;p>But in practice, this thing is &lt;em>slow as hell&lt;/em>, specifically, when creating image layers. Build times can go from &lt;strong>a fraction of a second&lt;/strong> on &lt;code>overlay2&lt;/code> to &lt;strong>several minutes&lt;/strong> on &lt;code>zfs&lt;/code>!&lt;/p>
&lt;p>&lt;strong>It is several magnitudes slower, a complete disaster.&lt;/strong>&lt;/p>
&lt;p>Let&amp;rsquo;s take the Dockerfile in &lt;a class="link" href="https://github.com/kubevela/kube-trigger" target="_blank" rel="noopener"
>kube-trigger&lt;/a> (a project that I worked on recently) as an example.&lt;/p>
&lt;details>
&lt;summary>Click to see the complete Dockerfile&lt;/summary>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Copyright 2022 The KubeVela Authors.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c">#&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Licensed under the Apache License, Version 2.0 (the &amp;#34;License&amp;#34;);&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># you may not use this file except in compliance with the License.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># You may obtain a copy of the License at&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c">#&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># http://www.apache.org/licenses/LICENSE-2.0&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c">#&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Unless required by applicable law or agreed to in writing, software&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># See the License for the specific language governing permissions and&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># limitations under the License.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Note: the ${BIN} needs to be replaced with the actual binary,&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># otherwise it won&amp;#39;t work. Refer to Makefile for how it can be done.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> &lt;span class="nv">BUILD_IMAGE&lt;/span>&lt;span class="o">=&lt;/span>golang:1.17&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> &lt;span class="nv">BASE_IMAGE&lt;/span>&lt;span class="o">=&lt;/span>gcr.io/distroless/static:nonroot&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Force native build platform, and cross-build to target platform later.&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> --platform=${BUILDPLATFORM:-linux/amd64} ${BUILD_IMAGE} as builder&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /workspace&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> go.mod go.mod&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> go.sum go.sum&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> GOPROXY&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">GOPROXY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">GOPROXY&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> go mod download&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> build build&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> hack hack&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> cmd cmd&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> api api&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> controllers controllers&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pkg pkg&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> TARGETARCH&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> ARCH&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> TARGETOS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> OS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> VERSION&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> GOFLAGS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> DIRTY_BUILD&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> ENTRY&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> &lt;span class="nv">ARCH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">TARGETARCH&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">ARCH&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="nv">amd64&lt;/span>&lt;span class="si">}}&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">OS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">TARGETOS&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">OS&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="nv">linux&lt;/span>&lt;span class="si">}}&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">OUTPUT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">BIN&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">VERSION&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">GOFLAGS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">GOFLAGS&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> /bin/sh build/build.sh &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="si">${&lt;/span>&lt;span class="nv">ENTRY&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> ${BASE_IMAGE}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --from&lt;span class="o">=&lt;/span>builder /workspace/&lt;span class="si">${&lt;/span>&lt;span class="nv">BIN&lt;/span>&lt;span class="si">}&lt;/span> .&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;/${BIN}&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/details>
&lt;p>We will focus on L39-L46:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ARG&lt;/span> TARGETARCH&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> ARCH&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> TARGETOS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> OS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> VERSION&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> GOFLAGS&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> DIRTY_BUILD&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ARG&lt;/span> ENTRY&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You might be thinking, this is just some build args, so what? Yes, this part almost does nothing (creates some image layers), and should finish immediately. That&amp;rsquo;s exactly the case on &lt;code>overlay2&lt;/code>, but not on &lt;code>zfs&lt;/code>, which will &lt;strong>take minutes&lt;/strong>!&lt;/p>
&lt;p>Such slow build times are driving me crazy.&lt;/p>
&lt;h2 id="why-zfs-driver-is-so-slow">Why &lt;code>zfs&lt;/code> driver is so slow?
&lt;/h2>&lt;h3 id="how-zfs-storage-driver-works">How ZFS storage driver works?
&lt;/h3>&lt;p>When using docker on a &lt;code>zfs&lt;/code> dataset, the only option is Docker&amp;rsquo;s &lt;code>zfs&lt;/code> driver, which uses ZFS dataset operations to create layered filesystems. The &lt;code>zfs&lt;/code> storage driver for Docker stores &lt;strong>each layer of each image&lt;/strong> as a separate legacy dataset. Even just a handful of images can result in a huge number of layers, each layer corresponding to a &lt;code>legacy&lt;/code> ZFS dataset. As a result, there are hundreds of datasets created when only running a dozen containers.&lt;/p>
&lt;blockquote>
&lt;p>The base layer of an image is a ZFS filesystem. Each child layer is a ZFS clone based on a ZFS snapshot of the layer below it. A container is a ZFS clone based on a ZFS Snapshot of the top layer of the image it’s created from. &lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>&lt;/p>&lt;/blockquote>
&lt;h3 id="wheres-the-bottleneck">Where&amp;rsquo;s the bottleneck?
&lt;/h3>&lt;p>Although when building images it do not have to deal with such many datasets. It will still spend a fair amount of time mounting and unmounting these datasets (can be seen from Docker debug logs).&lt;/p>
&lt;p>We can take a look at the code from Docker daemon (&lt;code>moby/moby&lt;/code>).&lt;/p>
&lt;p>Mount will happen (if necessary) whenever &lt;code>Get&lt;/code> is called:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: daemon/graphdriver/zfs/zfs.go&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/moby/moby/blob/7e44b7cddd43b1771a44a2dd56548627e491c950/daemon/graphdriver/zfs/zfs.go#L365-L408&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Get returns the mountpoint for the given id after creating the target directories if necessary.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">d&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Driver&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountLabel&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">_&lt;/span> &lt;span class="nx">containerfs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ContainerFS&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">retErr&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mountpoint&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">mountPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ctr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Increment&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">containerfs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewLocalContainerFS&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">retErr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">c&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ctr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Decrement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">c&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">mntErr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">unix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">mntErr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logrus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;storage-driver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;zfs&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Error unmounting %v: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mntErr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">rmErr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">unix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Rmdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">rmErr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IsNotExist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rmErr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logrus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;storage-driver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;zfs&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Debugf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Failed to remove %s: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rmErr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">filesystem&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">zfsPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">options&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">label&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">FormatMountLabel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountLabel&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logrus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;storage-driver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;zfs&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Debugf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">`mount(&amp;#34;%s&amp;#34;, &amp;#34;%s&amp;#34;, &amp;#34;%s&amp;#34;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filesystem&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">root&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">idMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RootPair&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Create the target directories if they don&amp;#39;t exist&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">idtools&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MkdirAllAndChown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mo">0755&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">root&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">mount&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filesystem&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;zfs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wrap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;error creating zfs mount&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// this could be our first mount after creation of the filesystem, and the root dir may still have root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// permissions instead of the remapped root uid:gid (if user namespaces are enabled):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">root&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Chown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;error modifying zfs mountpoint (%s) directory ownership: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">containerfs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewLocalContainerFS&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Unmount will happen whenever &lt;code>Put&lt;/code> is called:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: daemon/graphdriver/zfs/zfs.go&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/moby/moby/blob/7e44b7cddd43b1771a44a2dd56548627e491c950/daemon/graphdriver/zfs/zfs.go#L410-L431&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Put removes the existing mountpoint for the given id if it exists.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">d&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Driver&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mountpoint&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">mountPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ctr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Decrement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logger&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">logrus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;storage-driver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;zfs&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Debugf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">`unmount(&amp;#34;%s&amp;#34;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">unix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">unix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MNT_DETACH&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Warnf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Failed to unmount %s mount %s: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">unix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Rmdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IsNotExist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Debugf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Failed to remove %s mount point %s: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mountpoint&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Although Docker will not mount a filesystem twice, changes still exist when consecutive &lt;code>Get/Put&lt;/code> call happens.&lt;/p>
&lt;p>I am not an OpenZFS developer, but it seems to me that there is a bottleneck with ZFS with such frequent mount/unmount actions (with a large mount datasets and snapshots).&lt;/p>
&lt;p>As you can see, Docker is already optimizing this situation by using the mount syscall directly (instead of calling user-space mount command, which will again, after going to kernel-space from user-space, require the kernel to call the zfs mount binary in user-space, due to ZFS&amp;rsquo;s license issues with the &lt;code>vfs_mount&lt;/code> in kernel).&lt;/p>
&lt;h2 id="one-possible-solution">One possible solution
&lt;/h2>&lt;p>So there is not much to optimize in &lt;code>zfs&lt;/code> storage drivers. It is the actual zfs mount process that is slowing image build times down. Now, the problem with Docker&amp;rsquo;s &lt;code>zfs&lt;/code> storage driver is clear. There are two options left:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>optimize ZFS mount times&lt;/p>
&lt;/li>
&lt;li>
&lt;p>just get rid of &lt;code>zfs&lt;/code> storage driver&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Of course, you can also grab another disk in &lt;code>ext4&lt;/code> as use &lt;code>overlayfs&lt;/code> on top of it. But I only have &lt;code>zfs&lt;/code>-formatted disks, so I only have the above two options.&lt;/p>
&lt;p>The first one &amp;ldquo;optimize ZFS mount times&amp;rdquo; isn&amp;rsquo;t really an option. Currently, I don&amp;rsquo;t have the expertise or time to work on OpenZFS.&lt;/p>
&lt;p>With that out of the way, we only have one option left: &amp;ldquo;do not use &lt;code>zfs&lt;/code> storage driver&amp;rdquo;, i.e., use &lt;code>overlay2&lt;/code> on &lt;code>zfs&lt;/code> datasets.&lt;/p>
&lt;h2 id="but-it-doesnt-work">But it doesn&amp;rsquo;t work
&lt;/h2>&lt;p>Now, the problem is, how do we use &lt;code>overlay2&lt;/code> storage driver on a ZFS filesystem (dataset)?&lt;/p>
&lt;p>&lt;strong>Simply put, that&amp;rsquo;s not possible (directly).&lt;/strong> ZFS makes use of &lt;code>d_revalidate&lt;/code> . Having &lt;code>d_revalidate&lt;/code> set to not &lt;code>NULL&lt;/code> will make &lt;code>overlayfs&lt;/code> refuse to work.&lt;/p>
&lt;p>But why? To understand, we need to analyze some source code from OpenZFS and Linux kernel.&lt;/p>
&lt;h3 id="whats-d_revalidate">What&amp;rsquo;s &lt;code>d_revalidate&lt;/code>?
&lt;/h3>&lt;p>&lt;code>d_revalidate&lt;/code> is defined in Linux kernel &lt;code>include/linux/dcache.h&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: include/linux/dcache.h
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/1612c382ffbdf1f673caec76502b1c00e6d35363/include/linux/dcache.h#L128
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry_operations&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">d_revalidate&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// The rest are omitted.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="n">____cacheline_aligned&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The kernel documentation has a nice description on &lt;code>d_revalidate&lt;/code>.&lt;/p>
&lt;p>TL;DR: &lt;code>d_revalidate&lt;/code> is typically used with network filesystems, and is called called when the VFS needs to revalidate a &lt;code>dentry&lt;/code>, marking this &lt;code>dentry&lt;/code> is still valid or not, to prevent things change without the client being aware of it.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&lt;code>d_revalidate&lt;/code> is called called when the VFS needs to revalidate a dentry.&lt;/strong> This is called whenever a name look-up finds a dentry in the dcache. Most local filesystems leave this as NULL, because all their dentries in the dcache are valid. &lt;strong>Network filesystems are different since things can change on the server without the client necessarily being aware of it.&lt;/strong>&lt;/p>
&lt;p>This function should return a positive value if the dentry is still valid, and zero or a negative error code if it isn’t.&lt;/p>
&lt;p>d_revalidate may be called in rcu-walk mode (flags &amp;amp; LOOKUP_RCU). If in rcu-walk mode, the filesystem must revalidate the dentry without blocking or storing to the dentry, d_parent and d_inode should not be used without care (because they can change and, in d_inode case, even become NULL under us).&lt;/p>
&lt;p>If a situation is encountered that rcu-walk cannot handle, return -ECHILD and it will be called again in ref-walk mode.&lt;/p>
&lt;p>&lt;em>Excerpt from: &lt;a class="link" href="https://docs.kernel.org/filesystems/vfs.html#struct-dentry-operations" target="_blank" rel="noopener"
>Overview of the Linux Virtual File System — The Linux Kernel documentation&lt;/a>&lt;/em>&lt;/p>&lt;/blockquote>
&lt;h3 id="why-use-d_revalidate">Why use &lt;code>d_revalidate&lt;/code>?
&lt;/h3>&lt;p>ZFS makes use of &lt;code>d_revalidate&lt;/code> to invalidate &lt;code>dcache&lt;/code> after rolling back.&lt;/p>
&lt;p>See? This is the same situation as the kernel doc describes. When a roll back happens, the underlying files are changed, but the &lt;code>dcache&lt;/code> it not updated, so we need to use &lt;code>d_revalidate&lt;/code> to mark it as invalid.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: module/os/linux/zfs/zpl_inode.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/openzfs/zfs/blob/1d3ba0bf01020f5459b1c28db3979129088924c0/module/os/linux/zfs/zpl_inode.c#L701-L739
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#ifdef HAVE_D_REVALIDATE_NAMEIDATA
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nf">zpl_revalidate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">nameidata&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">nd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">flags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">nd&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="n">nd&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="nl">flags&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#else
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nf">zpl_revalidate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#endif &lt;/span>&lt;span class="cm">/* HAVE_D_REVALIDATE_NAMEIDATA */&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span> &lt;span class="cm">/* CSTYLED */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">zfsvfs_t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">zfsvfs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_fs_info&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">error&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">flags&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">LOOKUP_RCU&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">ECHILD&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * After a rollback negative dentries created before the rollback
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * time must be invalidated. Otherwise they can obscure files which
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * are only present in the rolled back dataset.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_inode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">spin_lock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_lock&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">time_before&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_time&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">zfsvfs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">z_rollback_time&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">spin_unlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_lock&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * The dentry may reference a stale inode if a mounted file system
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * was rolled back to a point in time where the object didn&amp;#39;t exist.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_inode&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nf">ITOZ&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_inode&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">z_is_stale&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>d_revalidate&lt;/code> is set to the &lt;code>zpl_revalidate&lt;/code> function that we have seen above.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: module/os/linux/zfs/zpl_inode.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/openzfs/zfs/blob/1d3ba0bf01020f5459b1c28db3979129088924c0/module/os/linux/zfs/zpl_inode.c#L830-L832
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">dentry_operations_t&lt;/span> &lt;span class="n">zpl_dentry_operations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">d_revalidate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">zpl_revalidate&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But why is having &lt;code>d_revalidate&lt;/code> set to not &lt;code>NULL&lt;/code> will make &lt;code>overlayfs&lt;/code> refuse to work?&lt;/p>
&lt;h3 id="how-overlayfs-refuses-d_revalidate-enabled-fs">How &lt;code>overlayfs&lt;/code> refuses &lt;code>d_revalidate&lt;/code>-enabled fs
&lt;/h3>&lt;p>To understand how and why How &lt;code>overlayfs&lt;/code> refuses &lt;code>d_revalidate&lt;/code>-enabled fs, let&amp;rsquo;s turn our focus to the Linux kernel.&lt;/p>
&lt;h4 id="dcache_op_revalidate-flag-is-set">&lt;code>DCACHE_OP_REVALIDATE&lt;/code> flag is set
&lt;/h4>&lt;p>If a &lt;code>dentry&lt;/code> has &lt;code>d_revalidate&lt;/code> set to not &lt;code>NULL&lt;/code>, which is the case with ZFS, kernel will mark &lt;code>DCACHE_OP_REVALIDATE&lt;/code> in its &lt;code>d_flags&lt;/code>. A &lt;code>d_flags&lt;/code> is just flags to tell what operations that this &lt;code>dentry&lt;/code> supports, and &lt;code>DCACHE_OP_REVALIDATE&lt;/code> means it supports &lt;code>d_revalidate&lt;/code> operations.&lt;/p>
&lt;p>Now in our case, ZFS uses &lt;code>d_revalidate&lt;/code>, so our &lt;code>d_flags&lt;/code> have a &lt;code>DCACHE_OP_REVALIDATE&lt;/code> present.&lt;/p>
&lt;p>&lt;em>Keep this in mind.&lt;/em> This flag will cause &lt;code>overlayfs&lt;/code> to identify it as a remote fs. You will see why later.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/dcache.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/dcache.c#L1915-L1943
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="nf">d_set_d_op&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">dentry_operations&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">WARN_ON_ONCE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_op&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">WARN_ON_ONCE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">DCACHE_OP_HASH&lt;/span> &lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DCACHE_OP_COMPARE&lt;/span> &lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DCACHE_OP_REVALIDATE&lt;/span> &lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DCACHE_OP_WEAK_REVALIDATE&lt;/span> &lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DCACHE_OP_DELETE&lt;/span> &lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DCACHE_OP_REAL&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_op&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">op&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_hash&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_HASH&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_compare&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_COMPARE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_revalidate&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_REVALIDATE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_weak_revalidate&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_WEAK_REVALIDATE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_delete&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_DELETE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_prune&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_PRUNE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">op&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_real&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">DCACHE_OP_REAL&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">EXPORT_SYMBOL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d_set_d_op&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="mounting-process-of-overlayfs">Mounting process of &lt;code>overlayfs&lt;/code>
&lt;/h4>&lt;p>To understand how &lt;code>overlayfs&lt;/code> rejects &lt;code>d_revalidate&lt;/code> enabled fs, we need to look at the code that mounts &lt;code>overlayfs&lt;/code>.&lt;/p>
&lt;p>When we mount a &lt;code>overlayfs&lt;/code>, we call &lt;code>ovl_mount()&lt;/code> in the kernel &lt;code>fs/overlayfs&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/overlayfs/super.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/overlayfs/super.c#L2158-L2162
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nf">ovl_mount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">file_system_type&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">fs_type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dev_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">raw_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">mount_nodev&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fs_type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">raw_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ovl_fill_super&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>ovl_fill_super()&lt;/code> will be called to fill the dir (&lt;code>workdir&lt;/code>) where &lt;code>overlayfs&lt;/code> mounts, which will call &lt;code>ovl_get_workdir&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/overlayfs/super.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/overlayfs/super.c#L2047-L2079
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">ovl_fill_super&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">super_block&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">silent&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Omitted till L2048
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">upperdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">struct&lt;/span> &lt;span class="n">super_block&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">upper_sb&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">EINVAL&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">workdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">pr_err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;missing &amp;#39;workdir&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">goto&lt;/span> &lt;span class="n">out_err&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">ovl_get_upper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ofs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">layers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">upperpath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">goto&lt;/span> &lt;span class="n">out_err&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">upper_sb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">ovl_upper_mnt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">mnt_sb&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nf">ovl_should_sync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">errseq&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">errseq_sample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">upper_sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_wb_err&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">errseq_check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">upper_sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_wb_err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">errseq&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">EIO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">pr_err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Cannot mount volatile when upperdir has an unseen error. Sync upperdir fs to clear state.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">goto&lt;/span> &lt;span class="n">out_err&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">ovl_get_workdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ofs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">upperpath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">goto&lt;/span> &lt;span class="n">out_err&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">workdir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_flags&lt;/span> &lt;span class="o">|=&lt;/span> &lt;span class="n">SB_RDONLY&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_stack_depth&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">upper_sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_stack_depth&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_time_gran&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">upper_sb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">s_time_gran&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Saw something called &lt;code>lowerdir&lt;/code> and &lt;code>upperdir&lt;/code>? Here&amp;rsquo;s what they means in &lt;code>overlayfs&lt;/code>:&lt;/p>
&lt;blockquote>
&lt;p>An overlay filesystem combines two filesystems - an &amp;lsquo;upper&amp;rsquo; filesystem and a &amp;rsquo;lower&amp;rsquo; filesystem. When a name exists in both filesystems, the object in the &amp;lsquo;upper&amp;rsquo; filesystem is visible while the object in the &amp;rsquo;lower&amp;rsquo; filesystem is either hidden or, in the case of directories, merged with the &amp;lsquo;upper&amp;rsquo; object.&lt;/p>
&lt;p>It would be more correct to refer to an upper and lower &amp;lsquo;directory tree&amp;rsquo; rather than &amp;lsquo;filesystem&amp;rsquo; as it is quite possible for both directory trees to be in the same filesystem and there is no requirement that the root of a filesystem be given for either upper or lower.&lt;/p>
&lt;p>The lower filesystem can be any filesystem supported by Linux and does not need to be writable. The lower filesystem can even be another overlayfs. The upper filesystem will normally be writable and if it is it must support the creation of trusted.* extended attributes, and must provide valid d_type in readdir responses, so NFS is not suitable.&lt;/p>
&lt;p>A read-only overlay of two read-only filesystems may use any filesystem type.&lt;/p>
&lt;p>&lt;em>Excerpt from &lt;a class="link" href="https://docs.kernel.org/filesystems/overlayfs.html#upper-and-lower" target="_blank" rel="noopener"
>Overlay Filesystem — The Linux Kernel documentation&lt;/a>&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>And &lt;code>ovl_get_workdir&lt;/code> will get where the &lt;code>workdir&lt;/code> is and it call &lt;code>ovl_make_workdir&lt;/code> to make the &lt;code>workdir&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/overlayfs/super.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/overlayfs/super.c#L1513
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">ovl_get_workdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">super_block&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">ovl_fs&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">struct&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">upperpath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Omitted till L1513
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">ovl_make_workdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ofs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">workpath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="zfs-is-identified-as-a-remote-fs">ZFS is identified as a remote fs
&lt;/h4>&lt;p>Now the good bit comes, notice the comments. This where the &lt;code>overlayfs&lt;/code> rejects remote fs. (We will see why ZFS is identified as remote fs later.)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/overlayfs/super.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/overlayfs/super.c#L1434-L1444
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">ovl_make_workdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">super_block&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">ovl_fs&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">struct&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">workpath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Omitted till L1433
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="cm">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * We allowed sub-optimal upper fs configuration and don&amp;#39;t want to break
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * users over kernel upgrade, but we never allowed remote upper fs, so
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * we can enforce strict requirements for remote upper fs.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">ovl_dentry_remote&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">workdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">d_type&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="n">rename_whiteout&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">ofs&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">noxattr&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">pr_err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;upper fs missing required features.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">EINVAL&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">goto&lt;/span> &lt;span class="n">out&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In &lt;code>ovl_dentry_remote&lt;/code>, it directly marks &lt;code>dentry&lt;/code> which has &lt;code>DCACHE_OP_REVALIDATE&lt;/code> flags (Remember what we said before? ZFS sets this flag.) as remote, and thus the code above will go into the if-condition, then rejecting it.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// File: fs/overlayfs/super.c
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Link: https://github.com/torvalds/linux/blob/3bc1bc0b59d04e997db25b84babf459ca1cd80b7/fs/overlayfs/util.c#L97-L101
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">ovl_dentry_remote&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="n">dentry&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dentry&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">dentry&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">d_flags&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">DCACHE_OP_REVALIDATE&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">DCACHE_OP_WEAK_REVALIDATE&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now everything comes together. Ah, this is why having &lt;code>d_revalidate&lt;/code> set to not &lt;code>NULL&lt;/code> will lead to Linux treating ZFS as a &lt;em>remote&lt;/em> filesystem (like NFS) and &lt;strong>thus things like &lt;code>overlayfs&lt;/code> won&amp;rsquo;t work with ZFS&lt;/strong>.&lt;/p>
&lt;p>There is PRs in OpenZFS to fix this problem: &lt;a class="link" href="https://github.com/openzfs/zfs/pull/9600" target="_blank" rel="noopener"
>https://github.com/openzfs/zfs/pull/9600&lt;/a> , &lt;a class="link" href="https://github.com/openzfs/zfs/pull/9414" target="_blank" rel="noopener"
>https://github.com/openzfs/zfs/pull/9414&lt;/a> . But currently they are held and I don&amp;rsquo;t the expertise or time to work on it either. Hopefully wish I can pick up that PR and fix it (if possible).&lt;/p>
&lt;h2 id="final-solution">Final solution
&lt;/h2>&lt;p>So, the only option left is not possible now. Is there something we can do?&lt;/p>
&lt;p>As I said earlier, &amp;ldquo;Simply put, that&amp;rsquo;s not possible (&lt;em>&lt;strong>directly&lt;/strong>&lt;/em>)&amp;rdquo;. Well, it turns out, there is still an &lt;em>indirect&lt;/em> way &amp;ndash; &lt;strong>ZFS Volumes&lt;/strong>. Let&amp;rsquo;s see what Oracle says:&lt;/p>
&lt;blockquote>
&lt;p>A ZFS volume is a dataset that represents &lt;strong>a block device&lt;/strong>.&lt;/p>
&lt;p>&lt;em>Excerpt from: &lt;a class="link" href="https://docs.oracle.com/cd/E19253-01/819-5461/gaypf/index.html" target="_blank" rel="noopener"
>https://docs.oracle.com/cd/E19253-01/819-5461/gaypf/index.html&lt;/a>&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>Note that it is a &lt;em>block device&lt;/em>. This is really important, which means we can treat it like a conventional hard drive and do whatever we want on ZFS datasets!&lt;/p>
&lt;p>Since it is a block device, we can use it as a Swap device, iSCSI target, and in this case, a block device holding a &lt;code>ext4&lt;/code> partitation to put &lt;code>overlayfs&lt;/code> on.&lt;/p>
&lt;h2 id="solve-problem">Solve problem
&lt;/h2>&lt;p>Finally! We now decide to use ZFS Volumes (zvol) to hold our &lt;code>overlayfs&lt;/code> , i.e., &lt;code>overlayfs&lt;/code> on top of &lt;code>ext4&lt;/code> on top of &lt;code>zvol&lt;/code> on top of ZFS datasets. (Well, it is a bit complex. But trust me, even with so many fs layers, the performance is still wayyyy higher than Docker&amp;rsquo;s &lt;code>zfs&lt;/code> driver.)&lt;/p>
&lt;p>Let&amp;rsquo;s fix this now.&lt;/p>
&lt;p>&lt;strong>Stop Docker:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo systemctl stop docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Destroy the dataset that Docker uses previously.&lt;/strong> You can use &lt;code>zfs list&lt;/code> to find all datasets. In our case, it is &lt;code>rpool/ROOT/ubuntu_uzcb39/var/lib/docker&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># I mount this dataset at /var/lib/docker, which docker uses. Remove it.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo zfs destroy rpool/ROOT/ubuntu_uzcb39/var/lib/docker -R -r
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Be careful! This will destroy datasets recursively.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Create a ZFS Volume.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo zfs create -sV 64G rpool/ROOT/ubuntu_uzcb39/var/lib/docker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># rpool/ROOT/ubuntu_uzcb39/var/lib/docker is where the dataset is. Note that this will not be mounted to /var/lib/docker, which is different from the one above.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -V creates a zvol&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -s makes it sparse, i.e, dynamically expands instead of taking all defined space&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># set a max size of 64G&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Format the zvol&lt;/strong>. ZFS Volumes are identified as devices in the &lt;code>/dev/zvol/{dsk,rdsk}/pool&lt;/code> directory. Since we created a block device, let&amp;rsquo;s format it to &lt;code>ext4&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># The zvol we just created is mounted at /dev/zvol/rpool/ROOT/ubuntu_uzcb39/var/lib/docker.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkfs.ext4 /dev/zvol/rpool/ROOT/ubuntu_uzcb39/var/lib/docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Mount the &lt;code>ext4&lt;/code> partitation to &lt;code>/var/lib/docker&lt;/code>.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/docker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Mount ext4-formatted zvol (block device) to /var/lib/docker&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mount /dev/zvol/rpool/ROOT/ubuntu_uzcb39/var/lib/docker /var/lib/docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Check if it is successfully mounted.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">df -hT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /dev/zd0 ext4 63G 5.8G 54G 10% /var/lib/docker&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># It is mounted, great!&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Start Docker back up and check status.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo systemctl start docker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># We are using overlay2 now!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; Storage Driver: overlay2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; Backing Filesystem: extfs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; Supports d_type: true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; Native Overlay Diff: true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; userxattr: false&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Make changes persistent.&lt;/strong> Make sure the zvol is automatically mounted to &lt;code>/var/lib/docker&lt;/code> after system reboots.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo vim /etc/fstab
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Append this:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /dev/zvol/rpool/ROOT/ubuntu_uzcb39/var/lib/docker /var/lib/docker ext4 defaults 0 0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Horrey! Build time are several orders of magnitude faster now!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;a class="link" href="https://wiki.debian.org/ZFS" target="_blank" rel="noopener"
>ZFS - Debian Wiki&lt;/a>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a class="link" href="https://docs.docker.com/storage/storagedriver/#storage-drivers-versus-docker-volumes" target="_blank" rel="noopener"
>About storage drivers | Docker Documentation&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>&lt;a class="link" href="https://docs.docker.com/storage/storagedriver/select-storage-driver/#suitability-for-your-workload" target="_blank" rel="noopener"
>Docker storage drivers | Docker Documentation&lt;/a>&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&amp;#160;&lt;a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>&lt;a class="link" href="https://docs.docker.com/storage/storagedriver/zfs-driver/#image-layering-and-sharing" target="_blank" rel="noopener"
>Use the ZFS storage driver | Docker Documentation&lt;/a>&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item></channel></rss>