<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DIY on Zwindler's Reflection</title><link>https://blog.zwindler.fr/en/categories/diy/</link><description>Recent content in DIY on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Mon, 24 Nov 2025 18:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/en/categories/diy/index.xml" rel="self" type="application/rss+xml"/><item><title>OCI Artifacts as LXC Machine Base on Proxmox 9.1, Finally?</title><link>https://blog.zwindler.fr/en/2025/11/24/oci-artifacts-as-lxc-machine-base-on-proxmox-9.1-finally/</link><pubDate>Mon, 24 Nov 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/11/24/oci-artifacts-as-lxc-machine-base-on-proxmox-9.1-finally/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci2.webp" alt="Featured image of post OCI Artifacts as LXC Machine Base on Proxmox 9.1, Finally?" /&gt;&lt;h2 id="context"&gt;Context
&lt;/h2&gt;&lt;p&gt;A few days ago, &lt;a class="link" href="https://proxmox.com/en/about/company-details/press-releases/proxmox-virtual-environment-9-1" target="_blank" rel="noopener"
&gt;the Proxmox VE dev team announced a new version (9.1)&lt;/a&gt; whose major new feature is the addition of OCI artifacts support as a base for creating LXC containers.&lt;/p&gt;
&lt;p&gt;A long-awaited feature, since in theory it finally opens the door to launching &amp;ldquo;Docker&amp;rdquo;-type containers in Proxmox, something the Proxmox devs had always refused.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s also why one of my blog articles gets the most traffic on Google (sniff, soon the end of glory):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2022/11/07/containers-docker-dans-proxmox-avec-lxc/" target="_blank" rel="noopener"
&gt;This article where I explain how to launch Docker containers (via LXC) in Proxmox VE (normally not possible)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Create LXC containers from OCI images&lt;/p&gt;
&lt;p&gt;Proxmox VE 9.1 integrates support for Open Container Initiative (OCI) images, a standard format for container distribution. Users can now download widely-adopted OCI images directly from registries or upload them manually to use as templates for LXC containers. Depending on the image, these containers are provisioned as full system containers or lean application containers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="lets-test-first-the-prerequisites"&gt;Let&amp;rsquo;s Test? First the Prerequisites
&lt;/h2&gt;&lt;p&gt;So, I&amp;rsquo;m curious, how does it work?&lt;/p&gt;
&lt;p&gt;First, you need to pre-download the image you&amp;rsquo;re interested in to your storage (one that can host &amp;ldquo;CT Templates&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Fun thing, there&amp;rsquo;s a button called &amp;ldquo;Query tags&amp;rdquo;, which I imagine retrieves a list of tags. Well, it&amp;rsquo;s not starting well, it doesn&amp;rsquo;t work (at least not on docker.io&amp;hellip;).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;command &amp;#39;skopeo list-tags docker://docker.io/zwindler/prime-calculator&amp;#39; failed: exit code 1 (500)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;EDIT: since then, it works again, on all my nodes. Perhaps a temporary issue.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/qwen.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note: &lt;code&gt;skopeo&lt;/code&gt; is precisely one of the dependencies used in my &amp;ldquo;proxmox+docker hack&amp;rdquo; article that allows manipulating (mainly downloading) containers from remote registries.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="creating-the-lxc-container"&gt;Creating the LXC Container
&lt;/h2&gt;&lt;p&gt;Now that the template (the OCI image) is available on our storage, we can create an LXC container as usual. Click on &amp;ldquo;Create CT&amp;rdquo;, give a CT ID, a password:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;For the template, we choose our OCI artifact:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci6.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;And validate. Proxmox should create our container:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci7.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Even though the terminal tells us that application containers may not work well in the JS console, for all the containers I managed to start (see below), I always had a functional console.&lt;/p&gt;
&lt;p&gt;So the bet seems rather successful.&lt;/p&gt;
&lt;p&gt;Example with &lt;code&gt;docker.io/python:3.11.14-trixie&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/python.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="when-it-wasnt-working-yet"&gt;When It Wasn&amp;rsquo;t Working (Yet)
&lt;/h2&gt;&lt;p&gt;I had 2 issues on a Proxmox VE 9.1 (that had been upgraded and tinkered with a bit, in short, a machine that had seen some use) starting an LXC container on OCI base with Proxmox VE 9.1 because I ran into 2 bugs.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t reproduce these bugs on a &amp;ldquo;fresh&amp;rdquo; install.&lt;/p&gt;
&lt;h3 id="first-bug-from-scratch-image"&gt;First Bug: &amp;ldquo;from scratch&amp;rdquo; Image
&lt;/h3&gt;&lt;p&gt;First, my &lt;code&gt;prime-calculator&lt;/code&gt; image wasn&amp;rsquo;t even created.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;TASK ERROR: unable to create CT 106 - Error while extracting OCI image: Unknown layer digest sha256:2bc4d17e1eb16f6b52acbafeb6d86c8f3eaf9588a5e2a7a245b6bb1d85e93396 found in rootfs.diff_ids: Unknown layer digest sha256:2bc4d17e1eb16f6b52acbafeb6d86c8f3eaf9588a5e2a7a245b6bb1d85e93396 found in rootfs.diff_ids
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OK, fair enough. My image is a binary &amp;ldquo;from scratch&amp;rdquo;, with nothing but the binary, and maybe I made a mistake somewhere&amp;hellip;&lt;/p&gt;
&lt;p&gt;Still doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;h3 id="second-bug-unable-to-start"&gt;Second Bug: Unable to Start
&lt;/h3&gt;&lt;p&gt;However, even with known images, the container is properly created:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci8.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;But impossible to start it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;problem with monitor socket, but continuing anyway: got timeout
TASK ERROR: unable to get PID for CT 105 (not running?)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Same error with command line:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pct start 105 --debug
problem with monitor socket, but continuing anyway: got timeout
lxc_start_main: 257 Container is already running
- Read uid map: type u nsid 0 hostid 100000 range 65536
INFO confile - ../src/lxc/confile.c:set_config_idmaps:2295 - Read uid map: type g nsid 0 hostid 100000 range 65536
ERROR lxc_start - ../src/lxc/tools/lxc_start.c:lxc_start_main:257 - Container is already running
unable to get PID for CT 105 (not running?)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci9.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;After (laborious) investigation, it turns out it&amp;rsquo;s an AppArmor problem. I don&amp;rsquo;t know if I caused it with my tinkering or if it&amp;rsquo;s a more generalized bug for now (probably option 1, but 2 isn&amp;rsquo;t impossible either&amp;hellip;)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Nov 23 10:37:26 node01 systemd[1]: Started pve-container-debug@105.service - PVE LXC Container: 151.
Nov 23 10:37:27 node01 kernel: audit: type=1400 audit(1764067047.008:175): apparmor=&amp;#34;DENIED&amp;#34; operation=&amp;#34;create&amp;#34; class=&amp;#34;net&amp;#34;
Nov 23 10:37:27 node01 kernel: audit: type=1400 audit(1764067047.008:176): apparmor=&amp;#34;DENIED&amp;#34; operation=&amp;#34;create&amp;#34; class=&amp;#34;net&amp;#34;
Nov 23 10:37:27 node01 systemd[1]: pve-container-debug@105.service: Deactivated successfully.
Nov 23 10:37:37 node01 pct[21369]: unable to get PID for CT 105 (not running?)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The temporary (and dirty) workaround is to disable the AppArmor profile for &lt;code&gt;lxc-start&lt;/code&gt;, and remove any zombie files/processes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apparmor_parser -R /etc/apparmor.d/usr.bin.lxc-start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ps aux &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;lxc-start&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;105&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs -r &lt;span class="nb"&gt;kill&lt;/span&gt; -9
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -f /var/lib/lxc/105/config.lock
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -f /run/lxc/lock/var/lib/lxc/105
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: You can also disable it permanently, but that&amp;rsquo;s obviously something you shouldn&amp;rsquo;t do.&lt;/p&gt;
&lt;p&gt;From there, the container is accessible, sometimes in console (depends on the container)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/pve-oci10.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Apparently, the problems I encountered don&amp;rsquo;t reproduce on a &amp;ldquo;fresh install&amp;rdquo;. So it&amp;rsquo;s probably an issue related to my &amp;ldquo;old&amp;rdquo; machines.&lt;/p&gt;
&lt;p&gt;As a bonus, another article in English that made me persevere when I understood it worked for some:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://raymii.org/s/tutorials/Finally_run_Docker_containers_natively_in_Proxmox_9.1.html" target="_blank" rel="noopener"
&gt;Raymii.org blog - Finally, run Docker containers natively in Proxmox 9.1 (OCI images)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: this article helped me rediscover the &lt;code&gt;pct enter CTID&lt;/code&gt; command that allows you to log into the container.&lt;/p&gt;
&lt;p&gt;In the meantime, you still have my hack ;-)&lt;/p&gt;</description></item><item><title>Flash Your Xiaomi Mijia Thermometers to Extend Battery Life by a Year</title><link>https://blog.zwindler.fr/en/2025/11/15/flash-your-xiaomi-mijia-thermometers-to-extend-battery-life-by-a-year/</link><pubDate>Sat, 15 Nov 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/11/15/flash-your-xiaomi-mijia-thermometers-to-extend-battery-life-by-a-year/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/11/accueil-ha.webp" alt="Featured image of post Flash Your Xiaomi Mijia Thermometers to Extend Battery Life by a Year" /&gt;&lt;h2 id="2-thermometers-that-become-a-financial-drain"&gt;€2 Thermometers That Become a Financial Drain
&lt;/h2&gt;&lt;p&gt;From time to time I tell myself I should get into home automation. After an attempt 4 years ago (&lt;a class="link" href="https://blog.zwindler.fr/2021/10/04/jeedom-dans-un-container-lxc/" &gt;with Jeedom, I didn&amp;rsquo;t really get into it in the end&lt;/a&gt;), I got back into it with HomeAssistant a little over a year ago.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll detail the setup in another article (spoiler: it&amp;rsquo;s a VM on &lt;a class="link" href="https://blog.zwindler.fr/2024/05/19/mon-nas-en-2024-jonsbo-n100-part2/" &gt;my homemade 2024 NAS&lt;/a&gt;, with a Bluetooth card with a USB passthrough antenna). For now, know that I started simple with just thermometers in each room.&lt;/p&gt;
&lt;p&gt;Note: you can see that one room has run out of batteries, and that Home Assistant had been down for 10 days without me noticing haha.&lt;/p&gt;
&lt;p&gt;Like many people who want to graph just about everything, even their house (guilty as charged), I started by buying a pack of &lt;strong&gt;Xiaomi Mijia thermometer-hygrometers&lt;/strong&gt; (reference LYWSD03MMC for those in the know).&lt;/p&gt;
&lt;p&gt;The main advantage of these little connected thermometers is that they&amp;rsquo;re &lt;strong&gt;very inexpensive&lt;/strong&gt;: between €1 and €4 depending on sales on AliExpress. Mainly because these are Bluetooth thermometers, much more widespread (and therefore less costly) than Zigbee, Z-Wave equivalents, etc.&lt;/p&gt;
&lt;p&gt;You can very easily pair them with a smartphone (Xiaomi provides an app to make it easier) or integrate them into any home automation platform. But the smartphone app is very limited and requires being at very close range (of course).&lt;/p&gt;
&lt;p&gt;So, like any self-respecting home automation hobbyist, I connected them to my Home Assistant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And that&amp;rsquo;s when the drama started.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As soon as you activate Bluetooth to integrate them with Home Assistant, the CR2032 battery (standard button cell) lasts about 3 months. You very quickly spend more on batteries than on thermometers&amp;hellip;&lt;/p&gt;
&lt;h2 id="pvvx-to-the-rescue"&gt;PVVX to the Rescue
&lt;/h2&gt;&lt;p&gt;Fortunately, the open source community found the solution!&lt;/p&gt;
&lt;p&gt;There are two main projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;ATC&lt;/strong&gt; project by atc1441&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;PVVX&lt;/strong&gt; fork (which improves the ATC project)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By flashing your sensors&amp;rsquo; firmware with the PVVX version, you can &lt;strong&gt;go from a few weeks to over a year of battery life&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I can confirm this: it&amp;rsquo;s now been about a year (last August) since I migrated all my sensors and I &lt;strong&gt;haven&amp;rsquo;t changed a single battery yet&lt;/strong&gt;! It&amp;rsquo;s pretty crazy when you think about it.&lt;/p&gt;
&lt;p&gt;Non-negligible bonus: the new firmware uses the &lt;strong&gt;BTHome v2&lt;/strong&gt; protocol, which drastically simplifies integration with Home Assistant (no more fiddling with HACS and stuff; those who know, know).&lt;/p&gt;
&lt;h2 id="required-hardware"&gt;Required Hardware
&lt;/h2&gt;&lt;p&gt;What&amp;rsquo;s cool about this tutorial is that you don&amp;rsquo;t need much:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;smartphone&lt;/strong&gt; (Android or iOS) with Bluetooth&lt;/li&gt;
&lt;li&gt;Your &lt;strong&gt;Xiaomi thermometers&lt;/strong&gt; (LYWSD03MMC)&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Internet connection&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it! No computer, USB-UART cable, soldering iron, or specialized equipment needed. This is really &amp;ldquo;flashing for dummies&amp;rdquo; (myself included).&lt;/p&gt;
&lt;h2 id="flashing-procedure-simpler-than-it-seems"&gt;Flashing Procedure: Simpler Than It Seems
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Step 1: Open the Web Flasher&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Go to the PVVX flasher site with your smartphone:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html" target="_blank" rel="noopener"
&gt;https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The flasher works entirely in the browser (not great on Firefox from memory, but 100% OK on Chrome) thanks to the &lt;a class="link" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API" target="_blank" rel="noopener"
&gt;Web Bluetooth API&lt;/a&gt;. No app to install, everything happens in the browser. It&amp;rsquo;s quite magical when you think about it 🤔.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Connect the Sensor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Click on the &lt;strong&gt;&amp;ldquo;Connect&amp;rdquo;&lt;/strong&gt; button.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Possible confusion&lt;/strong&gt;: you might think you need to first select the sensor from a dropdown list or something, but no! It&amp;rsquo;s after clicking &amp;ldquo;Connect&amp;rdquo; that a popup opens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A Bluetooth selection window opens with the list of detected devices nearby.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/ATC1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Select your thermometer (it should be called &lt;strong&gt;LYWSD03MMC&lt;/strong&gt; or something similar with numbers) then click &lt;strong&gt;&amp;ldquo;Pair&amp;rdquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Wait for Connection&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once paired, the sensor information will display on the web page.&lt;/p&gt;
&lt;p&gt;From experience, it can take a few seconds, or even require a few attempts. Watch the logs at the bottom of the page to see progress. Bluetooth is finicky sometimes (understatement).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/ATC2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I didn&amp;rsquo;t really understand if I needed to click &amp;ldquo;Login&amp;rdquo; and/or &amp;ldquo;Activation&amp;rdquo;. When in doubt, I did both 🤡. It seems to work in all cases, so&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Flash the Firmware&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once connected, the list of available firmwares appears.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/ATC3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leave the default choice&lt;/strong&gt;: &lt;code&gt;ATC_v47.bin&lt;/code&gt; (or the most recent version displayed, at the time I&amp;rsquo;m writing this draft, it was version v47).&lt;/p&gt;
&lt;p&gt;Click on &lt;strong&gt;&amp;ldquo;Start Flashing&amp;rdquo;&lt;/strong&gt; and&amp;hellip; wait with fingers crossed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/ATC4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;For me, the flash took between &lt;strong&gt;45 and 65 seconds&lt;/strong&gt; depending on the sensors. Don&amp;rsquo;t touch anything during this time! Especially don&amp;rsquo;t leave the web page, otherwise you have to start all over.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5: Verification&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once the flash is complete, the device will temporarily disconnect (a few seconds) then &lt;strong&gt;reappear with a new name&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of &lt;code&gt;LYWSD03MMC&lt;/code&gt;, it will now be called &lt;strong&gt;&lt;code&gt;ATC_XXXXXX&lt;/code&gt;&lt;/strong&gt; (where XXXXXX is a unique identifier based on the MAC address).&lt;/p&gt;
&lt;h2 id="firmware-configuration"&gt;Firmware Configuration
&lt;/h2&gt;&lt;p&gt;Once the sensor is reconnected with its new firmware, you can &lt;strong&gt;modify advanced settings&lt;/strong&gt; if you wish.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/ATC5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;The default values are probably optimal in most cases and you can leave them as is. &lt;strong&gt;But&lt;/strong&gt; if you&amp;rsquo;re like me and you like to tinker, here&amp;rsquo;s what I personally modified.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Advertising Interval: Save Even More&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;reduced the frequency&lt;/strong&gt; at which the device does its &amp;ldquo;advertising&amp;rdquo; (Bluetooth data broadcast).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default value: ~2.5 seconds&lt;/li&gt;
&lt;li&gt;My value: ~7.5 seconds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Advertising Type: BTHome v2 (DON&amp;rsquo;T TOUCH)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Leave the &lt;strong&gt;Advertising Type&lt;/strong&gt; on &lt;strong&gt;BTHome v2&lt;/strong&gt; with the AdFlags box checked.&lt;/p&gt;
&lt;p&gt;This is the standard protocol promoted by Home Assistant that greatly simplifies communication. With BTHome v2, your sensors will be &lt;strong&gt;auto-discovered&lt;/strong&gt; in Home Assistant without additional configuration.&lt;/p&gt;
&lt;p&gt;This wasn&amp;rsquo;t why I flashed the sensors originally, but it&amp;rsquo;s really the killer feature!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beware of Advanced Bluetooth Parameters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t touch BT5+ and LE Long Range options unless you &lt;strong&gt;really&lt;/strong&gt; know what you&amp;rsquo;re doing.&lt;/p&gt;
&lt;p&gt;You risk making your sensor incompatible with your existing installation, and you&amp;rsquo;ll have to reflash in recovery mode&amp;hellip; Not fun.&lt;/p&gt;
&lt;h2 id="integration-with-home-assistant"&gt;Integration with Home Assistant
&lt;/h2&gt;&lt;p&gt;With the PVVX firmware and BTHome v2, integration into Home Assistant is &lt;strong&gt;trivial&lt;/strong&gt; (for once we can say that):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make sure you have the &lt;strong&gt;BTHome&lt;/strong&gt; integration enabled (it&amp;rsquo;s native since 2023)&lt;/li&gt;
&lt;li&gt;Enable Bluetooth on your Home Assistant server&lt;/li&gt;
&lt;li&gt;Your sensors will appear &lt;strong&gt;automatically&lt;/strong&gt; in detected devices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There you go, you can graph the temperature in your house, it&amp;rsquo;s cool, we&amp;rsquo;re happy :). As promised, I&amp;rsquo;ll also (someday) write an article on the more complete configuration on the HomeAssistant side (VM installation, USB passthrough, creating the nice &amp;ldquo;card&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/11/accueil-ha.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;In the meantime. Have fun&lt;/p&gt;</description></item><item><title>MicroStack evolves: HP EliteDesk and Lenovo ThinkCentre compatibility for your homelab</title><link>https://blog.zwindler.fr/en/2025/09/17/microstack-evolves-hp-elitedesk-and-lenovo-thinkcentre-compatibility-for-your-homelab/</link><pubDate>Wed, 17 Sep 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/09/17/microstack-evolves-hp-elitedesk-and-lenovo-thinkcentre-compatibility-for-your-homelab/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/09/lenovo.webp" alt="Featured image of post MicroStack evolves: HP EliteDesk and Lenovo ThinkCentre compatibility for your homelab" /&gt;&lt;h1 id="a-microstack-update-"&gt;A MicroStack update? 😍
&lt;/h1&gt;&lt;p&gt;Yes! A few months ago, I introduced you to &lt;a class="link" href="https://blog.zwindler.fr/en/2025/03/22/microstack-an-open-source-project-to-3d-print-a-modular-rack-for-my-dell-micro-homelab/" &gt;MicroStack, my 3D printed modular rack project for Dell Micro&lt;/a&gt;. The project had unexpected success on MakerWorld / Bluesky (and even a bit on Reddit) and I received several requests to extend compatibility&amp;hellip;&lt;/p&gt;
&lt;p&gt;And since I&amp;rsquo;m nice (and I like my 3D printing side projects), I decided to respond to community requests!&lt;/p&gt;
&lt;h2 id="promise-kept-hp-elitedesk-and-lenovo-thinkcentre"&gt;Promise kept: HP EliteDesk and Lenovo ThinkCentre!
&lt;/h2&gt;&lt;p&gt;In the original article, I mentioned that &lt;strong&gt;&amp;ldquo;a version compatible with Lenovo ThinkCentre Tiny was planned&amp;rdquo;&lt;/strong&gt;. Well&amp;hellip; it&amp;rsquo;s done! And I even made a bonus with &lt;strong&gt;HP EliteDesk/ProDesk&lt;/strong&gt; because&amp;hellip; why not?&lt;/p&gt;
&lt;p&gt;Now, the &lt;strong&gt;MicroStack&lt;/strong&gt; project officially supports &lt;strong&gt;three ranges&lt;/strong&gt; of mini-PCs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dell OptiPlex Micro&lt;/strong&gt; (all generations) - &lt;em&gt;the pioneer&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lenovo ThinkCentre Tiny&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HP EliteDesk/ProDesk&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-growing-ecosystem"&gt;A growing ecosystem
&lt;/h2&gt;&lt;p&gt;The concept remains the same: &lt;strong&gt;stackable&lt;/strong&gt; and &lt;strong&gt;modular&lt;/strong&gt; modules to neatly organize your homelab. But now, you can &lt;strong&gt;mix brands&lt;/strong&gt; in the same rack if you have a heterogeneous fleet (and we all know how it happens in homelabs&amp;hellip; &amp;ldquo;oh look, this used machine is at a good price!&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;The &lt;a class="link" href="https://makerworld.com/en/collections/5818204-microstack-rack" target="_blank" rel="noopener"
&gt;complete MicroStack collection&lt;/a&gt; now includes:&lt;/p&gt;
&lt;h3 id="pc-modules"&gt;PC Modules:
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236448-microstack-rack-modular-rack-for-dell-micro-pcs#profileId-1256174" target="_blank" rel="noopener"
&gt;&lt;strong&gt;Dell Micro Module&lt;/strong&gt;&lt;/a&gt; - &lt;em&gt;the original&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1317460-microstack-rack-a-modular-rack-system-for-lenovo-m#profileId-1352912" target="_blank" rel="noopener"
&gt;&lt;strong&gt;Lenovo ThinkCentre Module&lt;/strong&gt;&lt;/a&gt; - &lt;em&gt;the requested one&lt;/em&gt; (thanks to a loan from someone on LinkedIn! Thanks!)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1446203-microstack-rack-modular-rack-for-hp-elitedesk#profileId-1505986" target="_blank" rel="noopener"
&gt;&lt;strong&gt;HP EliteDesk/ProDesk Module&lt;/strong&gt;&lt;/a&gt; - &lt;em&gt;the bonus&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="common-modules"&gt;Common Modules:
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236457-microstack-rack-modular-rack-storage-module#profileId-1256190" target="_blank" rel="noopener"
&gt;&lt;strong&gt;Storage Module&lt;/strong&gt;&lt;/a&gt; - for switch, accessories, etc.&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236461-microstack-rack-modular-rack-top-hat#profileId-1256198" target="_blank" rel="noopener"
&gt;&lt;strong&gt;Hat&lt;/strong&gt;&lt;/a&gt; - clean rack finish&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these modules are &lt;strong&gt;inter-compatible&lt;/strong&gt; and use the same stacking system.&lt;/p&gt;
&lt;h2 id="still-the-same-best-practices"&gt;Still the same best practices
&lt;/h2&gt;&lt;p&gt;As with the original, I still recommend printing in &lt;strong&gt;PETG&lt;/strong&gt; (or even ASA or ABS?) rather than PLA for better heat resistance, even though these mini-PCs remain quite cool. The &lt;strong&gt;honeycomb&lt;/strong&gt; floor design ensures good ventilation while saving filament.&lt;/p&gt;
&lt;h2 id="the-feedback-multi-range-modeling"&gt;The feedback: multi-range modeling
&lt;/h2&gt;&lt;p&gt;Adapting the original design to new ranges was &lt;strong&gt;simpler&lt;/strong&gt; than expected, thanks to the solid foundation of the initial project. Once the concept was established with the Dell module, you &amp;ldquo;just&amp;rdquo; need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Take (precise!) measurements of the new models (still need to find them&amp;hellip;)&lt;/li&gt;
&lt;li&gt;Adapt the chassis dimensions&lt;/li&gt;
&lt;li&gt;Do one (or more) test prints and validate stacking (the stress, every time, of having burned 120g of plastic for nothing if you messed up)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each new module took me about an hour of modeling, much less than the initial 3x2h of the &lt;a class="link" href="https://blog.zwindler.fr/2025/03/22/microstack-un-projet-open-source-pour-imprimer-un-rack-dell-micro/" &gt;original project&lt;/a&gt;. Prototyping (testing incomplete versions to save plastic) took quite a bit of time, which I had forgotten to count in the previous article. Easily a few more hours.&lt;/p&gt;
&lt;h2 id="community-feedback"&gt;Community feedback
&lt;/h2&gt;&lt;p&gt;What makes me happiest is seeing that the project found its audience. The feedback on MakerWorld is generally enthusiastic and the evolution requests show that the concept really meets a need.&lt;/p&gt;
&lt;p&gt;Some users have already sent me photos of their mixed installations with different brands in the same rack. This is exactly the &lt;strong&gt;modular&lt;/strong&gt; spirit I wanted to bring to the project!&lt;/p&gt;
&lt;p&gt;I also had &lt;strong&gt;one&lt;/strong&gt; negative feedback, for which I can&amp;rsquo;t do much as it stands but which was interesting and allowed me to add a warning. The fact that the back is dedicated to the charger (PSU) blocks some ports, particularly for the Lenovo. The solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live with only Ethernet (for a lab, personally I think that&amp;rsquo;s fine)&lt;/li&gt;
&lt;li&gt;Move the power supplies to the &amp;ldquo;storage&amp;rdquo; module&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/09/limitations.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="and-now"&gt;And now?
&lt;/h2&gt;&lt;p&gt;With these three ranges covered, my MicroStack project becomes really &lt;strong&gt;versatile&lt;/strong&gt;. If you have suggestions for other mini-PCs to support, don&amp;rsquo;t hesitate! MinisForum, perhaps?&lt;/p&gt;
&lt;p&gt;If this project interests you, feel free to comment, share, provide feedback, &lt;strong&gt;or even &lt;em&gt;boost&lt;/em&gt; the three models&lt;/strong&gt; for those who have a MakerWorld account (it&amp;rsquo;s free, but it&amp;rsquo;s appreciated :-P).&lt;/p&gt;</description></item><item><title>AppImages on Ubuntu, Example with Bambu Studio</title><link>https://blog.zwindler.fr/en/2025/05/04/appimages-on-ubuntu-example-with-bambu-studio/</link><pubDate>Sun, 04 May 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/05/04/appimages-on-ubuntu-example-with-bambu-studio/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/05/desktop.webp" alt="Featured image of post AppImages on Ubuntu, Example with Bambu Studio" /&gt;&lt;h2 id="bambu-studio-and-3d-printing-on-linux"&gt;Bambu Studio and 3D Printing on Linux
&lt;/h2&gt;&lt;p&gt;For a few months now, as you know, I&amp;rsquo;ve gotten into 3D printing. And while at the beginning, I started with Klipper (for the 3D printer) and &lt;a class="link" href="https://slic3r.org/" target="_blank" rel="noopener"
&gt;Slic3r&lt;/a&gt; (or rather its fork, Artillery Slicer) for slicing, since January I&amp;rsquo;ve switched to a Bambu Labs printer that I&amp;rsquo;m very happy with (that comment should upset LinuxFR).&lt;/p&gt;
&lt;p&gt;Of course, when I bought the printer, I checked if there was a Linux-compatible slicer, because Windows is fine for gaming (even then) but if I can skip it for 3D printing, that&amp;rsquo;s good too.&lt;/p&gt;
&lt;h2 id="yes-but"&gt;Yes, but&amp;hellip;
&lt;/h2&gt;&lt;p&gt;So yes, it&amp;rsquo;s &amp;ldquo;supported&amp;rdquo; (note the quotes) but it&amp;rsquo;s not as straightforward as for Windows and MacOS where you just click a button.&lt;/p&gt;
&lt;p&gt;A quick visit to the tool&amp;rsquo;s download site will reveal that we Linux users are indeed second-class nerds for BambuLab.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/05/t%c3%a9l%c3%a9chargement.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;You can see in tiny text that for Linux, we&amp;rsquo;ll have to settle for Github releases.&lt;/p&gt;
&lt;p&gt;Well, the idea of not bothering and using Wine emulation crossed my mind, but I figured I&amp;rsquo;d try the official method.&lt;/p&gt;
&lt;h2 id="appimage"&gt;AppImage
&lt;/h2&gt;&lt;p&gt;There are two options. The first, via AppImages, is the one that&amp;rsquo;s highlighted but probably not the most &amp;ldquo;practical&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Actually, I discovered a bit later that there&amp;rsquo;s a flatpak available through the flathub repository. The only &amp;ldquo;downside&amp;rdquo; is that it requires yet another package manager, but it&amp;rsquo;s not impossible that you already have it&amp;hellip;&lt;/p&gt;
&lt;p&gt;So, I&amp;rsquo;ll stick in this article with the method I used, namely AppImages, because it will allow us to play with Ubuntu&amp;rsquo;s .desktop files and that&amp;rsquo;s 2 tutorials in one (and that&amp;rsquo;s fun).&lt;/p&gt;
&lt;h2 id="download-and-launch"&gt;Download and Launch
&lt;/h2&gt;&lt;p&gt;This part is the simplest. So we simply go to BambuStudio&amp;rsquo;s github releases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/bambulab/BambuStudio/releases" target="_blank" rel="noopener"
&gt;github.com/bambulab/BambuStudio/releases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We take the latest one and download it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/05/releases.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note that AppImages are only available for Ubuntu 22.04 and 24.04 (and Fedora). A few versions earlier, it was 20.04 and 24.04. Since the AppImage is linked to libs (gtk) present on the OS, if you launch the Ubuntu image with the wrong version, it won&amp;rsquo;t find the lib version and it&amp;rsquo;s very annoying.&lt;/p&gt;
&lt;p&gt;You can cheat by &lt;em&gt;linking&lt;/em&gt; the versions so it finds the lib but it&amp;rsquo;s probably quite risky as a workaround&amp;hellip;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo ln -sf /usr/lib/x86_64-linux-gnu/libwebkit2gtk-4.1.so.0 /usr/lib/x86_64-linux-gnu/libwebkit2gtk-4.0.so.37
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once the archive is downloaded and extracted, you&amp;rsquo;ll end up with a folder containing the AppImage directly.&lt;/p&gt;
&lt;p&gt;If you double-click on it, it should work, Bambu Studio will launch.&lt;/p&gt;
&lt;h2 id="victory"&gt;Victory?
&lt;/h2&gt;&lt;p&gt;Yes OK, it works, but it&amp;rsquo;s still not very practical to go to the Downloads folder and click on the AppImage.&lt;/p&gt;
&lt;p&gt;You can obviously move the AppImage to a folder in your PATH (&lt;code&gt;/usr/local/bin&lt;/code&gt;) and call it via a terminal, but that&amp;rsquo;s not great either in terms of user experience (I&amp;rsquo;m not the only one doing 3D printing at home).&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;.desktop&lt;/code&gt; files. This is a text file that will allow us to add the appImage to Ubuntu&amp;rsquo;s application list and that&amp;rsquo;s nice because it&amp;rsquo;s user friendly.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll move the AppImage to a folder in PATH, and create a symbolic link:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/zwindler/.local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv Téléchargements/BambuStudio_ubuntu-24.04_PR-6688/Bambu_Studio_ubuntu-24.04_PR-6688.AppImage .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ln -s Bambu_Studio_ubuntu-24.04_PR-6688.AppImage bambustudio
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Why am I creating a symbolic link? 2 reasons.&lt;/p&gt;
&lt;p&gt;The first is that if I still want to launch Bambu Studio from my terminal (in case of issues at launch, I want to be able to see the logs), I can do it by simply running the command &lt;code&gt;bambustudio&lt;/code&gt; and not &lt;code&gt;Bambu_Studio_ubuntu-24.04_PR-6688.AppImage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second is that at the next update, I&amp;rsquo;ll &amp;ldquo;just&amp;rdquo; have to modify the symbolic link to point to the new appImage rather than having to redo all the steps that follow.&lt;/p&gt;
&lt;p&gt;By default, all applications in the list are in your &lt;code&gt;.local/share/applications/&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zwindler@normandy:~$ ls .local/share/applications/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;appimagekit-openshot-qt.desktop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bambu_Studio.desktop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chrome-hmjkmjkepdijhoojdojkdfohbdgmmhki-Default.desktop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mimeapps.list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mimeinfo.cache
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;userapp-Firefox-V48P02.desktop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what our &lt;code&gt;Bambu_Studio.desktop&lt;/code&gt; file should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Desktop&lt;/span&gt; &lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Bambu&lt;/span&gt; &lt;span class="nx"&gt;Studio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;GenericName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Bambu&lt;/span&gt; &lt;span class="nx"&gt;Studio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Bambu&lt;/span&gt; &lt;span class="nx"&gt;Studio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;home&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zwindler&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bin&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bambustudio&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;home&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zwindler&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bambustudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;png&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Terminal&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Application&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing too extravagant here, we mainly give the application name, a Type, and an execution path (and some additional parameters).&lt;/p&gt;
&lt;p&gt;The most observant among you will have noticed that you can also add an icon, it&amp;rsquo;s true that it&amp;rsquo;s much nicer to add the Bambu Lab logo than the default icon&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/05/icon1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;I got the Bambu Lab logo from their Github and placed it in the &lt;code&gt;/home/zwindler/.local/share/icons/&lt;/code&gt; folder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://avatars.githubusercontent.com/u/96461528?s=200&amp;amp;v=4" target="_blank" rel="noopener"
&gt;avatars.githubusercontent.com/u/96461528?s=200&amp;amp;v=4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And here&amp;rsquo;s the result :)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/05/desktop.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="addendum"&gt;Addendum
&lt;/h2&gt;&lt;p&gt;If you REALLY want to do AppImages, there&amp;rsquo;s a github project with 6k stars, &lt;strong&gt;AppImageLauncher&lt;/strong&gt;, that helps manage AppImages&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/TheAssassin/AppImageLauncher" target="_blank" rel="noopener"
&gt;https://github.com/TheAssassin/AppImageLauncher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;makes your Linux desktop AppImage ready™&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Basically it&amp;rsquo;s a launcher for AppImages, which gives you the choice between installing AppImages for you or running them oneshot, deleting and updating images, and a CLI.&lt;/p&gt;
&lt;p&gt;Thanks Fabio for the info.&lt;/p&gt;</description></item><item><title>MicroStack - an open source project to 3D print a modular rack for my Dell Micro homelab</title><link>https://blog.zwindler.fr/en/2025/03/22/microstack-an-open-source-project-to-3d-print-a-modular-rack-for-my-dell-micro-homelab/</link><pubDate>Sat, 22 Mar 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/03/22/microstack-an-open-source-project-to-3d-print-a-modular-rack-for-my-dell-micro-homelab/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/03/microstack.webp" alt="Featured image of post MicroStack - an open source project to 3D print a modular rack for my Dell Micro homelab" /&gt;&lt;h1 id="again-another-evolution-of-your-lab"&gt;AGAIN? Another evolution of your lab???
&lt;/h1&gt;&lt;p&gt;If you&amp;rsquo;ve been following the lore of my blog a bit, you know I have as many versions of my homelab as years of experience XD.&lt;/p&gt;
&lt;p&gt;2025 will be no exception. In early 2024, I embarked on the &lt;a class="link" href="https://blog.zwindler.fr/2024/02/15/microrack-v1-pour-dell-micro/" &gt;construction of a rack based on aluminum profiles and 3D printing (in french)&lt;/a&gt;, based on a project sent by a friend on Whatsapp!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2024/02/microrack.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;But in use, I found it massive, not very practical, not very pretty&amp;hellip; in short, not adapted to my needs.&lt;/p&gt;
&lt;p&gt;Except that (I&amp;rsquo;ve already mentioned it) I&amp;rsquo;ve since gotten into 3D printing and a bit (by force of circumstance) into 3D modeling (they often go hand in hand). I said to myself: why not create my own project that fits my needs?&lt;/p&gt;
&lt;p&gt;So I wanted to explore a homemade solution for my homelab, 3D printed, that would be both modular and stackable. The idea was to offer an adaptable system according to needs, with a simple and effective design.&lt;/p&gt;
&lt;h2 id="a-design-designed-for-organization"&gt;A design designed for organization
&lt;/h2&gt;&lt;p&gt;The &lt;strong&gt;MicroStack Rack&lt;/strong&gt; is designed to neatly store several &lt;strong&gt;Dell OptiPlex Micro&lt;/strong&gt; while maximizing space. It consists of &lt;strong&gt;three different modules&lt;/strong&gt; for now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/fr/models/1236448-microstack-rack-modular-rack-for-dell-micro-pcs#profileId-1256174" target="_blank" rel="noopener"
&gt;&lt;strong&gt;The PC module&lt;/strong&gt;&lt;/a&gt;, which accommodates a &lt;strong&gt;Dell Micro&lt;/strong&gt; (all generations) and its &lt;strong&gt;external power supply&lt;/strong&gt; at the back for more practicality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/dellmodule.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/fr/models/1236457-microstack-rack-modular-rack-storage-module#profileId-1256190" target="_blank" rel="noopener"
&gt;&lt;strong&gt;The storage module&lt;/strong&gt;&lt;/a&gt;, an empty compartment for &lt;strong&gt;accessories, network switch or other equipment&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/storagemodule.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/fr/models/1236461-microstack-rack-modular-rack-top-hat#profileId-1256198" target="_blank" rel="noopener"
&gt;&lt;strong&gt;The hat&lt;/strong&gt;&lt;/a&gt;, an optional element that covers the rack for a cleaner rendering.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/hatmodule.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;All modules are &lt;strong&gt;stackable&lt;/strong&gt;, allowing you to adapt the configuration according to your number of machines.&lt;/p&gt;
&lt;p&gt;Even though I printed the prototype in PLA, I recommend using &lt;strong&gt;PETG&lt;/strong&gt; instead, which resists heat better than PLA. However, these machines remain rather cool at idle and the &lt;strong&gt;honeycomb floor&lt;/strong&gt; improves ventilation (and reduces material consumption). Maybe it works too.&lt;/p&gt;
&lt;h2 id="how-long-does-it-take-to-do-this-kind-of-project"&gt;How long does it take to do this kind of project?
&lt;/h2&gt;&lt;p&gt;A quick note on time spent.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t done much 3D design in the past (I made a few maps on Unreal Tournament 2004&amp;hellip; oh my&amp;hellip;, that was 20 years ago!!!). So, I thought it might be interesting to share how long it took me.&lt;/p&gt;
&lt;p&gt;The modeling was done in &lt;strong&gt;three sessions of 2 hours&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2h&lt;/strong&gt; to take measurements, make an initial prototype and get familiar with Blender.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2h&lt;/strong&gt; to create the module dedicated to the &lt;strong&gt;Dell Micro&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2h&lt;/strong&gt; to add the &lt;strong&gt;storage module&lt;/strong&gt; and the &lt;strong&gt;hat&lt;/strong&gt; (it goes faster, I&amp;rsquo;ve already done part of the work and I&amp;rsquo;m more efficient in my use of Blender).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can add an hour or two for handling the 3D printer (launching the print, checking it doesn&amp;rsquo;t fail a few minutes in, cleaning the plate after printing, etc.) and 3-4 hours for the &amp;ldquo;social&amp;rdquo; part (photos, upload to makerworld, writing descriptions and blog post, sharing on social networks).&lt;/p&gt;
&lt;p&gt;A nice little side project of about ten hours then.&lt;/p&gt;
&lt;h2 id="files-and-evolutions"&gt;Files and evolutions
&lt;/h2&gt;&lt;p&gt;As promised on Bluesky where I teased the project, the files were created under &lt;strong&gt;Creative Commons 4.0 BY-SA&lt;/strong&gt; license (STL formats and Blender sources), but this is incompatible with &lt;strong&gt;MakerWorld&lt;/strong&gt; exclusive policy (obviously) so the 3MF files are in Standard Digital File Licence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236448-microstack-rack-modular-rack-for-dell-micro-pcs#profileId-1256174" target="_blank" rel="noopener"
&gt;MicroStack Rack – Dell Micro Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236457-microstack-rack-modular-rack-storage-module#profileId-1256190" target="_blank" rel="noopener"
&gt;MicroStack Rack – Storage Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://makerworld.com/en/models/1236461-microstack-rack-modular-rack-top-hat#profileId-1256198" target="_blank" rel="noopener"
&gt;MicroStack Rack – Hat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;strong&gt;version compatible with Lenovo ThinkCentre Tiny&lt;/strong&gt; is planned for the future (I was asked on Bluesky).&lt;/p&gt;
&lt;p&gt;If this project interests you, feel free to comment, share, provide feedback, or even &lt;em&gt;boost&lt;/em&gt; the three models for those who have a MakerWorld account (it&amp;rsquo;s free, but it&amp;rsquo;s appreciated :-P).&lt;/p&gt;</description></item><item><title>Sidero Omni - Talos Linux on Oracle Cloud (free tier)</title><link>https://blog.zwindler.fr/en/2025/01/04/sidero-omni-talos-linux-on-oracle-cloud-free-tier/</link><pubDate>Sat, 04 Jan 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2025/01/04/sidero-omni-talos-linux-on-oracle-cloud-free-tier/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/01/omni-oracle.webp" alt="Featured image of post Sidero Omni - Talos Linux on Oracle Cloud (free tier)" /&gt;&lt;p&gt;Fun fact, on January 4th, 2024 (exactly one year ago), my first article of the year was about &lt;a class="link" href="https://blog.zwindler.fr/2024/01/04/un-peu-plus-loin-avec-maas-canonical" &gt;Maas&lt;/a&gt;. Starting 2025, on the same day, with another article about a solution that aims to automate infrastructure, especially bare metal, is funny :)&lt;/p&gt;
&lt;h2 id="context"&gt;Context
&lt;/h2&gt;&lt;p&gt;I won!!!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/sidero-win.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;I occasionally play games on social media (less often now) and I also sometimes enter contests at conferences.&lt;/p&gt;
&lt;p&gt;And there, I won a one-year subscription to Sidero Labs&amp;rsquo; SaaS (the creators of Talos Linux), &lt;strong&gt;Omni&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For those wondering, Omni is a Kubernetes cluster deployment solution that allows you to orchestrate servers running Talos, a minimalist and secure Linux operating system dedicated to Kubernetes. As for Talos, we&amp;rsquo;ll certainly talk about it in future articles.&lt;/p&gt;
&lt;h2 id="what-does-this-have-to-do-with-oracle-cloud"&gt;What Does This Have to Do with Oracle Cloud?
&lt;/h2&gt;&lt;p&gt;Winning is great, it&amp;rsquo;s always nice. But what do I do with this SaaS now?&lt;/p&gt;
&lt;p&gt;The first idea that comes to mind is to order a dedicated server somewhere and install Talos on it (with an ISO generated by Omni), or do the same thing but install a hypervisor, then create Talos Linux VMs (again, thanks to Omni).&lt;/p&gt;
&lt;p&gt;But I&amp;rsquo;m &lt;del&gt;cheap&lt;/del&gt; eternally frugal and I saw that Oracle Cloud Infrastructure (OCI) is supported by Talos Linux and Omni, so I wanted to try it.&lt;/p&gt;
&lt;p&gt;For those who don&amp;rsquo;t know OCI, I wrote a series of articles in 2023 explaining how to create a free cluster with &lt;code&gt;kubeadm&lt;/code&gt; on the (quite generous) free tier:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2023/04/24/cluster-kubernetes-gratuit-part1/" &gt;A free Kubernetes cluster for your personal labs! - part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2023/05/23/cluster-kubernetes-gratuit-part2/" &gt;A free Kubernetes cluster for your personal labs! - part 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;rsquo;re going to reuse the free tier here, but to deploy and manage clusters with Omni.&lt;/p&gt;
&lt;p&gt;Note: you can reread them, but I&amp;rsquo;ve done better since then because I managed to integrate free tier machines INTO a managed Kubernetes cluster, which is much nicer to use. Stay tuned for the upcoming article ;).&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;For Talos Linux on Oracle Cloud Infrastructure (OCI) and more specifically integrating machines with Omni, there are a few necessary configuration steps.&lt;/p&gt;
&lt;p&gt;Note: this tutorial is partially based on &lt;a class="link" href="https://www.talos.dev/v1.9/talos-guides/install/cloud-platforms/oracle/" target="_blank" rel="noopener"
&gt;the official Talos Linux documentation&lt;/a&gt;, adapted for Omni and with additional details on Oracle Cloud Infrastructure.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m assuming you already have an account on Oracle Cloud Infrastructure, as well as the &lt;code&gt;talosctl&lt;/code&gt; binaries and the &lt;code&gt;oci&lt;/code&gt; CLI. Otherwise, a quick:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew install oci-cli talosctl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Authenticate with the CLI tool:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;oci session authenticate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When authentication is requested, choose the region corresponding to your location (for example, eu-paris-1). Once done, you&amp;rsquo;ll see a confirmation message in the browser and you&amp;rsquo;ll need to give a cute name to the profile for &lt;code&gt;oci&lt;/code&gt; in the terminal and validate.&lt;/p&gt;
&lt;h2 id="retrieving-the-compartment-id"&gt;Retrieving the Compartment ID
&lt;/h2&gt;&lt;p&gt;One of the interesting concepts of Oracle Cloud Infrastructure (found in other cloud providers in one form or another) is the notion of &amp;ldquo;compartment&amp;rdquo;. Basically, resources are stored in &amp;ldquo;compartments&amp;rdquo; to organize resources.&lt;/p&gt;
&lt;p&gt;You can work in the &amp;ldquo;root&amp;rdquo; compartment, but it&amp;rsquo;s cleaner to create a specific one for the occasion.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/compartments.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Example OCID:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ocid1.compartment.oc1..aaaaaaaayrmiilkb5m2b7never345435gonna345give87978you12upu6ifoh6csihm4awsjq
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="preparing-the-oracle-cloud-environment"&gt;Preparing the Oracle Cloud Environment
&lt;/h2&gt;&lt;p&gt;Once we have that, we&amp;rsquo;ll use the CLI to create all the things we&amp;rsquo;ll need for our virtual machines to register on Omni&amp;rsquo;s SaaS.&lt;/p&gt;
&lt;p&gt;Creating a VCN (Virtual Cloud Network) and network configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;cidr_block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.0.0/16
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;subnet_block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.0.0/24
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;compartment_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_ocid_compartment&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create the VCN and subnet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;vcn_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;oci network vcn create --cidr-block &lt;span class="nv"&gt;$cidr_block&lt;/span&gt; --display-name talos-example --compartment-id &lt;span class="nv"&gt;$compartment_id&lt;/span&gt; --query data.id --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;rt_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;oci network subnet create --cidr-block &lt;span class="nv"&gt;$subnet_block&lt;/span&gt; --display-name kubernetes --compartment-id &lt;span class="nv"&gt;$compartment_id&lt;/span&gt; --vcn-id &lt;span class="nv"&gt;$vcn_id&lt;/span&gt; --query data.route-table-id --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After the 2nd command, we retrieve not the OCID of the subnet (we don&amp;rsquo;t need it), but that of the &amp;ldquo;Route Table&amp;rdquo;, a subcomponent of the subnet, which allows defining the routing table of the subnet we just created.&lt;/p&gt;
&lt;p&gt;Configure a gateway (Internet Gateway):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ig_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;oci network internet-gateway create --compartment-id &lt;span class="nv"&gt;$compartment_id&lt;/span&gt; --is-enabled &lt;span class="nb"&gt;true&lt;/span&gt; --vcn-id &lt;span class="nv"&gt;$vcn_id&lt;/span&gt; --query data.id --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add a routing rule (to our route table) to allow Internet access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;oci network route-table update --rt-id &lt;span class="nv"&gt;$rt_id&lt;/span&gt; --route-rules &lt;span class="s2"&gt;&amp;#34;[{\&amp;#34;cidrBlock\&amp;#34;:\&amp;#34;0.0.0.0/0\&amp;#34;,\&amp;#34;networkEntityId\&amp;#34;:\&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ig_id&lt;/span&gt;&lt;span class="s2"&gt;\&amp;#34;}]&amp;#34;&lt;/span&gt; --force
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Disable default firewall rules (YOLO):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;sl_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;oci network vcn list --compartment-id &lt;span class="nv"&gt;$compartment_id&lt;/span&gt; --query &lt;span class="s1"&gt;&amp;#39;data[0].&amp;#34;default-security-list-id&amp;#34;&amp;#39;&lt;/span&gt; --raw-output&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From there, we have the functional network part, ready to host our machines!&lt;/p&gt;
&lt;h2 id="but-its-not-over-yet--"&gt;But It&amp;rsquo;s Not Over Yet :-/
&lt;/h2&gt;&lt;p&gt;With Oracle Cloud Infrastructure, it&amp;rsquo;s possible to boot a list of predefined OSes, but in our case, Talos Linux is not on the list.&lt;/p&gt;
&lt;p&gt;Fortunately, Omni will generate a preconfigured virtual disk with our credentials, which will allow us to enroll Talos Linux servers to our Omni SaaS from the first boot. Classy 😎.&lt;/p&gt;
&lt;p&gt;So we connect to our Omni interface and download the appropriate Talos Linux image:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/omni_iso_generation.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Here, I chose version 1.9.1 (the very latest because we like danger), for amd64 architecture. I retrieve the image &lt;code&gt;oracle-amd64-omni-zwindler-v1.9.1.qcow2.xz&lt;/code&gt; which I decompress:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;xz --decompress ./oracle-amd64-omni-zwindler-v1.9.1.qcow2.xz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reminder: OCI&amp;rsquo;s free tier consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2 &lt;strong&gt;amd64&lt;/strong&gt; machines with 1 oCPU (= vCPU) and 1 GB of RAM each&lt;/li&gt;
&lt;li&gt;a &lt;em&gt;flexible&lt;/em&gt; combination of &lt;strong&gt;arm64&lt;/strong&gt; instances having 1 x &lt;strong&gt;n&lt;/strong&gt; oCPU and 6 x &lt;strong&gt;n&lt;/strong&gt; GB, as long as the total doesn&amp;rsquo;t exceed 4 oCPU (and therefore 6x4 = 24 GB of RAM).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On OCI, there are several formats for importing disks. The simplest is to use OCI&amp;rsquo;s &amp;ldquo;homemade&amp;rdquo; format (the .oci, super original name), which consists of a metadata file and the QCOW2.&lt;/p&gt;
&lt;p&gt;Create the metadata file for import, with the &lt;strong&gt;VM.Standard.E2.1.Micro&lt;/strong&gt; shape since we have an amd64 image (otherwise it&amp;rsquo;s VM.Standard.A1.Flex):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; image_metadata.json &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;version&amp;#34;: 2,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;externalLaunchOptions&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;firmware&amp;#34;: &amp;#34;UEFI_64&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;networkType&amp;#34;: &amp;#34;PARAVIRTUALIZED&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;bootVolumeType&amp;#34;: &amp;#34;PARAVIRTUALIZED&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;remoteDataVolumeType&amp;#34;: &amp;#34;PARAVIRTUALIZED&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;localDataVolumeType&amp;#34;: &amp;#34;PARAVIRTUALIZED&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;launchOptionsSource&amp;#34;: &amp;#34;PARAVIRTUALIZED&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;pvAttachmentVersion&amp;#34;: 2,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;pvEncryptionInTransitEnabled&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;consistentVolumeNamingEnabled&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;imageCapabilityData&amp;#34;: null,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;imageCapsFormatVersion&amp;#34;: null,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;operatingSystem&amp;#34;: &amp;#34;Talos&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;operatingSystemVersion&amp;#34;: &amp;#34;1.9.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;additionalMetadata&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;shapeCompatibilities&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;internalShapeName&amp;#34;: &amp;#34;VM.Standard.E2.1.Micro&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;ocpuConstraints&amp;#34;: null,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;memoryConstraints&amp;#34;: null
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then create the image for import:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-img convert -f raw -O qcow2 oracle-amd64-omni-zwindler-v1.9.1.raw oracle-amd64-omni-zwindler-v1.9.1.qcow2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar zcf oracle-amd64-omni-zwindler-v1.9.1.oci oracle-amd64-omni-zwindler-v1.9.1.qcow2 image_metadata.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="importing-the-image-to-oracle-cloud"&gt;Importing the Image to Oracle Cloud
&lt;/h2&gt;&lt;p&gt;We have the file ready to be uploaded&amp;hellip; but where do we upload it??&lt;/p&gt;
&lt;p&gt;Unfortunately, this step is done in 2 parts. First, you need to upload the .oci file to a bucket. It&amp;rsquo;s quite simple to do in the UI (we could also do it via CLI):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a bucket in the Storage section to upload the compressed image.&lt;/li&gt;
&lt;li&gt;Upload the image (oracle-amd64-omni-zwindler-v1.9.1.oci) to this bucket.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/bucket.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Second step, we transform the file into &amp;ldquo;Custom images&amp;rdquo; (in the &lt;strong&gt;Compute&lt;/strong&gt; section):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/custom-images.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;This step is oddly quite long, the image which is only 100 MB took 10 minutes each time I tried so far.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/custom-images-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="creating-the-vm"&gt;Creating the VM
&lt;/h2&gt;&lt;p&gt;Once the image is imported, we can now use our Omni-customized image to boot our free tier machines.&lt;/p&gt;
&lt;p&gt;I also did this through the OCI interface, but again, we could do it via CLI.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/vm1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/vm2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A few seconds after boot, it appears in Omni&amp;rsquo;s UI and can be added to a cluster.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/01/omni-oracle.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Have fun!!&lt;/p&gt;
&lt;h2 id="addendum-is-it-really-free"&gt;Addendum: Is It Really Free?
&lt;/h2&gt;&lt;p&gt;Yes&amp;hellip;-ish.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In theory&lt;/strong&gt;, the machines are free (forever free as they say). But beware, lots of little things can be paid.&lt;/p&gt;
&lt;p&gt;For example, the bucket where we uploaded the QCOW2/.oci is most likely paid. Same for the custom image, which is billed as 1 GB of storage (according to the UI, I don&amp;rsquo;t have the bill yet LOL).&lt;/p&gt;
&lt;p&gt;If you put the VMs behind a load balancer (there&amp;rsquo;s no reason you&amp;rsquo;d do that here), be careful, only the &amp;ldquo;non-flexible&amp;rdquo; LB capped at 10 Mbps is free (and only one).&lt;/p&gt;
&lt;p&gt;And if like me, you already have 4 x 50 GB disks, the OS storage is paid (€1.85 / month).&lt;/p&gt;
&lt;p&gt;In short, as you&amp;rsquo;ve understood, it&amp;rsquo;s like often in the cloud, nothing is really free and you have to read all the fine print.&lt;/p&gt;</description></item><item><title>Migrating Cilium routing from iptables to eBPF... hot!</title><link>https://blog.zwindler.fr/en/2023/10/20/migrating-cilium-routing-from-iptables-to-ebpf...-hot/</link><pubDate>Fri, 20 Oct 2023 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/en/2023/10/20/migrating-cilium-routing-from-iptables-to-ebpf...-hot/</guid><description>&lt;img src="https://blog.zwindler.fr/2023/10/cilium-logo.webp" alt="Featured image of post Migrating Cilium routing from iptables to eBPF... hot!" /&gt;&lt;h2 id="context"&gt;Context
&lt;/h2&gt;&lt;p&gt;It could be that I configured production Kubernetes clusters with &lt;a class="link" href="https://cilium.io/" target="_blank" rel="noopener"
&gt;cilium&lt;/a&gt; in &lt;code&gt;iptables&lt;/code&gt; mode and not &lt;code&gt;eBPF&lt;/code&gt; mode. But you have no proof&amp;hellip;&lt;/p&gt;
&lt;p&gt;However, in the highly unlikely hypothesis that I could have made such a blunder, here&amp;rsquo;s how I would have gone about solving the problem.&lt;/p&gt;
&lt;p&gt;#trollface&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s imagine that when checking the Cilium configuration, here&amp;rsquo;s what you found:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl -n cilium exec -it cilium-aaaaa -- cilium status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;...]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Host Routing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Legacy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Masquerading: IPTables [IPv4: Enabled, IPv6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Disabled]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;...]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Damn!&lt;/p&gt;
&lt;p&gt;Looking more closely, the cilium-agent containers in your cilium pods are using a lot of CPU and RAM and are starting to clog up your workers&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a disaster&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="why"&gt;Why?
&lt;/h2&gt;&lt;p&gt;The first implementations of the Kubernetes &lt;em&gt;imaginary network&lt;/em&gt; (the famous CNI plugins) used &lt;code&gt;iptables&lt;/code&gt;. However, we&amp;rsquo;ve known for a very long time, especially in the Kubernetes use case, that &lt;code&gt;iptables&lt;/code&gt; is pretty bad at handling large quantities of rules.&lt;/p&gt;
&lt;p&gt;In the particular case of Kubernetes, the number of rules tends to increase exponentially with cluster size, and the CPU consumed to route network packets with it&amp;hellip;&lt;/p&gt;
&lt;p&gt;At some point, traversing the rules list takes all the CPU of a given node and makes it unresponsive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hello boss? We&amp;rsquo;re in trouble.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is one of the reasons why I really like cilium as a CNI plugin - the developers were among the first to bet on &lt;a class="link" href="https://ebpf.io/" target="_blank" rel="noopener"
&gt;eBPF&lt;/a&gt; as a replacement for iptables (although there are other implementations / other technologies that solve this problem).&lt;/p&gt;
&lt;h2 id="how-did-we-get-here"&gt;How did we get here?
&lt;/h2&gt;&lt;p&gt;To be honest, I just read the docs and trusted them&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We introduced eBPF-based host-routing in Cilium 1.9 to fully bypass iptables and the upper host stack, and to achieve a faster network namespace switch compared to regular veth device operation. &lt;strong&gt;This option is automatically enabled if your kernel supports it&lt;/strong&gt;. To validate whether your installation is running with eBPF host-routing, run cilium status in any of the Cilium pods and look for the line reporting the status for &amp;ldquo;Host Routing&amp;rdquo; which should state &amp;ldquo;BPF&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Basically, &lt;a class="link" href="https://docs.cilium.io/en/stable/operations/system_requirements/" target="_blank" rel="noopener"
&gt;according to the official docs, if you have the right kernel and the right modules&lt;/a&gt;, the cilium installation is supposed to automatically enable eBPF mode&amp;hellip; Except that we can clearly see above that this isn&amp;rsquo;t the case, despite a recent kernel (6.2).&lt;/p&gt;
&lt;p&gt;Digging a little deeper into the logs, here&amp;rsquo;s what you can find:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl -n cilium logs cilium-7b5cp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BPF host routing requires enable-bpf-masquerade. Falling back to legacy host routing (enable-host-legacy-routing=true).&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;daemon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well&amp;hellip; apparently, an option is missing in the Helm chart.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kubeProxyReplacement: strict
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ bpf:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ masquerade: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Problem solved, end of article?&lt;/p&gt;
&lt;h2 id="problem"&gt;Problem
&lt;/h2&gt;&lt;p&gt;Yes, of course, we can modify the chart like a bull in a china shop and end of story.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s say we don&amp;rsquo;t want to cut production traffic&amp;hellip; How do we do it?&lt;/p&gt;
&lt;p&gt;The cleanest solution that comes to mind is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we &lt;code&gt;drain&lt;/code&gt; a node&lt;/li&gt;
&lt;li&gt;we change its configuration&lt;/li&gt;
&lt;li&gt;we &lt;code&gt;uncordon&lt;/code&gt; it to put some traffic back on it&lt;/li&gt;
&lt;li&gt;we check that the new configuration works
AND&lt;/li&gt;
&lt;li&gt;we check that nodes can talk to each other between those with &lt;code&gt;iptables&lt;/code&gt; and those with &lt;code&gt;eBPF&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because yeah, it would be a shame to have half the cluster unable to communicate with the other half&amp;hellip;&lt;/p&gt;
&lt;h2 id="checking-connectivity"&gt;Checking connectivity
&lt;/h2&gt;&lt;p&gt;What&amp;rsquo;s cool about cilium (did I tell you I love cilium?) is that we already have tooling to test everything.&lt;/p&gt;
&lt;p&gt;The cilium CLI has a &lt;code&gt;cilium connectivity test&lt;/code&gt; subcommand that launches dozens of internal/external tests to check that everything is OK.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;➜ ~ cilium -n cilium connectivity &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ℹ️ Monitor aggregation detected, will skip some flow validation steps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying echo-same-node service...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying DNS &lt;span class="nb"&gt;test&lt;/span&gt; server configmap...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying same-node deployment...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying client deployment...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✅ All &lt;span class="m"&gt;32&lt;/span&gt; tests &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;265&lt;/span&gt; actions&lt;span class="o"&gt;)&lt;/span&gt; successful, &lt;span class="m"&gt;2&lt;/span&gt; tests skipped, &lt;span class="m"&gt;0&lt;/span&gt; scenarios skipped.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s also a &lt;code&gt;cilium-health&lt;/code&gt; command, embedded in the cilium-agent container, which allows you to get periodic latency statistics between all the cluster nodes. Useful!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl -n cilium &lt;span class="nb"&gt;exec&lt;/span&gt; -it cilium-ccccc -c cilium-agent -- cilium-health status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Probe time: 2023-09-12T14:47:03Z
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nodes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; node-02 &lt;span class="o"&gt;(&lt;/span&gt;localhost&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Host connectivity to 172.31.0.152:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;860.424µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;110.142µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Endpoint connectivity to 10.0.2.56:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;783.861µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;256.419µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; node-01:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Host connectivity to 172.31.0.151:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;813.324µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;553.445µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Endpoint connectivity to 10.0.1.53:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;865.976µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.440655ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally, we can just look at the &lt;code&gt;cilium status&lt;/code&gt; command which tells us who is &amp;ldquo;reachable&amp;rdquo; (again, in the cilium-agent container)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;➜ kubectl -n cilium &lt;span class="nb"&gt;exec&lt;/span&gt; -ti cilium-ddddd -- cilium status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host Routing: Legacy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Masquerading: IPTables &lt;span class="o"&gt;[&lt;/span&gt;IPv4: Enabled, IPv6: Disabled&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cluster health: 5/5 reachable &lt;span class="o"&gt;(&lt;/span&gt;2023-09-14T12:01:51Z&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="changing-a-nodes-configuration"&gt;Changing a node&amp;rsquo;s configuration
&lt;/h2&gt;&lt;p&gt;We&amp;rsquo;re in luck, because since cilium version 1.13 (the latest to date is 1.14), it&amp;rsquo;s possible to apply different configurations to a subset of nodes (official documentation for &lt;a class="link" href="https://docs.cilium.io/en/stable/configuration/per-node-config/#per-node-configuration" target="_blank" rel="noopener"
&gt;Per-node configuration&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Note: before that, it was still possible, &lt;em&gt;temporarily&lt;/em&gt;, by manually editing the cilium ConfigMap, then restarting the concerned pods for it to take effect.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s now a CRD to do this, called &lt;strong&gt;CiliumNodeConfig&lt;/strong&gt;. The only things we have to do are add a label to a node (io.cilium.enable-ebpf: &amp;ldquo;true&amp;rdquo;) and add the following manifest to our cluster:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; cilium-fix.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: cilium.io/v2alpha1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: CiliumNodeConfig
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: cilium
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: cilium-switch-from-iptables-ebpf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nodeSelector:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; matchLabels:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; io.cilium.enable-ebpf: &amp;#34;true&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; defaults:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enable-bpf-masquerade: &amp;#34;true&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f cilium-fix.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl label node node-05 --overwrite &lt;span class="s1"&gt;&amp;#39;io.cilium.enable-ebpf=true&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re in production, the cleanest approach is to &lt;code&gt;kubectl drain&lt;/code&gt; the Node beforehand.&lt;/p&gt;
&lt;p&gt;Out of curiosity, I still tried to do it &amp;ldquo;hot&amp;rdquo;, for fun.&lt;/p&gt;
&lt;p&gt;I deployed a daemonset containing &lt;a class="link" href="https://github.com/zwindler/vhelloworld" target="_blank" rel="noopener"
&gt;a V(lang) app that simply returns the container name in a web page&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;cat &amp;gt; vhelloworld-daemonset.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DaemonSet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vhelloworld-daemonset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zwindler/vhelloworld:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl apply -f vhelloworld-daemonset.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vhelloworld-daemonset-q4h44 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 3m31s 10.0.4.87 nodekube-05 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vhelloworld-daemonset-w97pv 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 3m31s 10.0.3.85 nodekube-04 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I created containers with a shell and &lt;code&gt;curl&lt;/code&gt; to periodically check from multiple nodes that the containers were accessible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl run -it --image curlimages/curl:latest curler -- /bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;If you don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t see a &lt;span class="nb"&gt;command&lt;/span&gt; prompt, try pressing enter.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~ $ curl http://10.0.4.87:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~ $ curl http://10.0.3.85:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~ $ &lt;span class="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; date
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl http://10.0.4.87:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl http://10.0.3.85:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the label was applied to a node and its cilium pod killed (via &lt;code&gt;kubectl delete&lt;/code&gt;), the pods remained accessible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:34 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:35 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:36 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At some point, cilium restarted, noticed the presence of existing pods, and took over with eBPF!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;evel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Rewrote endpoint BPF program&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;containerID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8b7be1b032 &lt;span class="nv"&gt;datapathPolicyRevision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;desiredPolicyRevision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;endpointID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1018&lt;/span&gt; &lt;span class="nv"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4773&lt;/span&gt; &lt;span class="nv"&gt;ipv4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.3.85 &lt;span class="nv"&gt;ipv6&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;k8sPodName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default/vhelloworld-daemonset-w97pv &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;endpoint
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Restored endpoint&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;endpointID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1018&lt;/span&gt; &lt;span class="nv"&gt;ipAddr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[10.0.3.85 ]&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;endpoint
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="does-it-work-but-is-it-better-in-terms-of-resource-consumption"&gt;Does it work, but is it better in terms of resource consumption?
&lt;/h2&gt;&lt;p&gt;That was &lt;strong&gt;the&lt;/strong&gt; good news. I was expecting gains because the cluster was really close to the dreaded death by iptables.&lt;/p&gt;
&lt;p&gt;The cilium containers were using 10% of my nodes&amp;rsquo; CPU, and several GB of RAM in &lt;code&gt;iptables&lt;/code&gt; mode. It could quickly have gone much higher if I had grown the cluster beyond 50 nodes / 2000 pods.&lt;/p&gt;
&lt;p&gt;Switching to eBPF mode allowed us to return to totally painless levels (1% CPU per node, 1-2% RAM) compared to my machines&amp;rsquo; configurations.&lt;/p&gt;
&lt;p&gt;Not bad, huh?&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://isovalent.com/blog/post/tutorial-migrating-to-cilium-part-1/" target="_blank" rel="noopener"
&gt;A really hairy tutorial to fully migrate CNI hot (from flannel to cilium)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Run AIDungeon 2 on Ubuntu 18.04</title><link>https://blog.zwindler.fr/en/2020/01/08/run-aidungeon-2-on-ubuntu-18.04/</link><pubDate>Wed, 08 Jan 2020 11:30:00 +0000</pubDate><guid>https://blog.zwindler.fr/en/2020/01/08/run-aidungeon-2-on-ubuntu-18.04/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/aidungeon_screen.webp" alt="Featured image of post Run AIDungeon 2 on Ubuntu 18.04" /&gt;&lt;h2 id="aidungeon-2"&gt;AIDungeon 2
&lt;/h2&gt;&lt;p&gt;A few weeks ago, I stumbled upon AIDungeon 2, a hilarious project mixing &lt;em&gt;Text based&lt;/em&gt; adventure, (heavy) machine learning and big (big BIG) CUDA GPUs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AIDungeon2 is a first of its kind AI generated text adventure. Using a 1.5B parameter machine learning model called GPT-2 AIDungeon2 generates the story and results of your actions as you play in this virtual world. Unlike virtually every other game in existence, you are not limited by the imagination of the developer in what you can do. Any thing you can express in language can be your action and the AI dungeon master will decide how the world responds to your actions.
&lt;a class="link" href="https://www.aidungeon.io/" target="_blank" rel="noopener"
&gt;https://www.aidungeon.io/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, if you haven’t heard of it yet, it’s a machine learning project created by Nick Walton, a college student. It’s based on the &lt;a class="link" href="https://openai.com/blog/better-language-models/" target="_blank" rel="noopener"
&gt;GPT-2 text model from OpenAI&lt;/a&gt; that you may already seen in other fun projects and was trained to predict the next word using 40 GB of Internet text (you can also check &lt;a class="link" href="https://talktotransformer.com/" target="_blank" rel="noopener"
&gt;TalkTotransformer by Adam D King&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="so-what-does-it-do-"&gt;So&amp;hellip; What does it do ?
&lt;/h2&gt;&lt;p&gt;That’s where the fun begins. Like the TalkTotransformer generator, from a simple semi random generated background, the machine learning model builds the start of a new story, different every time. And your actions stear the story in one way or the other.&lt;/p&gt;
&lt;p&gt;I won’t lie, it’s far from perfect. The model tends to run in circles, or forgets what the other characters did just a line before, which can be really annoying.&lt;/p&gt;
&lt;p&gt;But&amp;hellip; aside from this, the possibilities seem to be limitless, and that’s REALLY impressive.&lt;/p&gt;
&lt;p&gt;After all, that’s not really surprising. You’re only limited by the knowledge and writing style of 40 GB of Internet text! If you need examples, I invite you to take a look at Nick Walton’s twitter feed or &lt;a class="link" href="https://www.reddit.com/r/AIDungeon/" target="_blank" rel="noopener"
&gt;AIDungeon subreddit&lt;/a&gt; to find out the most hilarious adventures the AIDungeon community came upon.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/01/story.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In this example, my inputs are the 2 lines preceded by the « &amp;gt; » symbol, the machine did the rest&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="whats-the-catch-"&gt;What’s the catch ?
&lt;/h2&gt;&lt;p&gt;I’ve already said it.&lt;/p&gt;
&lt;p&gt;That game is probably &lt;em&gt;the most GPU intensive game you’ve run in your life&lt;/em&gt;. For a text based adventure, even that is already ironically fun.&lt;/p&gt;
&lt;p&gt;The game requires nearly 9 GB of GPU VRAM and a lot of CUDA cores, ruling out all AMD cards and nearly every NVidia cards costing less than 1500$.&lt;/p&gt;
&lt;p&gt;Bummer&amp;hellip;&lt;/p&gt;
&lt;h2 id="community-to-the-rescue"&gt;Community to the rescue
&lt;/h2&gt;&lt;p&gt;Hopefully, the community enthusiasm was so intense that Nick Walton and his brother have decided to drop everything else to improve it. During december, they built mobile Apps and now a web based one to play on every device.&lt;/p&gt;
&lt;p&gt;Of course, these apps run on AWS servers featuring Tegra GPUs and cost around 65k$ a month in hosting. They have managed to raise nearly 15k$/month on their Patreon Account but there may come a day (probably very soon) where they won’t be able to provide free access for everyone.&lt;/p&gt;
&lt;p&gt;So, after you read the article, &lt;a class="link" href="https://www.patreon.com/AIDungeon" target="_blank" rel="noopener"
&gt;if you like the game, don’t forget to support them!&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="so-what-now"&gt;So what now?
&lt;/h2&gt;&lt;p&gt;It also turns out that, Nick Walton published the game as an open source project. That’s where I became the most interested in this game, in fact. And you can find the sources &lt;a class="link" href="https://github.com/AIDungeon/AIDungeon" target="_blank" rel="noopener"
&gt;on the Github AIDungeons project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Starting from there, I asked myself:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey! Wouldn’t it be nice to run it on a cloud instance on a random cloud provider GPU powered VM?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I decided to try it.&lt;/p&gt;
&lt;p&gt;For my test, I chose to run AIDungeon on a NC6 virtual machine (6 vcpus, 56 GiB memory, Tegra K80) on Microsoft Azure on a free test account. This machine costs approximatly 0.43€ per hour (or even 0,21€ if you use preemtible VMs) so even if you don’t use a free credit, it won’t cost you too much.&lt;/p&gt;
&lt;p&gt;Side note: Of course, if you have a GTX 1080 Ti, GTX 2080 Ti or GTX 2080ti super (or a K80), you can also run it on your own machine&amp;hellip;&lt;/p&gt;
&lt;p&gt;On my NC6, I deployed a Ubuntu 18.04.&lt;/p&gt;
&lt;p&gt;And that’s where this tutorial begins ;-)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/01/aidungeon.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Side note: If you follow this guide and start on a fresh Ubuntu 18.04, the installation process should take 30 minutes to 45 minutes.&lt;/p&gt;
&lt;h2 id="install-updates-and-prerequisites"&gt;Install updates and prerequisites
&lt;/h2&gt;&lt;p&gt;Once connected on the machine, update and upgrade the OS.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get update
sudo apt-get upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, install prerequisites packages for AI&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get install git aria2 unzip python3-pip
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="dependancy-hellscape"&gt;Dependancy hellscape
&lt;/h2&gt;&lt;p&gt;Now, the real fun begins. AIDungeon uses TensorFlow and CUDA drivers to run. But here’s the catch: not every versions will work!&lt;/p&gt;
&lt;p&gt;To run AIDungeons, you have to install specifically tensorflow 1.15 (no more, no less). And tensorflow==1.15 specifically requires cuda10.0 (not cuda10.1 nor cuda10.2) and Python 3.4 to 3.7!&lt;/p&gt;
&lt;p&gt;The dependancy nightmare begins&amp;hellip;&lt;/p&gt;
&lt;h2 id="install-nvidia-drivers-and-cuda-and-machine-learning-modules"&gt;Install NVidia drivers and Cuda and machine learning modules
&lt;/h2&gt;&lt;p&gt;Add the cuda10.0 repos:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
sudo apt-get update
wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
sudo apt install ./nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
sudo apt-get update
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can install nvidia-driver, and reboot:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get install --no-install-recommends nvidia-driver-440 xserver-xorg-video-nvidia-440
sudo reboot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After reboot, install cuda10.0 libs&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get install --no-install-recommends \
cuda-10-0 cuda-runtime-10-0 cuda-demo-suite-10-0 cuda-drivers \
libcudnn7=7.6.2.24-1+cuda10.0 libcudnn7-dev=7.6.2.24-1+cuda10.0
sudo apt-get install -y --no-install-recommends libnvinfer5=5.1.5-1+cuda10.0 \
libnvinfer-dev=5.1.5-1+cuda10.0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="get-the-sources"&gt;Get the sources
&lt;/h2&gt;&lt;p&gt;Download the source from Github and hop-in in the directory:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git clone https://github.com/AIDungeon/AIDungeon/
cd AIDungeon/
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="install-python-dependancies-through-pip"&gt;Install python dependancies through pip
&lt;/h2&gt;&lt;p&gt;Sadly, installation time not yet over. Now that we have the python project on deck, we need to install the Python dependancies&amp;hellip; By default, Ubuntu 18.04 still serves Python 2 as default Python interpreter, which is now deprecated since 1st january. Hopefully Python 3 is easily available (not like on CentOS 7).&lt;/p&gt;
&lt;p&gt;Also, the pip (python package manager) installed should be updated as pip installed by Ubuntu is not compatible with tensorflow 1.15.&lt;/p&gt;
&lt;p&gt;Upgrading pip in place can be tedious as this often lead to &amp;ldquo;pip ImportError: cannot import name ‘main’ after update&amp;rdquo; error message. To work around this, use the script given in &lt;a class="link" href="https://pip.pypa.io/en/stable/installing/#upgrading-pip" target="_blank" rel="noopener"
&gt;upgrading pip&lt;/a&gt; official page and you should be fine.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python3 get-pip.py
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="install-aidungeon-for-good"&gt;Install AIDungeon for good
&lt;/h2&gt;&lt;p&gt;Since I wrote the draft of the article a few weeks ago, there was a dependancy missing (gsutil) and the install script was not perfect. But now it seems to be working better and even uses venvs for a clean Python dependancies install.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo ./install.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If it doesn’t work, you can install it yourself with the following commands:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python3 -m pip install -r requirements.txt --user
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The next script allows you to download the AIDungeon machine learning model through a torrent file (at first, Nick had a terrifying GDrive bill due to enormous egress traffic).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./download_model.sh
[...]
Status Legend:
(OK):download completed.
Download Complete!
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="run-aidungeon-2"&gt;Run AIDungeon 2
&lt;/h2&gt;&lt;p&gt;Finally, you can now sit back and enjoy the game!&lt;/p&gt;
&lt;p&gt;If you used the install.sh script, use the following command (with venv):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;source ./venv/bin/activate
./play.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If not, skip the venv step:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd ~/AIDungeon/
python3 play.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/01/initializing.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;The initialisation should take a few minutes (don’t panic, it’s &amp;ldquo;normal&amp;rdquo;), depending of your setup. In december, initialization took 5-10 minutes but there seem to have been optimisation now as it took only a minute or two last time I checked.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/01/aidungeon_screen.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="bonus-useful-command-to-check-gpu-consumption"&gt;Bonus: Useful command to check GPU consumption
&lt;/h2&gt;&lt;p&gt;Install gpustat to check if GPU usage is working&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip3 install gpustat --user
gpustat
gpustat -cp
aidungeon2 Mon Dec 9 13:22:24 2019 430.50
[0] Tesla K80 | 69&amp;#39;C, 17 % | 0 / 11441 MB |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or use integrated nvidia tool (a little crude)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nvidia-smi --loop=1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="bonus-check-that-gpu-is-working-with-tensorflow"&gt;Bonus: Check that GPU is working with tensorflow
&lt;/h2&gt;&lt;p&gt;See &lt;a class="link" href="https://www.tensorflow.org/guide/gpu" target="_blank" rel="noopener"
&gt;Tensorflow GPU guide&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python3
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
print(&amp;#34;Num GPUs Available: &amp;#34;, len(tf.config.experimental.list_physical_devices(&amp;#39;GPU&amp;#39;)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example working GPU setup&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2019-12-10 16:25:39.714605: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
2019-12-10 16:25:39.743758: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1618] Found device 0 with properties:
name: Tesla K80 major: 3 minor: 7 memoryClockRate(GHz): 0.8235
pciBusID: 81c7:00:00.0
2019-12-10 16:25:39.744001: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.0
[...]
2019-12-10 16:25:39.754396: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1746] Adding visible gpu devices: 0
Num GPUs Available: 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example of non working GPU setup&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
Num GPUs Available: 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you have this, game will be very slow (waiting 1-2 minutes between each answer) but will not crash. Check that Cuda and tensorFlow are proprely installed.&lt;/p&gt;
&lt;h2 id="bonus-gsutil-and-tensorflow-errors"&gt;Bonus: gsutil and tensorflow errors
&lt;/h2&gt;&lt;p&gt;If you forgot or failed to upgrade pip, you will get this error :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Collecting tensorflow==1.15 (from -r requirements.txt (line 6))
Could not find a version that satisfies the requirement tensorflow==1.15 (from -r requirements.txt (line 6)) (from versions: 0.12.1, 1.0.0, 1.0.1, 1.1.0rc0, 1.1.0rc1, 1.1.0rc2, 1.1.0, 1.2.0rc0, 1.2.0rc1, 1.2.0rc2, 1.2.0, 1.2.1, 1.3.0rc0, 1.3.0rc1, 1.3.0rc2, 1.3.0, 1.4.0rc0, 1.4.0rc1, 1.4.0, 1.4.1, 1.5.0rc0, 1.5.0rc1, 1.5.0, 1.5.1, 1.6.0rc0, 1.6.0rc1, 1.6.0, 1.7.0rc0, 1.7.0rc1, 1.7.0, 1.7.1, 1.8.0rc0, 1.8.0rc1, 1.8.0, 1.9.0rc0, 1.9.0rc1, 1.9.0rc2, 1.9.0, 1.10.0rc0, 1.10.0rc1, 1.10.0, 1.10.1, 1.11.0rc0, 1.11.0rc1, 1.11.0rc2, 1.11.0, 1.12.0rc0, 1.12.0rc1, 1.12.0rc2, 1.12.0, 1.12.2, 1.12.3, 1.13.0rc0, 1.13.0rc1, 1.13.0rc2, 1.13.1, 1.13.2, 1.14.0rc0, 1.14.0rc1, 1.14.0, 2.0.0a0, 2.0.0b0, 2.0.0b1)
No matching distribution found for tensorflow==1.15 (from -r requirements.txt (line 6))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You should not come across this anymore now, but if you get this error you should install gsutil python module to avoid stacktrace when saving / exiting&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; ^C
Traceback (most recent call last):
File &amp;#34;play.py&amp;#34;, line 211, in &amp;lt;module&amp;gt;
play_aidungeon_2()
File &amp;#34;play.py&amp;#34;, line 97, in play_aidungeon_2
action = input(&amp;#34;&amp;gt; &amp;#34;)
KeyboardInterrupt
Exception ignored in: &amp;lt;bound method Story.__del__ of &amp;lt;story.story_manager.Story object at 0x7f797f4af0f0&amp;gt;&amp;gt;
Traceback (most recent call last):
File &amp;#34;/home/zwindler/AIDungeon/story/story_manager.py&amp;#34;, line 35, in __del__
self.save_to_storage()
File &amp;#34;/home/zwindler/AIDungeon/story/story_manager.py&amp;#34;, line 131, in save_to_storage
p = Popen([&amp;#39;gsutil&amp;#39;, &amp;#39;cp&amp;#39;, file_name, &amp;#39;gs://aidungeonstories&amp;#39;], stdout=FNULL, stderr=subprocess.STDOUT)
File &amp;#34;/usr/lib/python3.6/subprocess.py&amp;#34;, line 729, in __init__
restore_signals, start_new_session)
File &amp;#34;/usr/lib/python3.6/subprocess.py&amp;#34;, line 1364, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: &amp;#39;gsutil&amp;#39;: &amp;#39;gsutil&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="sources"&gt;Sources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.tensorflow.org/install/gpu" target="_blank" rel="noopener"
&gt;Tensorflow installation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/tensorflow/tensorflow/issues/26182" target="_blank" rel="noopener"
&gt;Tensorflow install issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.tensorflow.org/guide/gpu" target="_blank" rel="noopener"
&gt;Tensorflow GPU guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>