<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Garambrogne 2.0</title><link href="http://blog.garambrogne.net/" rel="alternate"></link><link href="http://blog.garambrogne.net/feeds/all.atom.xml" rel="self"></link><id>http://blog.garambrogne.net/</id><updated>2026-01-28T18:15:00+01:00</updated><entry><title>Learning Pyxel: Fleeing Danger and Game Over</title><link href="http://blog.garambrogne.net/pyxel-beholder-en.html" rel="alternate"></link><published>2026-01-28T18:15:00+01:00</published><updated>2026-01-28T18:15:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2026-01-28:/pyxel-beholder-en.html</id><summary type="html">&lt;p&gt;New episode of the Pyxel introduction: your hero will try to survive the ray shot of a&amp;nbsp;beholder.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Third chapter of learning &lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt;, a retro game framework with its engine in Rust (for fluidity) and its &lt;span class="caps"&gt;API&lt;/span&gt; in Python (for&amp;nbsp;simplicity&lt;/p&gt;
&lt;p&gt;&lt;img alt="Beholder" class="image-process-article-image right pixel" src="/images/pyxel-beholder.png" /&gt;
This demo progresses in difficulty, with new Python concepts and the risk of getting your character&amp;nbsp;killed.&lt;/p&gt;
&lt;p&gt;The demo goes beyond the world of sprites by drawing a line (which fortunately remains&amp;nbsp;pixelated).&lt;/p&gt;
&lt;h1 id="demo"&gt;Demo&lt;/h1&gt;
&lt;p&gt;In this demo, a hero explores a dungeon and encounters a &lt;a href="https://en.wikipedia.org/wiki/Beholder_(Dungeons_%26_Dragons)"&gt;beholder&lt;/a&gt;.
The monster will aim, shoot, while the hero attempts to&amp;nbsp;dodge.&lt;/p&gt;
&lt;p&gt;The player will use their keyboard to control the&amp;nbsp;hero.&lt;/p&gt;
&lt;h2 id="assets"&gt;Assets&lt;/h2&gt;
&lt;p&gt;&lt;img :=":" alt="Beholder" src="/images/pyxel-beholder-edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;The sprites are organized: by rows, the characters (and their states), by columns, the direction: bottom (front), left, right, top&amp;nbsp;(back).&lt;/p&gt;
&lt;p&gt;The warrior has one additional sprite:&amp;nbsp;electrocution.&lt;/p&gt;
&lt;h2 id="the-hero-walks-around"&gt;The Hero Walks&amp;nbsp;Around&lt;/h2&gt;
&lt;p&gt;The warrior can walk around with the keyboard arrows, turning in the right&amp;nbsp;direction.&lt;/p&gt;
&lt;h3 id="python-concepts"&gt;Python&amp;nbsp;Concepts&lt;/h3&gt;
&lt;h4 id="list-comprehension"&gt;List&amp;nbsp;Comprehension&lt;/h4&gt;
&lt;p&gt;To create collections (including &lt;em&gt;lists&lt;/em&gt;), Python has a condensed&amp;nbsp;syntax.&lt;/p&gt;
&lt;p&gt;The following&amp;nbsp;code:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Is equivalent&amp;nbsp;to:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Technically, adding elements to a list is more expensive than the dense&amp;nbsp;syntax.&lt;/p&gt;
&lt;h3 id="demo_1"&gt;Demo&lt;/h3&gt;
&lt;p&gt;A&amp;nbsp;constant, &lt;code&gt;TRANSPARENT&lt;/code&gt;
, is used to designate the transparency color, without having to copy it throughout the code and to facilitate&amp;nbsp;readability.&lt;/p&gt;
&lt;p&gt;A character has&amp;nbsp;a &lt;code&gt;speed&lt;/code&gt; attribute that corresponds to its movement in pixels, each &lt;em&gt;frame&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The images are ordered,&amp;nbsp;the &lt;code&gt;angle&lt;/code&gt; attribute simultaneously describes which way the character is looking, and its rank in the image&amp;nbsp;row.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;move&lt;/code&gt; method moves the character one step&amp;nbsp;(the &lt;code&gt;speed&lt;/code&gt; attribute), in the right&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;To avoid a slew&amp;nbsp;of &lt;code&gt;ifs&lt;/code&gt;, a multiplier is&amp;nbsp;used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0:&amp;nbsp;nothing&lt;/li&gt;
&lt;li&gt;1:&amp;nbsp;forward&lt;/li&gt;
&lt;li&gt;-1:&amp;nbsp;backward&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, to prevent the character from leaving the screen, a bunch&amp;nbsp;of &lt;code&gt;ifs&lt;/code&gt; is&amp;nbsp;needed.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;App&lt;/code&gt; &lt;em&gt;class&lt;/em&gt; is very classic, the keyboard arrows are used to move&amp;nbsp;(with &lt;code&gt;move&lt;/code&gt;) the character in the right&amp;nbsp;direction.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The hero explores&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="the-hero-walks-in-front-of-the-beholder-who-follows-with-its-gaze"&gt;The Hero Walks in Front of the Beholder Who Follows with Its&amp;nbsp;Gaze&lt;/h2&gt;
&lt;h3 id="python-concepts_1"&gt;Python&amp;nbsp;Concepts&lt;/h3&gt;
&lt;h4 id="class-inheritance"&gt;Class&amp;nbsp;Inheritance&lt;/h4&gt;
&lt;p&gt;Two characters are described by two &lt;em&gt;classes&lt;/em&gt;, and since they have quite a bit in common (they are &lt;em&gt;sprites&lt;/em&gt; with a&amp;nbsp;direction).&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;class&lt;/em&gt; can inherit from another &lt;em&gt;class&lt;/em&gt;.
The parent &lt;em&gt;class&lt;/em&gt; defines &lt;em&gt;attributes&lt;/em&gt; and &lt;em&gt;methods&lt;/em&gt;, child &lt;em&gt;classes&lt;/em&gt; can have their own &lt;em&gt;attributes&lt;/em&gt;/&lt;em&gt;methods&lt;/em&gt; or &lt;em&gt;override&lt;/em&gt; an element of the parent &lt;em&gt;class&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wazza&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s possible for an &lt;em&gt;overridden method&lt;/em&gt; to &lt;em&gt;call&lt;/em&gt; the &lt;em&gt;method&lt;/em&gt; of an &lt;em&gt;ancestor&lt;/em&gt; with&amp;nbsp;the &lt;code&gt;super()&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I say &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;, or not&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Overriding a &lt;em&gt;method&lt;/em&gt; can be mandatory if the &lt;em&gt;parent method&lt;/em&gt; &lt;em&gt;raise&lt;/em&gt;&amp;nbsp;a &lt;code&gt;NotImplementedError&lt;/code&gt; &lt;em&gt;exception&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;override_me&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="multiple-assignment"&gt;multiple&amp;nbsp;assignment&lt;/h4&gt;
&lt;p&gt;Multiple assignement get a condensed syntax&amp;nbsp;too.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Is equivalent&amp;nbsp;to:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="typing"&gt;Typing&lt;/h4&gt;
&lt;p&gt;Python has &lt;a href="https://docs.python.org/3/library/typing.html"&gt;typing&lt;/a&gt; for its values, but not &lt;em&gt;strong typing&lt;/em&gt;: a &lt;em&gt;variable&lt;/em&gt; can be reassigned with a value of a different &lt;em&gt;type&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Python uses &lt;em&gt;hints&lt;/em&gt; to make the type of a variable explicit.
Python maintains backward compatibility: the &lt;em&gt;hints&lt;/em&gt; are not taken into account by the interpreter.
Typing is intended (among other things) for static analysis (a bot reads and gives its opinion on your&amp;nbsp;code).&lt;/p&gt;
&lt;p&gt;Modern editors take typing into account for their suggestions, and to complain (underline in red) about parts of your code that don&amp;#8217;t respect the&amp;nbsp;typing.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="c1"&gt;# a is an integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="ternary-operators"&gt;Ternary&amp;nbsp;operators&lt;/h4&gt;
&lt;p&gt;Python get a condensed syntax for value assignement with a&amp;nbsp;condition.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;become:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Value&amp;nbsp;1, &lt;code&gt;if&lt;/code&gt;,&amp;nbsp;clause, &lt;code&gt;else&lt;/code&gt;, value&amp;nbsp;2.&lt;/p&gt;
&lt;p&gt;Be careful not to overuse dense syntax which can destroy&amp;nbsp;readability.&lt;/p&gt;
&lt;h3 id="demo_2"&gt;Demo&lt;/h3&gt;
&lt;p&gt;The basic behaviors&amp;nbsp;of &lt;code&gt;Hero&lt;/code&gt; are now in&amp;nbsp;the &lt;code&gt;Sprite&lt;/code&gt; &lt;em&gt;class&lt;/em&gt; which will be the &lt;em&gt;parent class&lt;/em&gt;&amp;nbsp;of &lt;code&gt;Hero&lt;/code&gt; and &lt;code&gt;Beholder&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Beholder&lt;/code&gt; &lt;em&gt;class&lt;/em&gt; has&amp;nbsp;a &lt;code&gt;state&lt;/code&gt; &lt;em&gt;attribute&lt;/em&gt;, and uses 2 rows of&amp;nbsp;images.
&lt;code&gt;state&lt;/code&gt; allows knowing which row to use, the direction remains the column as in the previous demo (used in&amp;nbsp;the &lt;code&gt;image&lt;/code&gt; &lt;em&gt;method&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;The beholder can glare at a target with&amp;nbsp;the &lt;code&gt;watch&lt;/code&gt; &lt;em&gt;method&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A bit of math: we calculate the delta between the two characters on the horizontal and vertical&amp;nbsp;axis, &lt;code&gt;dx&lt;/code&gt; and &lt;code&gt;dy&lt;/code&gt;.
If the horizontal distance is greater than the vertical distance, the beholder looks horizontally.&amp;nbsp;If &lt;code&gt;dx&lt;/code&gt; is positive, it looks left, otherwise right.
Same thing for the vertical&amp;nbsp;axis.&lt;/p&gt;
&lt;p&gt;In&amp;nbsp;the &lt;code&gt;App&lt;/code&gt; &lt;em&gt;class&lt;/em&gt;,&amp;nbsp;the &lt;code&gt;update&lt;/code&gt; &lt;em&gt;method&lt;/em&gt; specifies that&amp;nbsp;the &lt;code&gt;space&lt;/code&gt; key activates&amp;nbsp;the &lt;code&gt;LOADING&lt;/code&gt; state of the beholder (it looks up), and that it watches the&amp;nbsp;nero.&lt;/p&gt;
&lt;!-- Ajouter un schéma --&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
&lt;span class="c1"&gt;# Beholder states&lt;/span&gt;
&lt;span class="n"&gt;LOADING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;WAITING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# bottom, left, right, top&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# What is the current image&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder can be normal : waiting, aiming, moving…&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder loads its death ray&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The beholder watch the hero&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_SPACE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="the-beholder-tries-to-strike-the-hero-with-lightning"&gt;The Beholder Tries to Strike the Hero with&amp;nbsp;Lightning&lt;/h2&gt;
&lt;h3 id="python-concepts_2"&gt;Python&amp;nbsp;Concepts&lt;/h3&gt;
&lt;p&gt;No new python concepts in this chapter, math calculus are boring&amp;nbsp;enough.&lt;/p&gt;
&lt;h3 id="demo_3"&gt;Demo&lt;/h3&gt;
&lt;p&gt;The hero and the monster now have&amp;nbsp;the &lt;code&gt;state&lt;/code&gt; &lt;em&gt;attribute&lt;/em&gt; which designates (obviously) the&amp;nbsp;state.&lt;/p&gt;
&lt;p&gt;The beholder &lt;em&gt;sprites&lt;/em&gt; are arranged on two&amp;nbsp;rows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The second when it prepares its shot (it looks&amp;nbsp;up)&lt;/li&gt;
&lt;li&gt;The first for the other states (waiting and&amp;nbsp;shooting)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hero has one more column, the electrocuted state, which has no&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;All this is handled by&amp;nbsp;the &lt;code&gt;image&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;To give the hero a chance, the beholder aims at the current position of the hero, in&amp;nbsp;the &lt;code&gt;aim&lt;/code&gt; &lt;em&gt;method&lt;/em&gt;, waits a bit, the time to charge its death ray, then shoots where the hero &lt;span class="caps"&gt;WAS&lt;/span&gt;,&amp;nbsp;the &lt;code&gt;shoot&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The ray is instantaneous, but it shoots late.
If the hero hasn&amp;#8217;t moved enough, it&amp;nbsp;dies.&lt;/p&gt;
&lt;p&gt;The start and end of the ray are from the center of the sprites, not from the top left&amp;nbsp;corner.&lt;/p&gt;
&lt;p&gt;To know if the hero is hit, we don&amp;#8217;t just consider its center (hit right in the heart?), but we determine the face of its sprite that faces the beholder.
If the ray touches even one of its toes, the character is&amp;nbsp;fried.&lt;/p&gt;
&lt;p&gt;The ray is interrupted by the hero, if it touches it, otherwise, it stops at the edge of the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;To avoid getting lost in&amp;nbsp;unreadable &lt;code&gt;if&lt;/code&gt; statements, a 3x3 grid is&amp;nbsp;used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the middle row corresponds to a vertical&amp;nbsp;shot&lt;/li&gt;
&lt;li&gt;the middle column has a horizontal&amp;nbsp;shot&lt;/li&gt;
&lt;li&gt;the center has a shot into its own&amp;nbsp;foot&lt;/li&gt;
&lt;li&gt;the other cells correspond to combinations of left/right,&amp;nbsp;up/down&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where the heap of math lines begins.
We start by calculating the slope of the shot, then we handle the case where the slope is zero, then we check if the hero is hit laterally, or vertically.
If it&amp;#8217;s hit, the ray stops at the point of impact, the hero switches to&amp;nbsp;the &lt;code&gt;ZAPPED&lt;/code&gt; state and the ray changes&amp;nbsp;color.&lt;/p&gt;
&lt;p&gt;The shot is drawn with&amp;nbsp;Pyxel&amp;#8217;s &lt;code&gt;line&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;In&amp;nbsp;the &lt;code&gt;App&lt;/code&gt;, no math.
The arrows no longer move the hero when it&amp;#8217;s dying.
After 30 frames of pain, the game&amp;nbsp;quits.&lt;/p&gt;
&lt;p&gt;The beholder cycles every 40 frames, it aims at frame 10, shoots at frame&amp;nbsp;30.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;

&lt;span class="c1"&gt;# Beholder states&lt;/span&gt;
&lt;span class="n"&gt;LOADING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;FIRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;MOVING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;WAITING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# Hero states&lt;/span&gt;
&lt;span class="n"&gt;HEALTHY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ZAPPED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# bottom, left, right, top&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# What is the current image&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder can be normal : waiting, aiming, moving…&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder loads its death ray&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# Where the beholder aims&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;aim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;
        &lt;span class="c1"&gt;# The ray starts avec self.x, self.y&lt;/span&gt;
        &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;  &lt;span class="c1"&gt;# horizontal end of the ray&lt;/span&gt;
        &lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;  &lt;span class="c1"&gt;# vertical end of the ray&lt;/span&gt;

        &lt;span class="c1"&gt;# Center of the shooter&lt;/span&gt;
        &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="c1"&gt;# Center of the target&lt;/span&gt;
        &lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="c1"&gt;# Side hit by the shot&lt;/span&gt;
        &lt;span class="n"&gt;target_side_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;target_side_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

        &lt;span class="c1"&gt;# Where the ray ends when it misses ?&lt;/span&gt;

        &lt;span class="n"&gt;cross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# x, y&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# It shoots its own foot&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;
            &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
            &lt;span class="n"&gt;y_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_side_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;
                &lt;span class="n"&gt;x_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_side_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_side_y&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_side_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;
        &lt;span class="n"&gt;bolt_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bolt_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_zapped_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HEALTHY&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HEALTHY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_zapped_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The beholder and its beam of death&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Everything is frozen when the hero is zapped&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 50 frames, the beholder aims&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 25 later, it shoots&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;GAME OVER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The sources of &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/03_beholder"&gt;Beholder are available on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can test the online version of &lt;a href="https://kitao.github.io/pyxel/wasm/launcher/?run=athoune.pyxel-initiation.03_beholder.3_beholder"&gt;Beholder Demo&amp;nbsp;Online&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;#8217;s&amp;nbsp;Next&lt;/h2&gt;
&lt;p&gt;The shot kills the hero in one hit, we could use a health bar system that decreases with each shot, as well as a loading bar for the beholder&amp;#8217;s shot&amp;nbsp;preparation.&lt;/p&gt;
&lt;p&gt;The hero cannot retaliate, what injustice. There would need to be sprites with a sword that strikes (a forward lunge?), with a (short) waiting time during an attack that prevents it from&amp;nbsp;moving.&lt;/p&gt;
&lt;p&gt;A &lt;span class="caps"&gt;HUUUUUUM&lt;/span&gt; sound during charging, then &lt;span class="caps"&gt;ZAAAAP&lt;/span&gt; during shooting, a &lt;span class="caps"&gt;GZZZZZ&lt;/span&gt; during electrocution seems essential to&amp;nbsp;me.&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="game"></category><category term="newbie"></category><category term="retro"></category></entry><entry><title>Uncloud, self hosted Cloud, seen by a developer for developers</title><link href="http://blog.garambrogne.net/uncloud-en.html" rel="alternate"></link><published>2026-01-05T09:17:00+01:00</published><updated>2026-01-05T09:17:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2026-01-05:/uncloud-en.html</id><summary type="html">&lt;p&gt;Uncloud is a promising non-cloud, made by and for developers, but what&amp;#8217;s under the&amp;nbsp;hood?&lt;/p&gt;</summary><content type="html">&lt;h1 id="uncloud"&gt;Uncloud&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://uncloud.run/"&gt;Uncloud&lt;/a&gt; defines itself as a non-cloud, similar to &lt;a href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt; or &lt;a href="https://render.com/"&gt;Render&lt;/a&gt;, open-source and targeting&amp;nbsp;self-hosting.&lt;/p&gt;
&lt;p&gt;Comparing a self-hosted cloud service to these vintage pre-Docker &lt;span class="caps"&gt;SAAS&lt;/span&gt; is weird.
&lt;span class="caps"&gt;OK&lt;/span&gt;, with all these services, you can push your container without any sysadmin involvement, without configuring (almost)&amp;nbsp;anything.&lt;/p&gt;
&lt;p&gt;Uncloud is built during the Docker era, embracing Compose, without any bloat or patronizing&amp;nbsp;developers.&lt;/p&gt;
&lt;p&gt;One more point, the elephant in the room, Kubernetes, the hegemonic solution for hosting containers (spoiler: the main developer has a great experience with K8s for small projects).
Uncloud sees itself as a &lt;a href="https://en.wikipedia.org/wiki/Citro%C3%ABn_2CV"&gt;Citroën &lt;span class="caps"&gt;2CV&lt;/span&gt;&lt;/a&gt; compared to K8s, as a&amp;nbsp;semi-trailer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Tree-kangaroo"&gt;&lt;img alt="Tree-kangaroo" class="image-process-article-image" src="/images/kangourou-arboricole.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="adminless"&gt;Adminless&lt;/h2&gt;
&lt;p&gt;With Uncloud, you can manage your production environment like your development laptop. This sentence is&amp;nbsp;terrifying.&lt;/p&gt;
&lt;p&gt;But, with a bit of rigor, this approach is credible; Uncloud handles a large part of the work with minimal voodoo&amp;nbsp;magic.&lt;/p&gt;
&lt;p&gt;Beware, if you bypass sysadmin, you have to fix online incidents (and updates and backup and fine tunings and a lot of&amp;nbsp;surprises).&lt;/p&gt;
&lt;h2 id="scaleless"&gt;Scaleless&lt;/h2&gt;
&lt;p&gt;Uncloud is hosting agnostic, you can even mix different hostings.
&lt;span class="caps"&gt;VPS&lt;/span&gt;, bare metal, self-hosting, mixing processor families&amp;nbsp;(yikes!).&lt;/p&gt;
&lt;p&gt;It creates a large virtual encrypted network (with Wireguard), and every container can communicate with each other on all&amp;nbsp;servers.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s recall that the product is Australian, which has neither a large number of local users nor an ultimate network connection with the rest of the world. Having distributed nodes close to clients is more important for them than for a French person who struggles to think beyond their metropolitan area (and Europe for the most&amp;nbsp;ambitious).&lt;/p&gt;
&lt;p&gt;Self-hosting is perfectly acceptable, as long as you have enough bandwidth to serve your users.
Remember, cheap hosting is, well, cheap, and doesn&amp;#8217;t blow 24/7 in your living room (or in a&amp;nbsp;closet).&lt;/p&gt;
&lt;p&gt;Mixing local and remote hosting is weird, perhaps to benchmark your fancy Nvidia card for a vital &lt;span class="caps"&gt;AI&lt;/span&gt; feature ?
Sure, give it a try, tokens aren&amp;#8217;t cheap, and local datas can&amp;#8217;t be used/sold somewhere in the Silicon&amp;nbsp;Valley.&lt;/p&gt;
&lt;h2 id="brainless"&gt;Brainless&lt;/h2&gt;
&lt;p&gt;Uncloud is just one&amp;nbsp;command, &lt;code&gt;uc&lt;/code&gt;, which mimics&amp;nbsp;the &lt;code&gt;docker&lt;/code&gt; command.
You can even use docker-compose.yml files (with some additional&amp;nbsp;information).&lt;/p&gt;
&lt;p&gt;Containers registries are boring to manage.
Uncloud push images directly to the remote server, throught &lt;span class="caps"&gt;SSH&lt;/span&gt; (layers by layers, not an ugly full image wrapped in tgz&amp;nbsp;archive).&lt;/p&gt;
&lt;p&gt;The neologism&amp;nbsp;is &lt;code&gt;pussh&lt;/code&gt;,&amp;nbsp;classy.&lt;/p&gt;
&lt;p&gt;By default, the container is pushed somewhere in your cluster, randomly (without packing complexity).
If your containers are heterogeneous (&lt;span class="caps"&gt;CPU&lt;/span&gt;, &lt;span class="caps"&gt;RAM&lt;/span&gt;, bandwidth, storage), good luck.
&lt;span class="caps"&gt;OK&lt;/span&gt;, you can target one or more specific servers (basic load balancing is provided).
Using &lt;em&gt;Docker Compose&lt;/em&gt;, you can set lots of options, like the number of replicas.
It&amp;#8217;s a cleaner deployment&amp;nbsp;strategy.&lt;/p&gt;
&lt;p&gt;Deployment is guaranteed to be uninterrupted, following the well-known &lt;em&gt;blue/green&lt;/em&gt; or &lt;em&gt;rollup&lt;/em&gt;&amp;nbsp;strategies.&lt;/p&gt;
&lt;p&gt;You can name your servers with the&amp;nbsp;free &lt;code&gt;*.cluster.uncloud.run&lt;/code&gt; domain.
Just add a &lt;span class="caps"&gt;DNS&lt;/span&gt; alias with your own domain.
The documentation is not ready yet; you have to&amp;nbsp;wait.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s Encrypt (via Caddy) provides free &lt;span class="caps"&gt;TLS&lt;/span&gt;&amp;nbsp;certificates.&lt;/p&gt;
&lt;p&gt;Firewall blocks everything, you can&amp;#8217;t oops a container, exposing a naked service to the Internet.
Not everything, &lt;span class="caps"&gt;HTTP&lt;/span&gt;/&lt;span class="caps"&gt;HTTPS&lt;/span&gt;/&lt;span class="caps"&gt;SSH&lt;/span&gt;/Wireguard services are public.
Uncloudd (the server side) is used throught &lt;span class="caps"&gt;SSH&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The deployment procedure is something like&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Works for&amp;nbsp;me&lt;/li&gt;
&lt;li&gt;Push to production (the fresh container is started, the old one is&amp;nbsp;stopped)&lt;/li&gt;
&lt;li&gt;If something goes wrong, we immediately have the logs to understand the cause of the&amp;nbsp;oops&lt;/li&gt;
&lt;li&gt;Quick&amp;nbsp;fix&lt;/li&gt;
&lt;li&gt;Re-deployment&lt;/li&gt;
&lt;li&gt;Break for a game of&amp;nbsp;foosball.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dissection"&gt;Dissection&lt;/h2&gt;
&lt;p&gt;It sounds great, but what is the secret sauce, the technical&amp;nbsp;choices?&lt;/p&gt;
&lt;p&gt;The first rule of Uncloud is decentralization. All nodes provide the same &lt;em&gt;uncloudd&lt;/em&gt; service, without specialization or an&amp;nbsp;orchestrator.&lt;/p&gt;
&lt;p&gt;Uncloud doesn&amp;#8217;t use configurations and promises to consume only &lt;span class="caps"&gt;150MB&lt;/span&gt; of &lt;span class="caps"&gt;RAM&lt;/span&gt; per&amp;nbsp;node.&lt;/p&gt;
&lt;h3 id="remote-api"&gt;Remote &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;All services use &lt;a href="https://grpc.io/"&gt;grpc&lt;/a&gt;, with the official &lt;span class="caps"&gt;REST&lt;/span&gt; &lt;a href="https://github.com/grpc-ecosystem/grpc-gateway"&gt;grpc-gateway&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Grpc services accept different transport layers (and wrappers)&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;SSH&lt;/span&gt; tunnel (native&amp;nbsp;or &lt;code&gt;ssh&lt;/code&gt; command)&lt;/li&gt;
&lt;li&gt;Wireguard&amp;nbsp;tunnel&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;UNIX&lt;/span&gt;&amp;nbsp;socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span class="caps"&gt;SSH&lt;/span&gt; is the default option (remember the admin motto: &amp;#8220;in ssh we&amp;nbsp;trust&amp;#8221;).&lt;/p&gt;
&lt;h3 id="containerization"&gt;Containerization&lt;/h3&gt;
&lt;p&gt;Uncloud is built on &lt;em&gt;Docker&lt;/em&gt; : system services are containers, user services are containers too.
The &lt;span class="caps"&gt;API&lt;/span&gt; use &lt;em&gt;containerd&lt;/em&gt; for low level features (and it uses &lt;span class="caps"&gt;GRPC&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Images are not centralized; everything is pushed directly to the target via the &lt;a href="https://github.com/psviderski/unregistry"&gt;unregistry&lt;/a&gt;&amp;nbsp;subproject.&lt;/p&gt;
&lt;p&gt;Bypassing the registry is cheating.
Github and Gitlab provide a free registry, one of the steps to continuous deployment.
This is not the way.
Uncloud is an autonomist; no external services can be mandatory.
The temptation of streamlining the technical stack, removing moving parts, is the path to&amp;nbsp;robustness.&lt;/p&gt;
&lt;p&gt;Container configuration uses configuration files accessed from&amp;nbsp;volumes.&lt;/p&gt;
&lt;p&gt;Environment variables are not available yet, which is a crime according to&amp;nbsp;12Factors.&lt;/p&gt;
&lt;h3 id="private-network"&gt;Private&amp;nbsp;Network&lt;/h3&gt;
&lt;p&gt;Private network management on a Cloud is a&amp;nbsp;punishment.&lt;/p&gt;
&lt;p&gt;Uncloud picks Wireguard (https://www.wireguard.com/) for linking all servers, and &lt;em&gt;iptable&lt;/em&gt; for routing, just like Tailscale (https://tailscale.com/).
The obvious answer for kniting networks between scattered Linux&amp;nbsp;servers.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;IP&lt;/span&gt; are neatly arranged, with&amp;nbsp;subnetworks.&lt;/p&gt;
&lt;p&gt;The (eventualy) consistency is assumed by Corrosion (https://github.com/superfly/corrosion), a competitor to Consul, created by Fly.io for large&amp;nbsp;clusters.&lt;/p&gt;
&lt;p&gt;An internal &lt;span class="caps"&gt;DNS&lt;/span&gt; allows access to everything, following naming&amp;nbsp;conventions.&lt;/p&gt;
&lt;h3 id="ingress"&gt;Ingress&lt;/h3&gt;
&lt;p&gt;Caddy is the &lt;span class="caps"&gt;HTTP&lt;/span&gt;/&lt;span class="caps"&gt;HTTPS&lt;/span&gt; gateway.
It provides load balancing, dead node eviction, &lt;span class="caps"&gt;TLS&lt;/span&gt; certificates with Let&amp;#8217;s Encrypt, and &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3.&lt;/p&gt;
&lt;p&gt;Caddy is deployed on each node, leaving the public &lt;span class="caps"&gt;DNS&lt;/span&gt; to choose an entry&amp;nbsp;point.&lt;/p&gt;
&lt;h3 id="high-availability"&gt;High&amp;nbsp;Availability&lt;/h3&gt;
&lt;p&gt;Qorums (sufficient electors within the cluster) terrorize Uncloud.
User must be able to use Uncloud, even if the cluster is&amp;nbsp;broken.&lt;/p&gt;
&lt;p&gt;In the &lt;span class="caps"&gt;CAP&lt;/span&gt; theorem, Uncloud sacrifices &lt;em&gt;consistency&lt;/em&gt; (for &lt;em&gt;availability&lt;/em&gt; and &lt;em&gt;performance&lt;/em&gt;), hoping that data will reconcile once all cluster members are&amp;nbsp;reunited.&lt;/p&gt;
&lt;p&gt;This is an opinionated choice.
But, Uncloud clusters are very small, with explicit service placement, local &lt;span class="caps"&gt;IP&lt;/span&gt; are locally managed, and dead nodes are handled by an internal load&amp;nbsp;balancer.&lt;/p&gt;
&lt;p&gt;Without self-healing (spawning services instances on healthy nodes), handling node crash can be&amp;nbsp;minimalistic.&lt;/p&gt;
&lt;h3 id="critics"&gt;Critics&lt;/h3&gt;
&lt;p&gt;Uncloud assumes not to be production-ready,&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;Beta software can&amp;#8217;t be torn apart with criticism, it&amp;#8217;s not&amp;nbsp;fair.&lt;/p&gt;
&lt;p&gt;Uncloud is a (almost) one-man project, even if the developer is talented and maintains exemplary documentation.
For the perenity of his project, he&amp;#8217;ll have to look at both sides before crossing the&amp;nbsp;road.&lt;/p&gt;
&lt;p&gt;Uncloud is not concerned by Infrastructure as Code or &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;, by design.
You are a big boy; you can do &lt;span class="caps"&gt;IAC&lt;/span&gt;/&lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;, by yourself, Uncloud doesn&amp;#8217;t care.
Remember, it&amp;#8217;s a project targeting developers.
&lt;span class="caps"&gt;IAC&lt;/span&gt;/&lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; are great, but are seen as wasted time for a freelancer on solo projects.
This strategy is called &amp;#8220;one-man&amp;nbsp;deployment.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Cluster management is not dry yet.
Mixing Corrosion (a rust project), with &lt;span class="caps"&gt;IPFS&lt;/span&gt; &lt;span class="caps"&gt;CRDT&lt;/span&gt; storage, is weird.
Consul is the obvious answer for cluster management, but &lt;span class="caps"&gt;IBM&lt;/span&gt; is slowly strangling Hashicorp&amp;#8217;s products, what a pity.
Sharing states and groups with gossip is the first step, but what is the purpose of a cluster without self-healing ? a&amp;nbsp;federation?&lt;/p&gt;
&lt;p&gt;Fault tolerance relies on a (free) &lt;span class="caps"&gt;DNS&lt;/span&gt; service, which is a single point of failure (&lt;span class="caps"&gt;SPOF&lt;/span&gt;) that will cause problems for everyone due to time-to-live (&lt;span class="caps"&gt;TTL&lt;/span&gt;)&amp;nbsp;issues.&lt;/p&gt;
&lt;p&gt;Like all distributed hosting offerings that promise fault tolerance, the &lt;em&gt;persistence&lt;/em&gt; aspect is conveniently&amp;nbsp;avoided.&lt;/p&gt;
&lt;p&gt;Is persistence in the scope of a cluster application?
Maybe not, but it&amp;#8217;s mandatory for the&amp;nbsp;user.&lt;/p&gt;
&lt;p&gt;The documentation should give some&amp;nbsp;leads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redis Cluster for&amp;nbsp;sessions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/minio/minio"&gt;Minio&lt;/a&gt; for file&amp;nbsp;storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two services have the advantage of being largely self-sufficient.
 For databases, it&amp;#8217;s a different story.
 You either have to compromise with exotic databases like &lt;a href="https://github.com/superfly/litefs"&gt;Litefs&lt;/a&gt;, &lt;a href="https://www.scylladb.com/"&gt;Scylladb&lt;/a&gt;, or &lt;a href="https://github.com/apple/foundationdb"&gt;Foundationdb&lt;/a&gt;&amp;#8230; or handle primary/secondary replication of traditional relational databases (without any&amp;nbsp;magic).&lt;/p&gt;
&lt;p&gt;The user-centric approach to data persistence immediately shatters the &amp;#8220;elegant simplicity&amp;#8221; of&amp;nbsp;Uncloud.&lt;/p&gt;
&lt;p&gt;Before complaining, start by having a proper backup procedure, &lt;span class="caps"&gt;WITH&lt;/span&gt; &lt;span class="caps"&gt;RESTORE&lt;/span&gt; &lt;span class="caps"&gt;TESTING&lt;/span&gt;; that would be a good&amp;nbsp;start.&lt;/p&gt;
&lt;p&gt;Otherwise, claiming that Uncloud is virtuously sustainable simply because it&amp;#8217;s less resource-intensive than Kubernetes is rather&amp;nbsp;petty.&lt;/p&gt;
&lt;h2 id="the-aftermath"&gt;The&amp;nbsp;Aftermath&lt;/h2&gt;
&lt;p&gt;Uncloud already has the wow effect (and visibility), which is a major achievement for a solo developer.
Have a look at the &lt;a href="https://news.ycombinator.com/item?id=46144275"&gt;Hacker News thread&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Given its current strategy, Uncloud will likely generate limited revenue, essentially selling its technical&amp;nbsp;expertise.&lt;/p&gt;
&lt;p&gt;The choice of targeting &amp;#8220;solo developers, small projects, discount hosting&amp;#8221; also won&amp;#8217;t help monetization.
Communication agencies will love it, without giving a&amp;nbsp;dime.&lt;/p&gt;
&lt;p&gt;Please, Uncloud, give a more ambitious example than the infamous WordPress+Mariadb&amp;nbsp;combo.&lt;/p&gt;
&lt;p&gt;Therefore, it&amp;#8217;s essential to hope for some form of&amp;nbsp;sponsorship.&lt;/p&gt;
&lt;p&gt;In any case, anything that shakes things up and challenges admins, DevOps engineers, and other cloud worshippers is refreshing, even if the proposal ultimately falls flat (I hope&amp;nbsp;not).&lt;/p&gt;
&lt;p&gt;A project to watch and&amp;nbsp;encourage!&lt;/p&gt;</content><category term="Ops"></category><category term="docker"></category><category term="cloud"></category><category term="wireguard"></category><category term="caddy"></category><category term="cluster"></category></entry><entry><title>Uncloud, le cloud dégraissé par les devs</title><link href="http://blog.garambrogne.net/uncloud.html" rel="alternate"></link><published>2025-12-09T09:17:00+01:00</published><updated>2025-12-09T09:17:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-12-09:/uncloud.html</id><summary type="html">&lt;p&gt;Uncloud est un non-cloud prometteur taillé pour les développeurs pressés, mais qu&amp;#8217;y a-t-il sous le capot&amp;nbsp;?&lt;/p&gt;</summary><content type="html">&lt;h1 id="uncloud"&gt;Uncloud&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://uncloud.run/"&gt;Uncloud&lt;/a&gt; se présente comme un non-cloud un service ressemblant à &lt;a href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt; ou &lt;a href="https://render.com/"&gt;Render&lt;/a&gt;, mais aux sources ouvertes.
Le seul point commun que je vois dans ces références est la possibilité de bazarder des conteneurs en prod sans passer par la case adminsys, mais avec la promesse de la montée à l&amp;#8217;échelle, le Graal de toute bonne startup.
La comparaison avec des services vieillissants est étrange, et même peu flatteuse en vue de l&amp;#8217;ambition technique de&amp;nbsp;Uncloud.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;autre comparaison, l&amp;#8217;éléphant dans le couloir, est Kubernetes.
Uncloud se présente comme une deux chevaux face à K8s le semi remorque de 38&amp;nbsp;tonnes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Dendrolagus"&gt;&lt;img alt="Kangourou arboricole" class="image-process-article-image" src="/images/kangourou-arboricole.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="adminless"&gt;Adminless&lt;/h2&gt;
&lt;p&gt;Uncloud permet de gérer sa prod comme son poste de dev.
Cette phrase est terrifiante.
Mais, avec un peu de rigueur, cette approche est crédible, Uncloud assume une grosse partie du travail, avec le minimum de magie&amp;nbsp;vaudou.&lt;/p&gt;
&lt;p&gt;Si on vire les adminsys de l&amp;#8217;équation, il faut assumer la correction des incidents en ligne (et les mises à jour, et les sauvegardes, et la configuration fine bas niveau et plein d&amp;#8217;autres&amp;nbsp;surprises).&lt;/p&gt;
&lt;h2 id="scaleless"&gt;Scaleless&lt;/h2&gt;
&lt;p&gt;Uncloud est conçu pour ne pas se soucier de l&amp;#8217;hébergement, et même d&amp;#8217;avoir plusieurs hébergeurs, ou même de l&amp;#8217;auto-hébergement.
Il crée un grand un réseau virtuel chiffré (avec Wireguard), et tout le monde peut causer avec tout le monde, sur tous les&amp;nbsp;serveurs.&lt;/p&gt;
&lt;p&gt;Rappelons que le produit est australien, qui n&amp;#8217;ont ni des tonnes d&amp;#8217;utilisateurs locaux, ni une connexion réseau ultime avec le reste du monde.
Avoir des noeuds distribués proches des clients est plus important pour eux que pour un français qui a du mal à se projeter au dela de la métropole (et de l&amp;#8217;Europe pour les plus&amp;nbsp;ambitieux).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;auto-hébergement est honorable, tant qu&amp;#8217;on a la bande passante suffisante pour servir ses utilisateurs, mais l&amp;#8217;hébergement à pas cher est, euh, pas cher, et ne souffle pas h24 dans votre salon (ou un placard).
Mixer hébergement local et distant est difficilement justifiable, peut-être pour bencher votre jolie carte NVidia en proposant une indispensable fonctionnalité cloud sans vous ruiner en token (ou sans offrir vos données à la Silicon&amp;nbsp;Valley).&lt;/p&gt;
&lt;h2 id="brainless"&gt;Brainless&lt;/h2&gt;
&lt;p&gt;Uncloud expose une&amp;nbsp;commande, &lt;code&gt;uc&lt;/code&gt; qui permet d&amp;#8217;agir comme on le ferait avec &lt;em&gt;docker&lt;/em&gt;, et même d&amp;#8217;utiliser des docker-compose.yml (avec quelques informations&amp;nbsp;complémentaires).&lt;/p&gt;
&lt;p&gt;Pour ne pas avoir à gérer un registre de conteneur (ça sonne mieux en anglais : &lt;em&gt;container registry&lt;/em&gt;), ce qui peut effectivement être pénible, les images sont directement poussées sur la cible, en &lt;span class="caps"&gt;SSH&lt;/span&gt;, créant ainsi le&amp;nbsp;néologisme &lt;code&gt;pussh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Uncloud fournit gracieusement un &lt;span class="caps"&gt;DNS&lt;/span&gt;&amp;nbsp;en &lt;code&gt;*.cluster.uncloud.run&lt;/code&gt; qui avec un simple alias depuis votre nom de domaine vous permettra d&amp;#8217;avoir une résolution de nom en tourniquet (Round Robin &lt;span class="caps"&gt;DNS&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Lets-encrypt (via Caddy) fournit le certificat &lt;span class="caps"&gt;TLS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Toutes les interactions avec les serveurs passent par &lt;span class="caps"&gt;SSH&lt;/span&gt;, même le&amp;nbsp;débug.&lt;/p&gt;
&lt;p&gt;Les conteneurs sont déployés sur une machine explicite, et s&amp;#8217;il y a plusieurs instances d&amp;#8217;un service, une répartition de charge (load balancing) se met en place&amp;nbsp;spontanément.&lt;/p&gt;
&lt;p&gt;Le déploiement est promis sans interruption, la fameuse stratégie&amp;nbsp;bleu/vert.&lt;/p&gt;
&lt;p&gt;Le réseau privé évite qu&amp;#8217;un service soit exposé par accident sur Internet (seuls les ports &lt;span class="caps"&gt;SSH&lt;/span&gt;, &lt;span class="caps"&gt;HTTP&lt;/span&gt; et &lt;span class="caps"&gt;HTTPS&lt;/span&gt; sont&amp;nbsp;accessibles).&lt;/p&gt;
&lt;p&gt;La procédure de déploiement est donc&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chez moi ça&amp;nbsp;marche&lt;/li&gt;
&lt;li&gt;Pousse en&amp;nbsp;prod&lt;/li&gt;
&lt;li&gt;Si ça pète, on a tout de suite les logs pour comprendre la cause du&amp;nbsp;oups&lt;/li&gt;
&lt;li&gt;Correction à&amp;nbsp;l&amp;#8217;arrache&lt;/li&gt;
&lt;li&gt;Repousse&lt;/li&gt;
&lt;li&gt;Pause au&amp;nbsp;babyfoot&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dissection"&gt;Dissection&lt;/h2&gt;
&lt;p&gt;Les promesses de la plaquette sont bien belles, mais qu&amp;#8217;en est-il techniquement&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Un des concepts de bases, si ce n&amp;#8217;est l&amp;#8217;unique, est la décentralisation, tous les noeuds fournissent le même service &lt;em&gt;uncloudd&lt;/em&gt;, sans spécialisation, sans&amp;nbsp;chef.&lt;/p&gt;
&lt;p&gt;Uncloud n&amp;#8217;utilise pas de configurations, et promet de ne consommer que &lt;span class="caps"&gt;150MB&lt;/span&gt; de &lt;span class="caps"&gt;RAM&lt;/span&gt; par&amp;nbsp;noeud. &lt;/p&gt;
&lt;h3 id="api-reseau"&gt;&lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;réseau&lt;/h3&gt;
&lt;p&gt;Tous les services sont en &lt;a href="https://grpc.io/"&gt;grpc&lt;/a&gt;, avec la passerelle &lt;span class="caps"&gt;REST&lt;/span&gt; officielle : &lt;a href="https://github.com/grpc-ecosystem/grpc-gateway"&gt;grpc-gateway&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le client utilise une connexion &lt;em&gt;ssh&lt;/em&gt; pour causer en &lt;em&gt;grpc&lt;/em&gt;, ce qui simplifie la gestion des&amp;nbsp;droits. &lt;/p&gt;
&lt;h3 id="conteneurisation"&gt;Conteneurisation&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Docker&lt;/em&gt; est évidement la brique de base de ce non-cloud, avec en interne des appels directement à &lt;em&gt;Containerd&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Les images ne sont pas centralisées, tout est poussé directement sur la cible, via le sous projet &lt;a href="https://github.com/psviderski/unregistry"&gt;unregistry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ce petit sacrilège est bien tentant pour simplifier une pile technique, même si Github/Gitlab fournit des registry&amp;nbsp;privées.&lt;/p&gt;
&lt;p&gt;La configuration des conteneurs se fait par des fichiers de conf accédés depuis des volumes.
Pour l&amp;#8217;instant la configuration via les variables d&amp;#8217;environnement n&amp;#8217;est pas disponible, ce qui est un crime selon &lt;a href="https://12factor.net/"&gt;12 factors&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="reseau-prive"&gt;Réseau&amp;nbsp;privé&lt;/h3&gt;
&lt;p&gt;La gestion du réseau privé est l&amp;#8217;élément le plus complexe de la pile&amp;nbsp;technique.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.wireguard.com/"&gt;Wireguard&lt;/a&gt; permet de relier les différents noeuds, avec des sous réseaux bien rangés, et du routage.
Même choix technique que &lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt;, gage de&amp;nbsp;sérieux.&lt;/p&gt;
&lt;p&gt;La coordination des différents noeuds du réseau passe par &lt;a href="https://github.com/superfly/corrosion"&gt;Corrosion&lt;/a&gt;, un concurrent de Consul, crée par Fly.io.
Écrit en Rust (what else ?) il expose une base de données &lt;em&gt;sqlite&lt;/em&gt; avec la possibilité de s&amp;#8217;abonner à une requête &lt;span class="caps"&gt;SQL&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Un &lt;span class="caps"&gt;DNS&lt;/span&gt; interne permet d&amp;#8217;accéder à tout, en suivant des conventions de&amp;nbsp;nommage.&lt;/p&gt;
&lt;p&gt;La persistance de la config réseau utilise &lt;a href="https://github.com/ipfs/go-ds-crdt"&gt;le datastore &lt;span class="caps"&gt;CRDT&lt;/span&gt; d&amp;#8217;&lt;span class="caps"&gt;IPFS&lt;/span&gt;&lt;/a&gt;, même si Corrosion propose la même chose et que d&amp;#8217;autres pistes sont&amp;nbsp;explorées.&lt;/p&gt;
&lt;h3 id="ingress"&gt;Ingress&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt; a été choisi comme passerelle &lt;span class="caps"&gt;HTTP&lt;/span&gt;/&lt;span class="caps"&gt;HTTPS&lt;/span&gt;, même si &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt; est aussi évoqué dans le wiki.
Il prend en charge le load balancing, l&amp;#8217;éviction des noeuds morts, les certificats &lt;span class="caps"&gt;TLS&lt;/span&gt; avec Letsencrypt et &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3.&lt;/p&gt;
&lt;p&gt;Caddy est déployé sur chacun des noeuds, laissant au &lt;span class="caps"&gt;DNS&lt;/span&gt; (public) le choix d&amp;#8217;un point&amp;nbsp;d&amp;#8217;entrée.&lt;/p&gt;
&lt;h3 id="haute-disponibilite"&gt;Haute&amp;nbsp;disponibilité&lt;/h3&gt;
&lt;p&gt;Uncloud est terrorisé par les quorums (suffisamment d&amp;#8217;électeurs au sein du cluster) qui pourraient empêcher la modification de l&amp;#8217;état du&amp;nbsp;cluster.&lt;/p&gt;
&lt;p&gt;Dans le théorème &lt;span class="caps"&gt;CAP&lt;/span&gt;, il sacrifie le C, la cohérence des données, et prie pour que les données vont se réconcilier une fois que tous les membres du cluster sont de nouveaux&amp;nbsp;réunis.&lt;/p&gt;
&lt;p&gt;Sur le papier, ça ressemble à un choix de lapin apeuré.
Concrètement, les clusters Uncloud seront de taille ridicule, avec des services déployés explicitement sur des noeuds n&amp;#8217;utilisant que très peu les instances multiples avec répartition de&amp;nbsp;charge.&lt;/p&gt;
&lt;p&gt;Tant que le projet Uncloud n&amp;#8217;a pas fait le ménage dans la partie gossip et état distribué, il n&amp;#8217;a aucune légitimité à faire de pareilles&amp;nbsp;affirmations.&lt;/p&gt;
&lt;p&gt;La notion de haute disponibilité de Uncloud est un peu une blague : il ne sait pas re-ventiler les services d&amp;#8217;un noeud qui a crashé ou en lancer de nouveau pour accompagner une montée en&amp;nbsp;charge.&lt;/p&gt;
&lt;h3 id="critiques"&gt;Critiques&lt;/h3&gt;
&lt;p&gt;Uncloud n&amp;#8217;est officiellement pas prêt pour de la prod, ce qui limite la possibilté de le défoncer à coups de&amp;nbsp;critiques.&lt;/p&gt;
&lt;p&gt;Uncloud est le projet d&amp;#8217;un seul homme (one man project), même s&amp;#8217;il est talentueux et maintient une documentation exemplaire.
Il va falloir qu&amp;#8217;il regarde bien des deux cotés avant de travers la&amp;nbsp;route.&lt;/p&gt;
&lt;p&gt;Les sources contiennent des paramètres spécifiques à la machine du développeur, même si ce n&amp;#8217;est qu&amp;#8217;un détail.
Le déploiement des services est sauvage (il pousse d&amp;#8217;office un binaire docker sur les&amp;nbsp;noeuds).&lt;/p&gt;
&lt;p&gt;La notion d&amp;#8217;Infra As Code ou de &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; ne semble pas l&amp;#8217;intéresser, mais c&amp;#8217;est un raccourci tentant pour un freelance qui ne fait que des missions en solo.
On peut parler de &amp;#8220;one man&amp;nbsp;deployment&amp;#8221;.&lt;/p&gt;
&lt;p&gt;La notion de multitenant ou même d&amp;#8217;isolation de différents projets ne paraissent pas effleurer le développeur.
C&amp;#8217;est un challenge intéressant pour la couche&amp;nbsp;réseau.&lt;/p&gt;
&lt;p&gt;La tolérance de panne passe par un service &lt;span class="caps"&gt;DNS&lt;/span&gt; (gratis) est un &lt;span class="caps"&gt;SPOF&lt;/span&gt; qui va embêter tout le monde avec les temps de diffusion (&lt;span class="caps"&gt;TTL&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Comme toutes les offres d&amp;#8217;hébergement distribué avec promesse de tolérance de panne, la partie &lt;em&gt;persistance&lt;/em&gt; est planquée sous le tapis.
Il faudrait au moins évoqué dans la documentation l&amp;#8217;intérêt de Redis-cluster pour les sessions, Minio pour le stockage de fichiers.
Ces deux services ont le bon gout de se débrouiller à peu près tout seul.
Pour les bases de données, ce n&amp;#8217;est pas la même histoire, il faut soit faire des compromis avec des bases exotiques : &lt;a href="https://github.com/superfly/litefs"&gt;litefs&lt;/a&gt;, &lt;a href="https://www.scylladb.com/"&gt;scylladb&lt;/a&gt;, &lt;a href="https://github.com/apple/foundationdb"&gt;foundatoindb&lt;/a&gt; &amp;#8230; ou assumer la réplication primaire/secondaire des classiques base de données&amp;nbsp;relationnelles.&lt;/p&gt;
&lt;p&gt;La gestion crédible (à la charge de l&amp;#8217;utilisateur) de la persistance casse tout de suite l&amp;#8217;ambiance &amp;#8220;élégance de la simplicité&amp;#8221;&amp;nbsp;d&amp;#8217;Uncloud.&lt;/p&gt;
&lt;p&gt;Avant de râler, commencez par avoir une procédure de sauvegarde d&amp;#8217;équerre, &lt;span class="caps"&gt;AVEC&lt;/span&gt; &lt;span class="caps"&gt;TEST&lt;/span&gt; &lt;span class="caps"&gt;DE&lt;/span&gt; &lt;span class="caps"&gt;RESTAURATION&lt;/span&gt;, ce sera déjà pas&amp;nbsp;mal.&lt;/p&gt;
&lt;p&gt;Sinon, affirmer qu&amp;#8217;Uncloud est vertueusement soutenable par ce que moins orgiaque que Kubernetes est un peu&amp;nbsp;mesquin.&lt;/p&gt;
&lt;h2 id="lapres"&gt;L&amp;#8217;après&lt;/h2&gt;
&lt;p&gt;Uncloud bénéficie déjà d&amp;#8217;une belle visibilité et réputation, ce qui est une grande victoire pour un développeur&amp;nbsp;seul.&lt;/p&gt;
&lt;p&gt;Par ses choix, Uncloud ne pourra générer que peu de revenus, juste vendre de l&amp;#8217;expertise technique.
Le choix de la cible &amp;#8220;développeur solo, petits projets, hébergement discount&amp;#8221; ne va pas non plus aider à ramener des sous, même si Uncloud risque de plaire aux agences de&amp;nbsp;communication.&lt;/p&gt;
&lt;p&gt;Il faut donc lui souhaiter du sponsoring, sous une forme ou une autre, et cibler des projets un poil plus ambitieux que l&amp;#8217;infâme Wordpress+Mariadb proposé dans les&amp;nbsp;exemples.&lt;/p&gt;
&lt;p&gt;Il y a pour l&amp;#8217;instant des parties mal définies qui rend le projet un peu brouillon.
Ces zones floues seront rapidement corrigées avec une dose de &lt;span class="caps"&gt;RTFM&lt;/span&gt; et d&amp;#8217;aide&amp;nbsp;extérieure.&lt;/p&gt;
&lt;p&gt;De toute façon, tout ce qui secoue et remet en cause les admins, devops et autre idolâtre du cloud est rafraichissant, même si la proposition se&amp;nbsp;vautre.&lt;/p&gt;
&lt;p&gt;À suivre et à encourager&amp;nbsp;!&lt;/p&gt;</content><category term="Ops"></category><category term="docker"></category><category term="cloud"></category><category term="wireguard"></category><category term="caddy"></category><category term="cluster"></category></entry><entry><title>Packages proxies</title><link href="http://blog.garambrogne.net/packages-proxies-en.html" rel="alternate"></link><published>2025-07-15T09:04:00+02:00</published><updated>2025-07-15T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-07-15:/packages-proxies-en.html</id><summary type="html">&lt;p&gt;Cache all your package&amp;nbsp;registries.&lt;/p&gt;</summary><content type="html">&lt;h2 id="packages-store"&gt;Packages&amp;nbsp;store&lt;/h2&gt;
&lt;p&gt;All package repositories are technically similar and unfancy.
Technical boredom is a guarantee of&amp;nbsp;stability.&lt;/p&gt;
&lt;p&gt;On a website, packages are indexed per release, per version, per architecture (and sometimes per group, like &amp;#8220;security&amp;#8221; or&amp;nbsp;&amp;#8220;backport&amp;#8221;).&lt;/p&gt;
&lt;p&gt;Lots of technical details are explained in the previous blog post (sorry for my&amp;nbsp;French).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lac Léman vu depuis Thonon-les-bains" class="image-process-article-image" src="/images/lac-leman-thonon.jpg" /&gt;&lt;/p&gt;
&lt;h3 id="faster-closer-and-more-efficient"&gt;Faster, closer, and more&amp;nbsp;efficient&lt;/h3&gt;
&lt;p&gt;If you manage a large float of servers or a busy continuous integration service, you have to cache package repositories (to avoid banishment, throttling, or bad&amp;nbsp;karma).&lt;/p&gt;
&lt;p&gt;Package integrity is handled behind the distribution channel with signatures.
At least, it should be&amp;nbsp;so.&lt;/p&gt;
&lt;p&gt;Distribution channel (&lt;span class="caps"&gt;HTTP&lt;/span&gt; most of the time) is not a security&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Old-school providers sign (with a trusted public key) the index of the packages (including their&amp;nbsp;hashes).&lt;/p&gt;
&lt;p&gt;The (almost) universal signing tool for open-source code is &lt;a href="https://www.sigstore.dev/"&gt;Sigstore&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With signatures, package integrity is guaranteed, so caching or mirroring is&amp;nbsp;safe.&lt;/p&gt;
&lt;p&gt;Repositories can be mirrored, but beware of the synchronization&amp;nbsp;periodicity.&lt;/p&gt;
&lt;p&gt;Some package providers handle their own mirrors, with explicit and implicit GeoDNS and cache proxy. They can broadcast updates to minimize mirror&amp;nbsp;lags.&lt;/p&gt;
&lt;h3 id="bring-your-own-cache"&gt;Bring your own&amp;nbsp;cache&lt;/h3&gt;
&lt;p&gt;Cache can be fancy, with a lot of features and complexity, or crude, just a plain old &lt;span class="caps"&gt;HTTP&lt;/span&gt; cache&amp;nbsp;proxy.&lt;/p&gt;
&lt;h4 id="fancy-caches"&gt;Fancy&amp;nbsp;caches&lt;/h4&gt;
&lt;p&gt;Here is a short selection of specialized&amp;nbsp;proxies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/EpicWink/proxpi"&gt;Proxpi&lt;/a&gt; for&amp;nbsp;Python&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/goproxy/goproxy"&gt;Goproxy&lt;/a&gt; or &lt;a href="https://github.com/gomods/athens"&gt;Athens&lt;/a&gt; for&amp;nbsp;Golang&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PierreRambaud/gemirro"&gt;Gemirro&lt;/a&gt; for&amp;nbsp;Ruby&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/panamax-rs/panamax"&gt;Panamax&lt;/a&gt; for&amp;nbsp;Rustlang&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some specialized proxies handle different kinds of packages, like &lt;a href="https://pulpproject.org/"&gt;Pulp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/soulteary/apt-proxy"&gt;apt-proxy&lt;/a&gt; (which also handles yum and apk) is designed to handle flappy Internet connections and find the best mirror (behind&amp;nbsp;it).&lt;/p&gt;
&lt;h4 id="old-school-caches"&gt;Old school&amp;nbsp;caches&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.squid-cache.org/"&gt;Squid&lt;/a&gt; is boring; lets use &lt;a href="https://docs.nginx.com/nginx/admin-guide/content-cache/content-caching/"&gt;Nginx cache&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-proxy-cache-for-your-repositories"&gt;A proxy cache for your&amp;nbsp;repositories&lt;/h3&gt;
&lt;p&gt;Dinosaurs remember the time when a simple HTTP_PROXY environment variable plugged a proxy in front of the target&amp;nbsp;site.&lt;/p&gt;
&lt;p&gt;Proxy only works with &lt;span class="caps"&gt;HTTP&lt;/span&gt; servers. &lt;span class="caps"&gt;HTTPS&lt;/span&gt; encrypts the connection, which can&amp;#8217;t be&amp;nbsp;cached.&lt;/p&gt;
&lt;p&gt;Now, all repositories use &lt;span class="caps"&gt;HTTPS&lt;/span&gt; by default now (kudos to &lt;a href="https://www.eff.org/https-everywhere"&gt;&lt;span class="caps"&gt;SSL&lt;/span&gt; Everywhere&lt;/a&gt;).
The proxy must be a &amp;#8220;man in the middle&amp;#8221;, and intercept the&amp;nbsp;queries.&lt;/p&gt;
&lt;p&gt;Each kind of package must be configured&amp;nbsp;specificallyy.&lt;/p&gt;
&lt;p&gt;Setting a proxy for all package families can be done with a config file.
It&amp;#8217;s explicit, but not&amp;nbsp;universal.&lt;/p&gt;
&lt;p&gt;Developers and &lt;span class="caps"&gt;CI&lt;/span&gt; can use different private&amp;nbsp;caches.&lt;/p&gt;
&lt;p&gt;Some package manager can be configured with few &lt;span class="caps"&gt;ENV&lt;/span&gt;; for the others, a mix of file configuration and &lt;span class="caps"&gt;ENV&lt;/span&gt; can do the&amp;nbsp;trick.&lt;/p&gt;
&lt;h4 id="demo-time"&gt;Demo&amp;nbsp;time&lt;/h4&gt;
&lt;p&gt;One cache to rule them&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;Setting a private &lt;span class="caps"&gt;DNS&lt;/span&gt; and &lt;span class="caps"&gt;TLS&lt;/span&gt; is boring; life is short.
The easiest and laziest way is to mix local and containerized development
with bare &lt;span class="caps"&gt;IP&lt;/span&gt; and containerized cache&amp;nbsp;server.&lt;/p&gt;
&lt;p&gt;Please don&amp;#8217;t do that ia real production&amp;nbsp;environment.&lt;/p&gt;
&lt;p&gt;Caching for few package families are explained later in this blog&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;This examples use Docker to build&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;The adaptation for local development is&amp;nbsp;trivial.&lt;/p&gt;
&lt;p&gt;Docker has&amp;nbsp;the &lt;code&gt;ONBUILD&lt;/code&gt; command in its Dockerfilen very useful for setting variable in the build&amp;nbsp;process.&lt;/p&gt;
&lt;p&gt;The easiest way to build image which can use cache is to build an image atop the main language&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;The layers of the cake&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;base&amp;nbsp;image&lt;/li&gt;
&lt;li&gt;cachable&amp;nbsp;image&lt;/li&gt;
&lt;li&gt;project&amp;nbsp;image&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="nginx-cache"&gt;Nginx&amp;nbsp;cache&lt;/h5&gt;
&lt;p&gt;The official Nginx image doesn&amp;#8217;t have&amp;nbsp;the &lt;code&gt;subs-filter&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;Lets build a new image with&amp;nbsp;it.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--build-arg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ENABLED_MODULES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;subs-filter&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;nginx-subs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;https://raw.githubusercontent.com/nginx/docker-nginx/refs/heads/master/mainline/alpine/Dockerfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each cache packages families use path route, and when it&amp;#8217;s impossible, it uses hostname route (the client is configured to use the cache as a&amp;nbsp;proxy).&lt;/p&gt;
&lt;p&gt;Nginx configuration is minimalistic, tune it if you&amp;nbsp;wish.&lt;/p&gt;
&lt;p&gt;Pick your favorite resolver, I use 193.110.81.0 (dns0.eu), 8.8.8.8 (google) is &amp;#8220;déjà&amp;nbsp;vue&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Cache size needs your attention, set a correct value, nor to small, nor to&amp;nbsp;huge.&lt;/p&gt;
&lt;p&gt;One server, one port, no&amp;nbsp;hostname.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;worker_rlimit_nofile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;worker_connections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;log_format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$request&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$bytes_sent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;bytes&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$upstream_addr&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$http_location&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$http_user_agent&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;error_log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/dev/stdout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/dev/stdout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;resolver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;193&lt;/span&gt;&lt;span class="s"&gt;.110.81.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# dns0.eu&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;default_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;tcp_nopush&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_names_hash_bucket_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/data/cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;keys_zone=fat_cache:10m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;max_size=1g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;inactive=60m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;use_temp_path=off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The server section is empty,&amp;nbsp;some &lt;code&gt;location&lt;/code&gt; will be added&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Pick a private interface face, and its &lt;span class="caps"&gt;IP&lt;/span&gt; for publishing the cache service.
The cache server needs to be available from containers and your&amp;nbsp;workstation.&lt;/p&gt;
&lt;p&gt;The ip is stored as SERVER_IP&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ip&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;inet&lt;span class="w"&gt; &lt;/span&gt;addr&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;docker0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;inet &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s#.*inet (.*)/.*#\1#g&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or something more dirty with&amp;nbsp;MacOS, &lt;code&gt;docker0&lt;/code&gt; is inside the &lt;span class="caps"&gt;VM&lt;/span&gt;, so, the &lt;span class="caps"&gt;IP&lt;/span&gt; of the laptop is used (check your firewall settings,&amp;nbsp;first).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ifconfig&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;en0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;inet &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can now run the cache server (with its&amp;nbsp;SERVER_IP).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;data/cache
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/data/cache:/data/cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/nginx.conf:/etc/nginx/nginx.conf:ro&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SERVER_IP&lt;span class="k"&gt;)&lt;/span&gt;:8082:80&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;nginx-subs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Linux, you should use an user with your id, cache owned by root is&amp;nbsp;unorthodox.&lt;/p&gt;
&lt;h5 id="debian-and-ubuntu"&gt;Debian (and&amp;nbsp;Ubuntu)&lt;/h5&gt;
&lt;p&gt;Debian uses URLs starting&amp;nbsp;with &lt;code&gt;/debian&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;^/debian(.*)$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://deb.debian.org/debian&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cache&amp;nbsp;image.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:bookworm-slim&lt;/span&gt;

&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APT_MIRROR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$APT_MIRROR&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;No mirror&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Acquire::http::Proxy \&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$APT_MIRROR&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/apt.conf.d/cache.conf&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.debian-mirror&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;deb-mirror&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Debian demo imagee&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deb-mirror&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;--no-install-suggests&lt;span class="w"&gt; &lt;/span&gt;--no-install-recommends&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;cowsay&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;/var/lib/apt/lists/*

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cowsay&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Through the Looking-Glass&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build it , with a specific cache&amp;nbsp;server.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.debian&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;debian-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--build-arg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APT_MIRROR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://192.168.1.35:8082/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see a flow of package URLs in the terminal with Nginix&amp;nbsp;Cache.&lt;/p&gt;
&lt;p&gt;Run it&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;debian-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Ubuntu variant needs only few&amp;nbsp;modifications.&lt;/p&gt;
&lt;p&gt;Ubuntu doesn&amp;#8217;t use prefixed URLs;the hostnameme must be&amp;nbsp;used.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;^(.*)\.ubuntu.com$;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$1.ubuntu.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="alpine"&gt;Alpine&lt;/h5&gt;
&lt;p&gt;Nginx&amp;nbsp;conf:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/alpine/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://dl-cdn.alpinelinux.org/alpine/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:latest&lt;/span&gt;

&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HTTP_PROXY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$HTTP_PROXY&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;No mirror&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/https/http/g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;/etc/apk/repositories&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is&amp;nbsp;trick, &lt;code&gt;$HTTP_PROXY&lt;/code&gt; will be used&amp;nbsp;by &lt;code&gt;apk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Build cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.alpine-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;alpine-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Demo&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine-with-cache&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apk&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--no-cache&lt;span class="w"&gt; &lt;/span&gt;figlet

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;figlet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Carpe diem&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build demo&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.alpine&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;alpine-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--build-arg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HTTP_PROXY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SERVER_IP&lt;span class="k"&gt;)&lt;/span&gt;:8082&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run&amp;nbsp;demo:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;alpine-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="pypi"&gt;Pypi&lt;/h5&gt;
&lt;p&gt;Nginx conf for&amp;nbsp;pypi:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;^/pypi/(.*)$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://pypi.org/simple/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_protocols&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;TLSv1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_session_reuse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;pypi.org&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pip&lt;/code&gt; can use an index without https, but the host needs to be&amp;nbsp;trusted.&lt;/p&gt;
&lt;p&gt;Cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.13-slim&lt;/span&gt;

&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PYPI_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$PYPI_CACHE&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;No mirror&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[global]\n\&lt;/span&gt;
&lt;span class="s2"&gt;        index-url = http://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PYPI_CACHE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pypi\n\&lt;/span&gt;
&lt;span class="s2"&gt;        trusted-host = &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PYPI_CACHE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/pip.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.python-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;python-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Demo&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python-with-cache&lt;/span&gt;

&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;/demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/demo/bin/pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cowsay

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/demo/bin/cowsay&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tux&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-t&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\&amp;quot;Welcome to the thunder dome\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build&amp;nbsp;example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;python-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--build-arg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PYPI_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SERVER_IP&lt;span class="k"&gt;)&lt;/span&gt;:8082&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run&amp;nbsp;example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;python-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="npm"&gt;npm&lt;/h5&gt;
&lt;p&gt;Nginx&amp;nbsp;conf:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;registry.npmjs.org&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://registry.npmjs.org/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_protocols&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;TLSv1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_session_reuse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_ssl_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;registry.npmjs.org&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:24-alpine&lt;/span&gt;

&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NPM_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;ONBUILD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$NPM_CACHE&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;No mirror&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NPM_CACHE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--location&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https-proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NPM_CACHE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--location&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;registry&lt;span class="w"&gt; &lt;/span&gt;http://registry.npmjs.org/&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build cache&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.npm-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;node-with-cache&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Demo&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node-with-cache&lt;/span&gt;

&lt;span class="c"&gt;# npm should be somewhere, not /&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/opt/demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/opt/demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cowsay

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/opt/demo/node_modules/.bin/cowsay&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-e&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xx&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\&amp;quot;With a little help from my friends\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build demo&amp;nbsp;image:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Dockerfile.node&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;node-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--build-arg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NPM_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SERVER_IP&lt;span class="k"&gt;)&lt;/span&gt;:8082&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run&amp;nbsp;demo:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;node-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="docker"&gt;Docker&lt;/h5&gt;
&lt;p&gt;Docker daemon can use a &lt;a href="https://docs.docker.com/docker-hub/image-library/mirror/"&gt;mirror&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker Hub, now, has quota; if you don&amp;#8217;t want to be banned, use a&amp;nbsp;mirror.&lt;/p&gt;
&lt;p&gt;A private registry is mandatory to deploy your&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;All registries can be used as a proxy cache for another public or private&amp;nbsp;registry.&lt;/p&gt;
&lt;p&gt;Harbor, graduated by &lt;a href="https://www.cncf.io/"&gt;&lt;span class="caps"&gt;CNCF&lt;/span&gt;&lt;/a&gt;, like all registries, can be a &lt;a href="https://goharbor.io/docs/2.4.0/administration/configure-proxy-cache/"&gt;proxy&amp;nbsp;cache&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using nginx for caching Docker Hub should not be done in production, but it&amp;#8217;s fun to cache everyting with one&amp;nbsp;server.&lt;/p&gt;
&lt;p&gt;Docker daemon&amp;nbsp;configuration.&lt;/p&gt;
&lt;p&gt;Add this line in&amp;nbsp;the &lt;code&gt;daemon.json&lt;/code&gt; config&amp;nbsp;file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;quot;registry-mirrors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://_server_ip_:8082/docker/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Linux the path&amp;nbsp;is &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;, but, before breaking something, &lt;span class="caps"&gt;RTFM&lt;/span&gt; the &lt;a href="https://docs.docker.com/reference/cli/dockerd/#daemon-configuration-file"&gt;Docker configuration file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With Docker Desktop (&lt;span class="caps"&gt;OSX&lt;/span&gt; or Windows), the configuration is in the tab &amp;#8220;Docker&amp;nbsp;Engine.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Docker can use a containerized mirror, but pull and build the image &lt;span class="caps"&gt;BEFORE&lt;/span&gt; using&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Nginx&amp;nbsp;conf:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/docker/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;rewrite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;^/docker/(.*)&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;fat_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_background_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://registry.hub.docker.com/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="github-demo-project"&gt;Github Demo&amp;nbsp;Project&lt;/h5&gt;
&lt;p&gt;Copy pasting is boring;, the &lt;a href="https://github.com/athoune/packages-cache"&gt;package-cache&lt;/a&gt; contains all the files cited in this post, with an&amp;nbsp;useful &lt;code&gt;Makefile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Split your terminal (with tmux maybe), and build and run the cache&amp;nbsp;server.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;cache
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run all&amp;nbsp;demos:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For the Docker cache demo, you have to tweak your Docker daemon configuration and pull a fresh image, not one already cached in your local&amp;nbsp;registry.&lt;/p&gt;
&lt;h3 id="cache-all-the-things"&gt;Cache all the&amp;nbsp;things&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;CI&lt;/span&gt; can share a private local cache between&amp;nbsp;steps.&lt;/p&gt;
&lt;p&gt;Useful, but this cache is not shared (cache poisoning is very&amp;nbsp;dangerous).&lt;/p&gt;
&lt;p&gt;The cache proxy is safe, the user can&amp;#8217;t write data (and poison it), and the cache is shared between all project&amp;nbsp;builds.&lt;/p&gt;
&lt;p&gt;Have fun with Nginx demo, but sooner or later, you will use specifics&amp;nbsp;caches.&lt;/p&gt;</content><category term="Ops"></category><category term="proxy"></category><category term="package"></category><category term="nginx"></category></entry><entry><title>Infomaniak et le chiffrage des mails</title><link href="http://blog.garambrogne.net/infomaniak-securite-mail.html" rel="alternate"></link><published>2025-06-18T15:15:00+02:00</published><updated>2025-06-18T15:15:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-06-18:/infomaniak-securite-mail.html</id><summary type="html">&lt;p&gt;Comment sécuriser les échanges de mails, et est-ce même possible&amp;nbsp;?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Infomaniak, vous savez, les charmants Suisses qui ont (a priori) repris le karma du vendeur de &lt;span class="caps"&gt;DNS&lt;/span&gt; à Gandi (époque Chemla), ils ont aussi une offre mail construite, pas juste un Roundcube de&amp;nbsp;politesse.&lt;/p&gt;
&lt;p&gt;Ils viennent d&amp;#8217;annoncer une évolution de leur &lt;a href="https://www.infomaniak.com/fr/support/faq/1582/securiser-un-envoi-de-mail-par-cle-de-chiffrement-sur-lapp-web-mail-infomaniak"&gt;offre pour plus de sécurité&lt;/a&gt; : il y a maintenant un petit bouton cadenas dans l&amp;#8217;interface du&amp;nbsp;webmail.&lt;/p&gt;
&lt;p&gt;Toutes les démarches pour améliorer la sécurité et la confidentialité sont les&amp;nbsp;bienvenues.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cadenas agressant le pont des arts" class="image-process-article-image" src="/images/cadenas.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="ma-legitimite-sur-lanalyse"&gt;Ma légitimité sur&amp;nbsp;l&amp;#8217;analyse&lt;/h2&gt;
&lt;p&gt;Disclaimer, j&amp;#8217;ai l&amp;#8217;expertise en sécurité d&amp;#8217;un lecteur de Wikipedia, de &lt;span class="caps"&gt;RFC&lt;/span&gt; et de la presse people qui rapportent les plus beaux &lt;span class="caps"&gt;CVE&lt;/span&gt;.
Jamais je n&amp;#8217;assurerai que quoi que ce soit est à l&amp;#8217;épreuve des balles.
C&amp;#8217;est un métier et clairement pas le mien.
Par contre, si je vois un trou dans la coque, je peux affirmer qu&amp;#8217;il y&amp;nbsp;est.&lt;/p&gt;
&lt;h2 id="gpg"&gt;&lt;span class="caps"&gt;GPG&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Donc, Infomaniak utilise &lt;span class="caps"&gt;GPG&lt;/span&gt;, standard de fait (et une des plus belles déceptions&amp;nbsp;d&amp;#8217;Internet).&lt;/p&gt;
&lt;p&gt;Techniquement, &lt;span class="caps"&gt;GPG&lt;/span&gt; utilise les mêmes algorithmes de clefs asymétriques que &lt;span class="caps"&gt;TLS&lt;/span&gt; ou &lt;span class="caps"&gt;SSH&lt;/span&gt;. Ça part sur de bonnes&amp;nbsp;bases.&lt;/p&gt;
&lt;p&gt;Les distributions Linux l&amp;#8217;utilisent pour signer leurs paquets, git permet aussi de la faire, bref, c&amp;#8217;est dans l&amp;#8217;écosystème libre depuis&amp;nbsp;longtemps.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;SAUF&lt;/span&gt; &lt;span class="caps"&gt;QUE&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Le chiffrage asymétrique ne sait comment exposer la clef publique des utilisateurs en garantissant qu&amp;#8217;il n&amp;#8217;est pas un&amp;nbsp;usurpateur.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;SSH&lt;/span&gt; utilise par défaut le naïf &lt;a href="https://en.wikipedia.org/wiki/Trust_on_first_use"&gt;Trust on first use&lt;/a&gt;, mais peut utiliser des clefs signées par une autorité de&amp;nbsp;certification.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;GPG&lt;/span&gt; a fait le choix utopiste (niais ?) de ne rien centraliser (bon choix), mais d&amp;#8217;utiliser une chaine de confiance.
Un utilisateur peut signer la clef d&amp;#8217;autres utilisateurs qui pourront faire de même et ainsi former une toile d&amp;#8217;araignée de confiance.
Comment savoir que la clef que l&amp;#8217;on signe n&amp;#8217;est pas une fourberie ?
Comment savoir qu&amp;#8217;une personne qui a déjà signé est digne de confiance&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Bref, le &amp;#8220;web of trust&amp;#8221; est une fausse bonne idée (comme &lt;a href="https://en.wikipedia.org/wiki/CRAM-MD5"&gt;&lt;span class="caps"&gt;CRAM&lt;/span&gt;-&lt;span class="caps"&gt;MD5&lt;/span&gt;&lt;/a&gt; par&amp;nbsp;exemple)&lt;/p&gt;
&lt;p&gt;Pour que &lt;span class="caps"&gt;GPG&lt;/span&gt; fonctionne, il faut pouvoir prouver que la clef appartient bien à la bonne&amp;nbsp;personne.&lt;/p&gt;
&lt;p&gt;Pour les paquets, c&amp;#8217;est tout simple : le trousseau des clefs légitimes est disponibles quelque part en &lt;span class="caps"&gt;TLS&lt;/span&gt;. La confiance est déléguée au &lt;span class="caps"&gt;DNS&lt;/span&gt; et à l&amp;#8217;autorité qui a signé le certificat (comme Let&amp;#8217;s&amp;nbsp;Encrypt).&lt;/p&gt;
&lt;p&gt;Pour des utilisateurs lambdas, c&amp;#8217;est un poil plus compliqué, &lt;a href="https://en.wikipedia.org/wiki/Keybase"&gt;Keybase&lt;/a&gt; propose de légitimer une clef en prouvant que l&amp;#8217;on est bien possesseur de différents services (de bonnes réputations) sur Internet (un site web, Github, Twitter, Hackernews, Reddit…).
Petit souci, c&amp;#8217;est un freeware racheté par Zoom (pour rafistoler sa réputation de passoire) et seulement 400k utilisateurs&amp;nbsp;l&amp;#8217;utilisent.&lt;/p&gt;
&lt;p&gt;Il existe &lt;a href="https://wiki.gnupg.org/WKD"&gt;Web Key Directory&lt;/a&gt; une &lt;a href="https://linuxfr.org/users/gouttegd/journaux/deployer-un-service-d-annuaire-de-clefs-openpgp-pour-son-domaine"&gt;solution à base&amp;nbsp;de &lt;code&gt;.well-known&lt;/code&gt; sur un serveur web&lt;/a&gt;.
Il n&amp;#8217;a pas sa page Wikipedia, ce qui est une faute de gout.
Il faut que je l&amp;#8217;essaye avant de le&amp;nbsp;dauber.&lt;/p&gt;
&lt;p&gt;De toute façon, le crime originel de &lt;span class="caps"&gt;GPG&lt;/span&gt; pour les mails est qu&amp;#8217;il ne chiffre que le corps du mail. Le reste est en clair, comme l&amp;#8217;expéditeur, le destinataire, par où il est passé. Sans ça, pas moyen d&amp;#8217;acheminer le mail.
Pour une utilisation crédible, il faut utiliser des comptes mails avec des pseudos, des &lt;span class="caps"&gt;SMTP&lt;/span&gt; les plus banaux possibles et des titres cryptiques, et échanger correctement les clefs publiques. Bon courage aux lanceurs d&amp;#8217;alerte non&amp;nbsp;techniques.&lt;/p&gt;
&lt;h2 id="smime"&gt;S/&lt;span class="caps"&gt;MIME&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Il existe une autre norme, &lt;a href="https://fr.wikipedia.org/wiki/S/MIME"&gt;S/&lt;span class="caps"&gt;MIME&lt;/span&gt;&lt;/a&gt; qui mise tout sur &lt;span class="caps"&gt;TLS&lt;/span&gt;.
Peut-être qu&amp;#8217;un jour Let&amp;#8217;s Encrypt proposera de signer des clefs pour S/&lt;span class="caps"&gt;MIME&lt;/span&gt; qui est souvent implémenté dans les clients&amp;nbsp;mails.&lt;/p&gt;
&lt;p&gt;S/&lt;span class="caps"&gt;MIME&lt;/span&gt; ne va pas pouvoir faire mieux que &lt;span class="caps"&gt;GPG&lt;/span&gt;, et ne chiffrera que le corps du&amp;nbsp;mail.&lt;/p&gt;
&lt;h2 id="webmail"&gt;Webmail&lt;/h2&gt;
&lt;p&gt;Nouveau&amp;nbsp;drame.&lt;/p&gt;
&lt;p&gt;Les mails sont souvent utilisés en webmail.
Comment fournir et stocker sa clef privée ?
Techniquement, javascript sait &lt;a href="https://fetch.spec.whatwg.org/#url"&gt;lire un fichier local&lt;/a&gt;, gérer du &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto"&gt;chiffrage&lt;/a&gt; et il existe &lt;a href="https://openpgpjs.org/"&gt;Openpgpjs&lt;/a&gt; (soutenu par l&amp;#8217;Union&amp;nbsp;Européenne).&lt;/p&gt;
&lt;p&gt;Mais, le site, qui héberge ce code Javascript peut être un fourbe (ou compromis), et on va lui confier sa clef&amp;nbsp;privée.&lt;/p&gt;
&lt;p&gt;Clef que l&amp;#8217;on transporte comment ?
Sur une clef &lt;span class="caps"&gt;USB&lt;/span&gt;, dans ce cas, autant utiliser &lt;a href="https://tails.net/"&gt;Tails&lt;/a&gt; (si la machine peut booter sur une&amp;nbsp;clef).&lt;/p&gt;
&lt;h2 id="infomaniak"&gt;Infomaniak&lt;/h2&gt;
&lt;p&gt;Implémentation par Infomaniak&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La clef est chez l&amp;#8217;hébergeur (stocké de manière sécurisée) mais accessible par&amp;nbsp;eux.&lt;/li&gt;
&lt;li&gt;Si on écrit un mail à un client Infomaniak, la clef publique est directement utilisée (avec&amp;nbsp;confiance).&lt;/li&gt;
&lt;li&gt;Pour écrire à un utilisateur externe, il faut se démerder pour lui faire passer un mot de&amp;nbsp;passe.&lt;/li&gt;
&lt;li&gt;La recherche ne fonctionne plus que sur les titres (comme vu plus&amp;nbsp;haut).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J&amp;#8217;ai de gros doutes sur la crédibilité de ce petit cadenas bleu en bas de la&amp;nbsp;page.&lt;/p&gt;
&lt;h2 id="et-au-dela"&gt;Et&amp;nbsp;au-delà&lt;/h2&gt;
&lt;p&gt;Le mail est à la fois indispensable (car universel), et maudit.
La confidentialité intégrale est techniquement impossible.
Plus de sécurité, comme la signature des mails (par le serveur avec &lt;span class="caps"&gt;DKIM&lt;/span&gt;, par l&amp;#8217;utilisateur avec &lt;span class="caps"&gt;GPG&lt;/span&gt; ou S/&lt;span class="caps"&gt;MIME&lt;/span&gt;), ou le &lt;span class="caps"&gt;TLS&lt;/span&gt; sur tout le chemin sont&amp;nbsp;possibles.&lt;/p&gt;
&lt;p&gt;La confiance dans l&amp;#8217;hébergeur est&amp;nbsp;incontournable.&lt;/p&gt;
&lt;p&gt;Il y a deux points bloquants pour atteindre le meilleur : les applications mails (dont les webmails) qui ne gère pas forcément toutes les fonctionnalités possibles et l&amp;#8217;ergonomie pour les&amp;nbsp;utilisateurs.&lt;/p&gt;
&lt;p&gt;Le plus simple est d&amp;#8217;utiliser &lt;a href="https://github.com/signalapp"&gt;Signal&lt;/a&gt;, même si l&amp;#8217;&lt;a href="https://support.signal.org/hc/fr/articles/360007060632-Qu-est-ce-que-le-num%C3%A9ro-de-s%C3%A9curit%C3%A9-et-pourquoi-a-t-il-chang%C3%A9"&gt;authentification de son interlocuteur&lt;/a&gt; n&amp;#8217;est pas magique&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;Il faut fournir un code, via un QRcode par exemple, ou en faisant une vision pour vérifier qu&amp;#8217;il a la bonne&amp;nbsp;tête.&lt;/p&gt;
&lt;p&gt;Prenez soin de vous, amis&amp;nbsp;paranos.&lt;/p&gt;</content><category term="Ops"></category><category term="mail"></category><category term="gpg"></category><category term="webmail"></category></entry><entry><title>Kloset sur la table de dissection</title><link href="http://blog.garambrogne.net/kloset.html" rel="alternate"></link><published>2025-06-11T09:04:00+02:00</published><updated>2025-06-11T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-06-11:/kloset.html</id><summary type="html">&lt;p&gt;Dédoublonner son archivage, ça change du combo tgz+&lt;span class="caps"&gt;FTP&lt;/span&gt;. Kloset, fils de Plakar, vous dévoile ses&amp;nbsp;secrets.&lt;/p&gt;</summary><content type="html">&lt;h2 id="un-plakar-pour-ranger-ses-archives"&gt;Un Plakar pour ranger ses&amp;nbsp;archives&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://plakar.io/"&gt;Plakar&lt;/a&gt; est un outil de sauvegarde multi-plateforme et open source.
Basé sur les technologies et les usages contemporains : il utilise la déduplication, la compression et met la priorité sur le chiffrement et la signature (intégrité de l&amp;#8217;archive et&amp;nbsp;authenticité).&lt;/p&gt;
&lt;p&gt;Le code de Plakar, en golang, est découpé entre plusieurs bibliothèques que l&amp;#8217;on peut utiliser de manière granulaire.
Plakar est l&amp;#8217;application qui va tirer toutes ses bibliothèques pour proposer une &lt;span class="caps"&gt;UI&lt;/span&gt;&amp;nbsp;élégante.&lt;/p&gt;
&lt;h2 id="kloset"&gt;Kloset&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/PlakarKorp/kloset"&gt;Kloset&lt;/a&gt; (qui m&amp;#8217;évoque la martyre de &lt;a href="https://fr.wikipedia.org/wiki/Th%C3%A9nardier"&gt;Thénardier&lt;/a&gt;) s&amp;#8217;occupe des archives, c&amp;#8217;est cette partie que je souhaite&amp;nbsp;disséquer.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hamster" class="image-process-article-image" src="/images/hamster.jpg" title="Un hamster dans sa roue" /&gt;&lt;/p&gt;
&lt;h3 id="theorie"&gt;Théorie&lt;/h3&gt;
&lt;h4 id="decouper-en-tranches"&gt;Découper en&amp;nbsp;tranches&lt;/h4&gt;
&lt;p&gt;Le document est découpé en &lt;strong&gt;tranches&lt;/strong&gt; (&lt;em&gt;chunks&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;), avec l&amp;#8217;espoir d&amp;#8217;avoir des tranches en commun avec d&amp;#8217;autres&amp;nbsp;fichiers.&lt;/p&gt;
&lt;p&gt;Si le format de fichier ne secoue pas tout chaque fois qu&amp;#8217;il enregistre, il est fort probable d&amp;#8217;avoir des duplicas.
C&amp;#8217;est le pari de &lt;em&gt;git&lt;/em&gt; qui travaille à partir de &lt;em&gt;patch&lt;/em&gt;.
Cette approche fonctionne très bien avec des fichiers plats (tant qu&amp;#8217;il n&amp;#8217;y a pas de guerre vaine de&amp;nbsp;formatage).&lt;/p&gt;
&lt;h5 id="limiter-les-transferts"&gt;Limiter les&amp;nbsp;transferts&lt;/h5&gt;
&lt;p&gt;Même avec une connexion moderne, il est toujours appréciable de limiter la bande passante utilisée lors d&amp;#8217;un transfert de données.
À l&amp;#8217;ère du NVMe, la sauvegarde sur un disque à plateau (plus pérenne à long terme) va vous paraitre&amp;nbsp;mollassonne.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;outil de référence (et historique) de synchronisation incrémentale est &lt;strong&gt;rsync&lt;/strong&gt;, enfin, &lt;a href="https://github.com/librsync/librsync"&gt;librsync&lt;/a&gt;.
L&amp;#8217;explication de l&amp;#8217;algorithme (simple et élégant) est décrit dans l&amp;#8217;article originel &lt;a href="https://www.andrew.cmu.edu/course/15-749/READINGS/required/cas/tridgell96.pdf"&gt;The rsync algorithm&lt;/a&gt; de&amp;nbsp;1996.&lt;/p&gt;
&lt;p&gt;Il est agréablement&amp;nbsp;lisible.&lt;/p&gt;
&lt;p&gt;Le fichier &lt;strong&gt;cible&lt;/strong&gt; est découpé en blocs de taille fixe (des &lt;em&gt;chunks&lt;/em&gt;), dont on calcule deux &lt;em&gt;hashs&lt;/em&gt;.
Le premier &lt;em&gt;hash&lt;/em&gt;, dit &amp;#8220;faible&amp;#8221;, peu couteux à calculer, aide à trouver des similarités, le second, dit &amp;#8220;fort&amp;#8221;, garanti que deux &lt;em&gt;chunks&lt;/em&gt; sont&amp;nbsp;identiques.&lt;/p&gt;
&lt;p&gt;La &lt;strong&gt;cible&lt;/strong&gt; fournit cette liste à la &lt;strong&gt;source&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="explication large"&gt;
&lt;p&gt;Le premier hachage est techniquement une &amp;#8220;somme de contrôle&amp;#8221; (&lt;em&gt;checksum&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;), précis, rapide à calculer mais avec des risques de faux-positifs (des&amp;nbsp;collisions).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée est d&amp;#8217;utiliser un &lt;a href="https://en.wikipedia.org/wiki/Rolling_hash"&gt;&lt;em&gt;rolling hash&lt;/em&gt;&lt;/a&gt; peu couteux à calculer, car il réutilise le calcul précédant (en &amp;#8220;faisant rouler&amp;#8221; la fenêtre de &lt;em&gt;hash&lt;/em&gt; d&amp;#8217;un octet).
Le &lt;em&gt;rolling hash&lt;/em&gt; peut décaler les &lt;em&gt;chunks&lt;/em&gt; et créer des &amp;#8220;trous&amp;#8221; dans la liste décrivant le fichier&amp;nbsp;local.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rolling Checksum" class="right gif" src="/images/rolling.gif" /&gt;
Si un &lt;em&gt;rolling checksum&lt;/em&gt; est dans la liste des &lt;em&gt;hashs&lt;/em&gt; distants, bingo, il ne sera pas nécessaire de l&amp;#8217;envoyer.
Le second hachage est dit fort (le risque de collision est considéré comme improbable) et va garantir que les &lt;em&gt;chunks&lt;/em&gt; sont&amp;nbsp;identiques.&lt;/p&gt;
&lt;p&gt;Il suffit d&amp;#8217;envoyer la liste des &lt;em&gt;chunks&lt;/em&gt; qui ont changé de place, les octets à ajouter ou effacer, et un &lt;em&gt;hash&lt;/em&gt; pour vérifier la cohérence du fichier&amp;nbsp;modifié.&lt;/p&gt;
&lt;p&gt;Un &lt;em&gt;patch&lt;/em&gt; peut être créé avec cet algorithme, en local, pour fournir des mises à jour de&amp;nbsp;logiciel.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Cette approche est élégante, mais le fichier cible doit être non chiffré pour pouvoir appliquer les modifications sur la cible.
Techniquement, il est possible d&amp;#8217;avoir le fichier cible chiffré tant qu&amp;#8217;il fournit la liste de ses &lt;em&gt;hashs&lt;/em&gt;, par contre, ils seront stockés en l&amp;#8217;état sur la cible.
Le moment où l&amp;#8217;on souhaitera effacer les anciennes versions pour faire de la place va être&amp;nbsp;compliqué.&lt;/p&gt;
&lt;p&gt;Le temps de calcul va dépendre (de manière non linéaire) de la taille du fichier, bon courage pour les vidéos et les images&amp;nbsp;disques.&lt;/p&gt;
&lt;h5 id="deduplication-par-le-chunk"&gt;Déduplication par le &lt;em&gt;chunk&lt;/em&gt;&lt;/h5&gt;
&lt;p&gt;Les &lt;em&gt;chunks&lt;/em&gt; peuvent être utilisés de manières&amp;nbsp;différentes.&lt;/p&gt;
&lt;p&gt;En décrivant les fichiers avec des listes des &lt;em&gt;chunks&lt;/em&gt;, sans &amp;#8220;fenêtre roulante&amp;#8221;, la création de &lt;em&gt;patch&lt;/em&gt; minimaliste n&amp;#8217;est plus possible.
Par contre, en travaillant sur un volume conséquent de fichiers (en To), il y aura statistiquement de doublons, des &lt;em&gt;chunks&lt;/em&gt; communs à plusieurs fichiers : le Graal du&amp;nbsp;dédoublonnage.&lt;/p&gt;
&lt;p&gt;La déduplication est fort pratique pour la sauvegarde, mais elle est aussi déployée pour le stockage distant, qualifié de &amp;#8220;synchronisation&amp;#8221;, comme le propose Dropbox, Google Drive et tant d&amp;#8217;autre.
&lt;a href="https://github.com/haiwen/seafile"&gt;Seafile&lt;/a&gt; est un équivalent open source, même s&amp;#8217;il a &lt;a href="https://github.com/haiwen/seafile/graphs/contributors"&gt;un coup de mou depuis 2016&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Historiquement, les morceaux découpés avaient une taille fixe, plus simple, moins couteux en &lt;span class="caps"&gt;CPU&lt;/span&gt;, mais limitant les chances d&amp;#8217;avoir des&amp;nbsp;doublons.&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://www.researchgate.net/publication/340391934_The_Design_of_Fast_Content-Defined_Chunking_for_Data_Deduplication_Based_Storage_Systems"&gt;Content-Defined Chunking décrits dans ce papier&lt;/a&gt; (&amp;#8220;Découpage basé sur le contenu&amp;#8221; dans la langue de Pivot) propose d&amp;#8217;utiliser des morceaux de tailles variables (dans une fourchette imposée) pour augmenter le taux de&amp;nbsp;duplicas.&lt;/p&gt;
&lt;p&gt;Hugginface est enthousiaste sur le dédoublonnage permettant un &lt;a href="https://huggingface.co/blog/from-files-to-chunks"&gt;gain en place et bande passante pour ses modèles monstrueux&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="optimiser-le-debit"&gt;Optimiser le&amp;nbsp;débit&lt;/h4&gt;
&lt;p&gt;Le meilleur moyen d&amp;#8217;optimiser le débit d&amp;#8217;un transfert et de ne pas transférer.
Pour ça, il faut maximiser la chance que le &lt;em&gt;chunk&lt;/em&gt; soit déjà sur place, un duplica.
Les petits &lt;em&gt;chunks&lt;/em&gt; ont plus de chance d&amp;#8217;avoir des duplicas, mais trop de &lt;em&gt;chunks&lt;/em&gt; sont plus lourds à indexer : les &lt;em&gt;chunks&lt;/em&gt; sont regroupés dans un fichier de taille raisonnable, il faut donc maintenir un index pour savoir dans quel fichier se trouve un &lt;em&gt;chunk&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://huggingface.co/blog/from-chunks-to-blocks"&gt;Hugginface explique ses optimisations pour partager ses données&lt;/a&gt;.
Il possède, à l&amp;#8217;époque de la rédaction de leur billet, 45Po de données découpées en &lt;em&gt;chunks&lt;/em&gt; de 64ko, soit 690 milliards de &lt;em&gt;chunks&lt;/em&gt;.
Pour un fichier de 200Go, taille normale chez eux, il faut 3.125 millions de &lt;em&gt;chunks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pour téléverser un fichier, il faut le découper en &lt;em&gt;chunks&lt;/em&gt;, et pour chacun vérifier qu&amp;#8217;il n&amp;#8217;est pas déjà sur le serveur via un filtre de Bloom : on prend le &lt;em&gt;hash&lt;/em&gt; du &lt;em&gt;chunk&lt;/em&gt;, modulo 1000, et hop, on connait le nom de l&amp;#8217;index à télécharger qui peut contenir le nom du &lt;em&gt;chunk&lt;/em&gt;.
Les &lt;em&gt;chunks&lt;/em&gt; sont regroupés en bloc de 64Mo, et envoyés dans un ordre&amp;nbsp;quelconque.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;archivage se fait sur un disque dur, les &lt;strong&gt;&lt;span class="caps"&gt;SSD&lt;/span&gt;&lt;/strong&gt;/&lt;strong&gt;NVMe&lt;/strong&gt; restent hors de prix pour les gros volumes.
Le &lt;strong&gt;&lt;span class="caps"&gt;HDD&lt;/span&gt;&lt;/strong&gt;, lui continue sa course à la densité, même si le débit ne suit&amp;nbsp;pas.&lt;/p&gt;
&lt;p&gt;En dispersant les &lt;em&gt;chunks&lt;/em&gt; sur un disque dur, les performances en lecture ne sont pas optimales, les lectures séquentielles sont bien plus&amp;nbsp;performantes.&lt;/p&gt;
&lt;p&gt;Les écritures sont regroupées en plus gros blocs, plus simple à optimiser pour le système de&amp;nbsp;fichier.&lt;/p&gt;
&lt;p&gt;Les fabricants de disques durs font de gros efforts pour conjuguer densité et&amp;nbsp;débit.&lt;/p&gt;
&lt;p&gt;Le cache distant (celui du disque, ou d&amp;#8217;un &lt;span class="caps"&gt;SSD&lt;/span&gt; en renfort), et local (les duplicas) amélioreront grandement les performances en téléchargement.
La restauration complète d&amp;#8217;une sauvegarde, sans cache local, avec peu de chance que les chunks soient dans le cache distant, est le cas le plus&amp;nbsp;défavorable.&lt;/p&gt;
&lt;p&gt;Tout ça n&amp;#8217;est que théorie, il faut des tests de charge pour valider/invalider ces&amp;nbsp;hypothèses.&lt;/p&gt;
&lt;p&gt;Le bus interne (&lt;span class="caps"&gt;SATA&lt;/span&gt;/&lt;span class="caps"&gt;SAS&lt;/span&gt;), le processeur, la bande passante de l&amp;#8217;hébergeur, et fort probablement la bande passante de l&amp;#8217;utilisateur peuvent être des goulots&amp;nbsp;d&amp;#8217;étranglement.&lt;/p&gt;
&lt;p&gt;Hugginface, est dans un cas bien plus favorable, ses modèles en best-seller profitent pleinement d&amp;#8217;un cache en &lt;span class="caps"&gt;SSD&lt;/span&gt;/NVMe.&lt;/p&gt;
&lt;h4 id="entropie-dun-binaire"&gt;Entropie d&amp;#8217;un&amp;nbsp;binaire&lt;/h4&gt;
&lt;p&gt;L&amp;#8217;entropie mesure le côté aléatoire d&amp;#8217;un binaire, l&amp;#8217;absence de motifs répétés (comme des séries de 1 ou de 0).
Les binaires pleins de trous ont même un nom, les &lt;a href="https://en.wikipedia.org/wiki/Sparse_file"&gt;sparse files&lt;/a&gt; et les systèmes de fichiers savent bien les gérer.
Une faible entropie va perturber le processus de découpage basé sur le contenu qui créera des &lt;em&gt;chunks&lt;/em&gt; rares, qui n&amp;#8217;auront que peu de duplicas.
Les images disques de machine virtuelle sont un bon exemple de &lt;em&gt;sparse file&lt;/em&gt; en représentant l&amp;#8217;espace disponible de longues suites de&amp;nbsp;0.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article &lt;a href="https://cs.uwaterloo.ca/~alkiswan/papers/LowEntropy_CLOUD24.pdf"&gt;The Impact of Low-Entropy on Chunking Techniques for Data Deduplication&lt;/a&gt; explique ça avec&amp;nbsp;précision.&lt;/p&gt;
&lt;h4 id="traiter-les-donnees-depuis-la-cible"&gt;Traiter les données depuis la&amp;nbsp;cible&lt;/h4&gt;
&lt;p&gt;Traditionnellement, les &lt;em&gt;chunks&lt;/em&gt; ne sont pas chiffrés, ce qui permet d&amp;#8217;agir depuis la&amp;nbsp;cible.&lt;/p&gt;
&lt;p&gt;Redécouper les &lt;em&gt;chunks&lt;/em&gt; pour chaque client, en fonction de la qualité de leur connexion (latence et pertes de paquets) permettra une utilisation fluide sur un réseau de qualité médiocre (ce qui arrive régulièrement sur les réseaux de téléphonie&amp;nbsp;mobile).&lt;/p&gt;
&lt;p&gt;Indexer le contenu permettra aux utilisateurs de chercher dans leur fatras de documents en ligne.
Dropbox vous propose de &lt;a href="https://dropbox.tech/machine-learning/how-image-search-works-at-dropbox"&gt;chercher vos images&lt;/a&gt; (&lt;span class="caps"&gt;OCR&lt;/span&gt; et description par une &lt;span class="caps"&gt;IA&lt;/span&gt;) mais aussi de bannir le &lt;a href="https://www.wired.com/story/dropbox-dmca-position/"&gt;contenu protégé par le droit d&amp;#8217;auteur&lt;/a&gt; (initialement à base de hachages sur liste noire) et finalement de faire &lt;a href="https://gizmodo.com/dropbox-refuses-to-explain-its-mysterious-child-porn-de-1722573363"&gt;la chasse aux pédonazis&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="chiffrement"&gt;chiffrement&lt;/h4&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Oui, mais moi, je n&amp;#8217;ai rien à cacher&amp;#8221;, comme l&amp;#8217;affirme &lt;a href="https://fr.wikipedia.org/wiki/Echelon"&gt;le projet &lt;span class="caps"&gt;ECHELON&lt;/span&gt;&lt;/a&gt;.
Depuis la révélation de ce projet, &lt;a href="https://www.eff.org/encrypt-the-web"&gt;le chiffrement s&amp;#8217;est généralisé sur Internet&lt;/a&gt;.
Il n&amp;#8217;y a aucune raison de ne pas chiffrer son stockage en ligne (dans le Nuage&amp;nbsp;?).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cryptomator.org/"&gt;Cryptomator&lt;/a&gt; se positionne comme intermédiaire local entre vous et un stockage distant.
C&amp;#8217;est gentil de leur part, mais l&amp;#8217;un des objectifs du chiffrement est de transformer vos données en ce qui semble être un amas d&amp;#8217;octets complétement aléatoire.
Cette bouillie d&amp;#8217;octets est le pire cas pour le &lt;em&gt;chunking&lt;/em&gt; et le&amp;nbsp;dédoublonnage.&lt;/p&gt;
&lt;p&gt;Le chiffrement doit se faire &lt;strong&gt;après&lt;/strong&gt; le découpage des &lt;em&gt;chunks&lt;/em&gt;, à la source et la cible ne doit avoir aucun moyen de lire le&amp;nbsp;contenu.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Barrage_de_Bissorte"&gt;&lt;img alt="Lac de Bissorte" src="/images/lac-de-bissorte.jpg" title="Lac de Bissorte" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="implementation-de-kloset"&gt;Implémentation de&amp;nbsp;Kloset&lt;/h3&gt;
&lt;p&gt;Comme le laisse supposer les précédentes explications théoriques, Kloset va chunker, vérifier, authentifier, dédoublonner, compresser et&amp;nbsp;chiffrer.&lt;/p&gt;
&lt;p&gt;Son objectif est la sobriété (stockage et bande passante), la résilience et la&amp;nbsp;sécurité.&lt;/p&gt;
&lt;p&gt;Ces explications, bien que denses, se contentent de vulgariser les choix techniques de Kloset, pour avoir une connaissance exhaustive, il faut lire le&amp;nbsp;code.&lt;/p&gt;
&lt;h4 id="chungking-express"&gt;Chungking&amp;nbsp;Express&lt;/h4&gt;
&lt;p&gt;Le jeu de mot avec &lt;a href="https://www.rottentomatoes.com/m/chungking_express"&gt;le film&lt;/a&gt; était trop tentant, même si c&amp;#8217;est loin d&amp;#8217;être le meilleur de ce&amp;nbsp;réalisateur.&lt;/p&gt;
&lt;p&gt;Kloset utilise du &lt;span class="caps"&gt;CDC&lt;/span&gt; (des &lt;em&gt;chunk&lt;/em&gt; de taille variables) et comme il n&amp;#8217;y a aucun &lt;em&gt;hash&lt;/em&gt; à rouler, un seul, fort, est utilisé.
Un &lt;em&gt;chunk&lt;/em&gt;, et bien d&amp;#8217;autres objets sont désignés par leur&amp;nbsp;hachage.&lt;/p&gt;
&lt;h4 id="clef-maitresse"&gt;Clef&amp;nbsp;maîtresse&lt;/h4&gt;
&lt;p&gt;Toute la sécurité de Kloset part du mot de passe de l&amp;#8217;utilisateur (qui est validé par diverses contraintes de sécurité lors de sa&amp;nbsp;création).&lt;/p&gt;
&lt;p&gt;À la création du &lt;strong&gt;dépôt&lt;/strong&gt;, un bloc de 32 octets aléatoire est créé, il sera le &lt;strong&gt;sel&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Le &lt;strong&gt;mot de passe&lt;/strong&gt; et le &lt;strong&gt;sel&lt;/strong&gt; vont générer une &lt;em&gt;master key&lt;/em&gt; (de 32 octets)  avec une &lt;a href="https://en.wikipedia.org/wiki/Key_derivation_function"&gt;fonction de dérivation de clef&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ce hachage est volontairement compliqué et lent (même pour un &lt;span class="caps"&gt;GPU&lt;/span&gt;) pour ralentir les attaques en force brute.
&lt;a href="https://en.wikipedia.org/wiki/Argon2"&gt;Argon2&lt;/a&gt; a été choisi (gagnant du concours de &lt;em&gt;hash&lt;/em&gt; de 2015 quand&amp;nbsp;même).&lt;/p&gt;
&lt;h4 id="mac"&gt;&lt;span class="caps"&gt;MAC&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;Il n&amp;#8217;est pas tout à fait vrai de dire que Kloset utilise des &lt;em&gt;hash&lt;/em&gt; pour vérifier l&amp;#8217;intégrité de ses contenus.
Pour être précis, il faut parler de &lt;a href="https://en.wikipedia.org/wiki/HMAC"&gt;&lt;span class="caps"&gt;HMAC&lt;/span&gt;&lt;/a&gt;, sur-ensemble d&amp;#8217;un &lt;em&gt;hash&lt;/em&gt;, qui permet de vérifier en plus l&amp;#8217;authenticité avec un &lt;strong&gt;secret&lt;/strong&gt;.
N&amp;#8217;importe qui peut modifier le document et fournir un &lt;em&gt;hash&lt;/em&gt; valide, mais seul les personnes connaissant le &lt;strong&gt;secret&lt;/strong&gt; peuvent signer et authentifier.
Le&amp;nbsp;terme &lt;code&gt;MAC&lt;/code&gt; est utilisé dans le code pour désigner les &lt;span class="caps"&gt;HMAC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;La &lt;strong&gt;clef maitresse&lt;/strong&gt; est utilisée comme &lt;strong&gt;secret&lt;/strong&gt; pour les différents &lt;span class="caps"&gt;HMAC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Pour de meilleures performances le hachage n&amp;#8217;est pas forcément confié au standard &lt;span class="caps"&gt;SHA&lt;/span&gt;-256, mais utilise par défaut &lt;a href="https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3"&gt;&lt;span class="caps"&gt;BLAKE3&lt;/span&gt;&lt;/a&gt;, très rapide et facilement parallélisable (&lt;span class="caps"&gt;SIMD&lt;/span&gt; et&amp;nbsp;multi-threads).&lt;/p&gt;
&lt;p&gt;Ce &lt;code&gt;MAC&lt;/code&gt; sert de clef pour désigner les différents objets du&amp;nbsp;projet.&lt;/p&gt;
&lt;h4 id="chunk-et-packfile"&gt;Chunk et&amp;nbsp;PackFile&lt;/h4&gt;
&lt;p&gt;Kloset considère que tout est un blob binaire, référencé par&amp;nbsp;un &lt;code&gt;Chunk&lt;/code&gt;.
Les blobs et&amp;nbsp;leur &lt;code&gt;Chunk&lt;/code&gt; sont entassés dans&amp;nbsp;un &lt;code&gt;Packfile&lt;/code&gt; qui, lui, sera enregistré sur un disque dur, un S3, quelque&amp;nbsp;part.&lt;/p&gt;
&lt;p&gt;Un &lt;code&gt;PackFile&lt;/code&gt; se compose de trois parties.
D&amp;#8217;abord une écriture en flot (sans tout charger en mémoire) d&amp;#8217;une suite de blobs.
Ensuite viennent les métadonnées (de taille fixe) pour chacun des blobs, consolidées en &lt;span class="caps"&gt;RAM&lt;/span&gt; (28o de méta par bloc, c&amp;#8217;est plus que raisonnable).&amp;nbsp;Le &lt;code&gt;PackFile&lt;/code&gt; se termine par un &lt;em&gt;footer&lt;/em&gt;, de taille fixe (28o aussi) qui contient, entre autre, l&amp;#8217;&lt;em&gt;offset&lt;/em&gt; de&amp;nbsp;l&amp;#8217;index.&lt;/p&gt;
&lt;p&gt;Les &lt;code&gt;Packfile&lt;/code&gt; ont une taille maximale.
Si l&amp;#8217;on a besoin de plus de place, il suffit d&amp;#8217;en créer un&amp;nbsp;nouveau.&lt;/p&gt;
&lt;div class="explication large"&gt;
&lt;p&gt;&lt;img alt="PackFile" class="gif" src="/images/PackFile.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Pour lire&amp;nbsp;un &lt;code&gt;Packfile&lt;/code&gt;, il suffit d&amp;#8217;aller 32 octets avant la fin à la fin du fichier, lire&amp;nbsp;le &lt;code&gt;Footer&lt;/code&gt; (en bleu), qui décrit l&amp;#8217;emplacement de&amp;nbsp;l&amp;#8217;&lt;code&gt;Index&lt;/code&gt; (en jaune).
Entre le début du fichier et&amp;nbsp;l&amp;#8217;&lt;code&gt;Index&lt;/code&gt; se trouve la zone&amp;nbsp;de &lt;code&gt;Blobs&lt;/code&gt; contenant de manière indistinct (mais séquentiel) des blocs de contenu (en rose).
Dans&amp;nbsp;l&amp;#8217;&lt;code&gt;Index&lt;/code&gt;, tous les 28 octets,&amp;nbsp;un &lt;code&gt;Blob&lt;/code&gt; est décrit.&amp;nbsp;Chaque &lt;code&gt;Blob&lt;/code&gt; décrit l&amp;#8217;emplacement d&amp;#8217;un bloc de contenu.
Il est possible de lire partiellement l&amp;#8217;index si les méta-datas contenu dans&amp;nbsp;le &lt;code&gt;Blob&lt;/code&gt; correspond à sa recherche.
Une fois&amp;nbsp;le &lt;code&gt;Blob&lt;/code&gt; sélectionné, il suffit d&amp;#8217;aller le&amp;nbsp;lire.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;En théorie&amp;nbsp;un &lt;code&gt;Packfile&lt;/code&gt; peut contenir n&amp;#8217;importe quels binaires typés (jusqu&amp;#8217;à 2^32 types différents).
Kloset s&amp;#8217;en contente d&amp;#8217;une&amp;nbsp;vingtaine.&lt;/p&gt;
&lt;p&gt;Comme tous les différents objets sont désignés par des &lt;em&gt;hash&lt;/em&gt;,&amp;nbsp;les &lt;code&gt;Chunk&lt;/code&gt; vont se retrouver ventilés dans de&amp;nbsp;multiples &lt;code&gt;Packfiles&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Il est techniquement possible d&amp;#8217;itérer dans tous&amp;nbsp;vos &lt;code&gt;Packfile&lt;/code&gt; pour trouver&amp;nbsp;un &lt;code&gt;MAC&lt;/code&gt; (une clef) précis, mais un confortable index permet de connaitre, rapidement, pour chaque objet&amp;nbsp;son &lt;code&gt;Packfile&lt;/code&gt;, son &lt;em&gt;offset&lt;/em&gt;, sa taille et son&amp;nbsp;type.&lt;/p&gt;
&lt;h4 id="entry"&gt;Entry&lt;/h4&gt;
&lt;p&gt;Kloset sauvegarde des fichiers, pas uniquement des données binaires&amp;nbsp;abstraites.&lt;/p&gt;
&lt;p&gt;Le fichier est modélisé par&amp;nbsp;un &lt;code&gt;Entry&lt;/code&gt; (participant ?), sérialisable, qui va contenir toutes les méta-informations spécifiques à l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; (&lt;span class="caps"&gt;UNIX&lt;/span&gt;, Windows), et les fichiers spéciaux (dossiers, liens symboliques, liens durs) en plus des méta-données liées à&amp;nbsp;l&amp;#8217;archivage.&lt;/p&gt;
&lt;p&gt;Une &lt;code&gt;Entry&lt;/code&gt; contient moult méta-données, et une référence à&amp;nbsp;son &lt;code&gt;Object&lt;/code&gt; listant des&amp;nbsp;références &lt;code&gt;Chunk&lt;/code&gt; (qui eux font référence au contenu&amp;nbsp;binaire).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://msgpack.org/"&gt;MessagePack&lt;/a&gt; est utilisé pour la sérialisation.
C&amp;#8217;est un format similaire à &lt;span class="caps"&gt;JSON&lt;/span&gt; (auto-descriptif), mais rapide et&amp;nbsp;compact.&lt;/p&gt;
&lt;h4 id="repository"&gt;Repository&lt;/h4&gt;
&lt;p&gt;Le &amp;#8220;dépôt&amp;#8221; est le point de départ pour sauvegarder ses données.
Différents paramètres permettent de l&amp;#8217;adapter à ses besoins, et il maintient un index pour manipuler efficacement ses&amp;nbsp;archives.&lt;/p&gt;
&lt;p&gt;Un dépôt peut utiliser un disque local, distant, un serveur de stockage (façon S3) ou un serveur distant (&lt;em&gt;sftp&lt;/em&gt; et même &lt;em&gt;ftp&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Un dépôt peut se synchroniser avec un autre&amp;nbsp;dépôt.&lt;/p&gt;
&lt;h4 id="snapshot"&gt;Snapshot&lt;/h4&gt;
&lt;p&gt;Les fichiers sont archivés par lots, dans&amp;nbsp;des &lt;code&gt;Snapshot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Un &lt;code&gt;Importer&lt;/code&gt; va effectuer un &lt;strong&gt;scan&lt;/strong&gt;, un parcours (&lt;strong&gt;walk&lt;/strong&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;) de l&amp;#8217;arborescence de fichiers filtrés par des&amp;nbsp;règles.&lt;/p&gt;
&lt;p&gt;Kloset décrit l&amp;#8217;&lt;em&gt;interface&lt;/em&gt; &lt;code&gt;Importer&lt;/code&gt; qui n&amp;#8217;a qu&amp;#8217;une implémentation de test.
Plakar, lui, fournit des implémentations&amp;nbsp;concrètes.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;&lt;code&gt;Importer&lt;/code&gt; fournit un flot&amp;nbsp;de &lt;code&gt;ScanRecord&lt;/code&gt; représentant les fichiers à&amp;nbsp;sauvegarder.&lt;/p&gt;
&lt;h4 id="packagemanager"&gt;PackageManager&lt;/h4&gt;
&lt;p&gt;Un &lt;code&gt;Repository&lt;/code&gt; possède&amp;nbsp;un &lt;code&gt;PackageManager&lt;/code&gt; (qui parallélise ses taches) que l&amp;#8217;on va nourrir&amp;nbsp;de &lt;code&gt;Blob&lt;/code&gt; (type, &lt;span class="caps"&gt;MAC&lt;/span&gt;, contenu) pour créer&amp;nbsp;des &lt;code&gt;PackFile&lt;/code&gt;,&amp;nbsp;le &lt;code&gt;Repository&lt;/code&gt; se chargeant de les compresser et chiffrer (si la configuration le&amp;nbsp;réclame).&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;instant, aucune excentricité dans les choix des outils de compression : le vénérable &lt;span class="caps"&gt;GZIP&lt;/span&gt; (bonne compression, mais débit mou) ou &lt;span class="caps"&gt;LZ4&lt;/span&gt; (débit fantastique au détriment de la&amp;nbsp;compression).&lt;/p&gt;
&lt;p&gt;Le chiffrement propose des configurations normées d&amp;#8217;&lt;span class="caps"&gt;AES&lt;/span&gt;-256.&lt;/p&gt;
&lt;h4 id="store-et-index"&gt;Store et&amp;nbsp;index&lt;/h4&gt;
&lt;p&gt;Les &lt;code&gt;PackFile&lt;/code&gt; sont enregistrés dans&amp;nbsp;un &lt;code&gt;Store&lt;/code&gt;, une &lt;em&gt;interface&lt;/em&gt; avec juste une implémentation de test dans Kloset, mais des implémentations concrètes dans&amp;nbsp;Plakar.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;y a pas d&amp;#8217;index pour retrouver ses petits, mais un cache, que l&amp;#8217;on peut reconstruire.
Plus précisément, c&amp;#8217;est une &lt;em&gt;interface&lt;/em&gt; &lt;code&gt;StateCache&lt;/code&gt; qui a pour une fois des implémentations dans Kloset basés&amp;nbsp;sur &lt;code&gt;PebbleCache&lt;/code&gt;.
&lt;a href="https://github.com/cockroachdb/pebble"&gt;Pebble&lt;/a&gt; est un clone de RocksDB en&amp;nbsp;Golang.&lt;/p&gt;
&lt;h4 id="securite"&gt;Sécurité&lt;/h4&gt;
&lt;p&gt;La sécurité, c&amp;#8217;est comme les interfaces utilisateurs, il ne faut jamais laisser un développeur s&amp;#8217;en&amp;nbsp;approcher.&lt;/p&gt;
&lt;p&gt;Lire la page Wikipédia pour comprendre les concepts est un bon début.
Ne pas tripoter les curseurs, surtout sans savoir ce qu&amp;#8217;ils font est simplement du bon sens.
Faire valider/auditer par un spécialiste la partie liée à la sécurité est sage (ça éloigne le mauvais œil et les zero&amp;nbsp;days).&lt;/p&gt;
&lt;h4 id="les-salles-secretes-du-donjon"&gt;Les salles secrètes du&amp;nbsp;donjon&lt;/h4&gt;
&lt;p&gt;Après une rapide visite du donjon de Kloset, on peut apercevoir des indices sur des fonctionnalités&amp;nbsp;futures.&lt;/p&gt;
&lt;p&gt;Des notions de &amp;#8220;classification&amp;#8221; et de &amp;#8220;Score&amp;#8221;&amp;nbsp;apparaissent.&lt;/p&gt;
&lt;p&gt;Il est aussi possible que j&amp;#8217;aie raté des éléments dans ma lecture du&amp;nbsp;code.&lt;/p&gt;
&lt;h2 id="ma-lettre-au-pere-noel"&gt;Ma lettre au père&amp;nbsp;Noël&lt;/h2&gt;
&lt;h3 id="trousseaux"&gt;Trousseaux&lt;/h3&gt;
&lt;p&gt;Laisser trainer sa clef avec le risque de la perdre et de transformer ses archives en brique est inutilement&amp;nbsp;stressant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Intégration à &lt;a href="https://github.com/hashicorp/vault"&gt;Vault&lt;/a&gt; ou un équivalent, pour gérer le &amp;#8220;grand&amp;nbsp;secret&amp;#8221;&lt;/li&gt;
&lt;li&gt;[ ] Intégration à différents trousseaux (gnome keyring, keepass, freedesktop keyring, apple&amp;nbsp;keyring…)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="resilience"&gt;Résilience&lt;/h3&gt;
&lt;p&gt;Les disques durs qui meurent, ou plus fourbe, les octets pourris (&lt;em&gt;bit rot&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;), ça arrive, c&amp;#8217;est même pour ça que l&amp;#8217;on fait des sauvegardes (même si le cas d&amp;#8217;usage classique et de pouvoir revenir sur une action&amp;nbsp;hasardeuse).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Correction d&amp;#8217;erreurs de Reed-Solomon pour palier ou compléter la flemme de la&amp;nbsp;réplication&lt;/li&gt;
&lt;li&gt;[ ] Gestion de la parité en &lt;span class="caps"&gt;XOR&lt;/span&gt; sur 3 disques pour contourner Minio ou le &lt;span class="caps"&gt;RAID&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ux"&gt;&lt;span class="caps"&gt;UX&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;La sauvegarde doit être systématique, machinale, la moindre friction ressentie par l&amp;#8217;utilisateur va espacer les backups.
L&amp;#8217;assurance de pouvoir restaurer sereinement est tout autant&amp;nbsp;indispensable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Sauvegarde qui se déclenche (ou demande gentiment) dès que l&amp;#8217;on branche un disque &lt;span class="caps"&gt;USB&lt;/span&gt; dédié (et si la machine est branché à son&amp;nbsp;chargeur)&lt;/li&gt;
&lt;li&gt;[ ] Décompression à la volée des archives, comme les dumps &lt;span class="caps"&gt;SQL&lt;/span&gt; ou les fichiers OpenDocument, pour avoir de jolis &lt;em&gt;chunk&lt;/em&gt; (et de recompresser dans&amp;nbsp;le &lt;code&gt;PackFile&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Sauvegarder un &lt;em&gt;snapshot&lt;/em&gt;, si le système de fichier en est capable (&lt;a href="https://en.wikipedia.org/wiki/ZFS"&gt;&lt;span class="caps"&gt;ZFS&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Btrfs"&gt;Btrfs&lt;/a&gt;, &lt;a href="https://bcachefs.org/"&gt;bcachefs&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Apple_File_System"&gt;&lt;span class="caps"&gt;APFS&lt;/span&gt;&lt;/a&gt;…)&lt;/li&gt;
&lt;li&gt;[ ] Connexion aux sauvegardes intégrées d&amp;#8217;applications, comme le &lt;a href="https://www.postgresql.org/docs/current/continuous-archiving.html"&gt;&lt;code&gt;archive_command&lt;/code&gt; de&amp;nbsp;Postgresql&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="plus-vite-plus-haut-plus-fort"&gt;Plus vite, plus haut, plus&amp;nbsp;fort&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Gestion des &lt;a href="https://en.wikipedia.org/wiki/Shingled_magnetic_recording"&gt;disques &lt;span class="caps"&gt;SMR&lt;/span&gt;&lt;/a&gt; avec&amp;nbsp;des &lt;code&gt;PackFile&lt;/code&gt; qui remplissent efficacement&amp;nbsp;les &lt;code&gt;tracks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Échange des PackFile en &lt;span class="caps"&gt;QUIC&lt;/span&gt;, tant pis pour les paquets perdus, on les redemandera plus tard. Ça revient presque à du &lt;em&gt;chunk&lt;/em&gt; de &lt;em&gt;chunk&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="indexation-et-recherche"&gt;Indexation et&amp;nbsp;recherche&lt;/h3&gt;
&lt;p&gt;Là, on s&amp;#8217;éloigne du backup pour se rapprocher de&amp;nbsp;l&amp;#8217;archivage.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Création de vignette pour les images, et la possibilité d&amp;#8217;afficher, comme pour le teste, le delta (avec un &lt;a href="https://github.com/mapbox/pixelmatch"&gt;PixelMatch&lt;/a&gt; moins laid) pour choisir la version à&amp;nbsp;restaurer&lt;/li&gt;
&lt;li&gt;[ ] &lt;em&gt;Embeding&lt;/em&gt; avec le &lt;a href="https://github.com/huggingface/tokenizers"&gt;Fast Tokenizer d&amp;#8217;Hugginface&lt;/a&gt; pour de la recherche par mot (enfin, par &lt;em&gt;token&lt;/em&gt;) ou même de la&amp;nbsp;similarité.&lt;/li&gt;
&lt;li&gt;[ ] Indexation des images (création de mots clefs) avec &lt;a href="https://github.com/ultralytics/ultralytics"&gt;&lt;span class="caps"&gt;YOLO&lt;/span&gt;&lt;/a&gt; ou d&amp;#8217;&lt;a href="https://huggingface.co/models?pipeline_tag=image-to-text&amp;amp;sort=trending"&gt;autres modèles&lt;/a&gt; (avec une licence&amp;nbsp;compatible).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="saas"&gt;&lt;span class="caps"&gt;SAAS&lt;/span&gt;&lt;/h3&gt;
&lt;h4 id="chacun-ses-chunk"&gt;Chacun ses &lt;em&gt;chunk&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.backblaze.com/"&gt;Backblaze&lt;/a&gt; a un prix d&amp;#8217;archive au kilo (enfin, au téra) fort difficile à concurrencer, même s&amp;#8217;ils fournissent la recette pour construire leurs&amp;nbsp;serveurs.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;offre de Backblaze est, à la louche, le prix d&amp;#8217;un dédié (&lt;span class="caps"&gt;OVH&lt;/span&gt;, &lt;span class="caps"&gt;SCW&lt;/span&gt;, Hetzner), avec une redondance de stockage de deux. Il faut compter en plus les salaires et leur&amp;nbsp;marge.&lt;/p&gt;
&lt;h4 id="mutualiser-les-chunk-de-differents-utilisateurs"&gt;Mutualiser les &lt;em&gt;chunk&lt;/em&gt; de différents&amp;nbsp;utilisateurs&lt;/h4&gt;
&lt;p&gt;Juste pour l&amp;#8217;exercice d&amp;#8217;une offre &lt;span class="caps"&gt;SAAS&lt;/span&gt; qui partage des &lt;em&gt;chunk&lt;/em&gt; entre utilisateurs (&lt;em&gt;chunk&lt;/em&gt; qui reste rangés dans&amp;nbsp;des &lt;code&gt;Packfile&lt;/code&gt;), pour dédoublonner encore plus fort.
Pour ça, il faut que chaque &lt;em&gt;chunk&lt;/em&gt; ait une clef de chiffrement différente, et un &lt;em&gt;hash&lt;/em&gt; (pas un &lt;em&gt;hmac&lt;/em&gt;) pour les&amp;nbsp;désigner.&lt;/p&gt;
&lt;p&gt;Il faut un archiviste-notaire qui certifie une clef &lt;span class="caps"&gt;TLS&lt;/span&gt; pour chaque&amp;nbsp;utilisateur.&lt;/p&gt;
&lt;p&gt;Un utilisateur souhaite envoyer un &lt;em&gt;chunk&lt;/em&gt; avec une taille et un &lt;em&gt;hash&lt;/em&gt;, le serveur dit &amp;#8220;&lt;span class="caps"&gt;OK&lt;/span&gt;, je pense l&amp;#8217;avoir déjà, voici un &lt;strong&gt;sel&lt;/strong&gt; aléatoire, calcul moi le &lt;em&gt;hmac&lt;/em&gt; du &lt;em&gt;chunk&lt;/em&gt;&amp;#8220;, si la preuve est correcte, pas besoin d&amp;#8217;envoyer le fichier. Le notaire va ajouter l&amp;#8217;utilisateur à la liste des propriétaires du &lt;em&gt;chunk&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;La partie chiffrement est la plus&amp;nbsp;casse-gueule.&lt;/p&gt;
&lt;p&gt;Pour déchiffrer un &lt;em&gt;chunk&lt;/em&gt; que l&amp;#8217;on n&amp;#8217;a pas créé, il faut demander au notaire la liste des propriétaires, et contacter l&amp;#8217;un d&amp;#8217;eux en point à point, en s&amp;#8217;authentifiant en mTLS.
Le propriétaire s&amp;#8217;assure auprès du notaire que la demande est légitime, puis il fournira la clef (qu&amp;#8217;il a chiffrée en local avec sa &lt;strong&gt;clef maitresse&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Pour ça, il faut qu&amp;#8217;une des personnes soit connecté et réponde à un moment ou à un autre (comme l&amp;#8217;utopique &lt;span class="caps"&gt;IPFS&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Sinon, le serveur conserve et fournit les clefs, ce qui est contraire au concept même de&amp;nbsp;Kloset.&lt;/p&gt;
&lt;p&gt;Je vois là une belle usine à gaz instable, et je n&amp;#8217;ai même pas prononcé le mot &lt;span class="caps"&gt;DHT&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Un concept de crypto qui m&amp;#8217;échappe peut, peut-être, résoudre ce&amp;nbsp;micmac.&lt;/p&gt;
&lt;h3 id="la-limite-du-raisonnable"&gt;La limite du&amp;nbsp;raisonnable&lt;/h3&gt;
&lt;p&gt;Oui, une partie des souhaits ne sont pas&amp;nbsp;raisonnables.&lt;/p&gt;
&lt;p&gt;Essayer de contourner des services existants est&amp;nbsp;risqué.&lt;/p&gt;
&lt;p&gt;Optimiser quelque qui chose qui fonctionne déjà correctement, ou pour des conditions extrêmes n&amp;#8217;est jamais une bonne&amp;nbsp;idée.&lt;/p&gt;
&lt;p&gt;Par contre, d&amp;#8217;autres gadgets, comme la synchronisation &lt;span class="caps"&gt;QUIC&lt;/span&gt; peut être développé (pour le sport) à côté sans rien secouer.
Si le gain est décevant, poubelle, et retour aux approches&amp;nbsp;sages.&lt;/p&gt;
&lt;h2 id="old"&gt;Old&lt;/h2&gt;
&lt;p&gt;Le &lt;a href="https://www.plakar.io/posts/2025-04-29/kloset-the-immutable-data-store/"&gt;billet de blog officiel décrivant Kloset existe&lt;/a&gt;, mais promis, je suis parti des sources et des publications universitaires pour écrire ma&amp;nbsp;version.&lt;/p&gt;</content><category term="Ops"></category><category term="golang"></category><category term="backup"></category><category term="chunk"></category><category term="deduplication"></category></entry><entry><title>Packageless, la distribution sans paquets</title><link href="http://blog.garambrogne.net/packageless.html" rel="alternate"></link><published>2025-05-26T09:04:00+02:00</published><updated>2025-05-26T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-05-26:/packageless.html</id><summary type="html">&lt;p&gt;Les paquets permettent d&amp;#8217;installer des logiciels et des bibliothèques sur un ordinateur, mais quel format pour quelle tache&amp;nbsp;?&lt;/p&gt;</summary><content type="html">&lt;h2 id="paquets"&gt;Paquets&lt;/h2&gt;
&lt;p&gt;Les paquets permettent d&amp;#8217;installer des logiciels et des bibliothèques sur un ordinateur.
Sur un système d&amp;#8217;exploitation si on veut être&amp;nbsp;précis.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.colinmaire.net/lac-de-laffrey/"&gt;&lt;img alt="Lac de Laffrey" class="image-process-article-image" src="/images/lac-laffrey.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Les paquets ont la bonne granularité déployer des applications et mutualiser les bibliothèques (en limitant la redondance sur le disque dur) et permettent de mettre à jour simplement la&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article se concentre sur Linux (pour les serveurs), mais ils existent des outils similaires à installer sur des &lt;span class="caps"&gt;OS&lt;/span&gt; propriétaires comme &lt;a href="https://chocolatey.org/"&gt;Chocolatey&lt;/a&gt; pour Windows ou &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; pour&amp;nbsp;MacOS.&lt;/p&gt;
&lt;h3 id="formats"&gt;Formats&lt;/h3&gt;
&lt;h4 id="archives-brutes"&gt;Archives&amp;nbsp;brutes&lt;/h4&gt;
&lt;p&gt;Sur les distributions de dinosaures, comme &lt;a href="http://www.slackware.com/"&gt;Slackware&lt;/a&gt; (en 1993), de simples archives&amp;nbsp;(du &lt;code&gt;tar&lt;/code&gt; compressé par exemple) sont utilisés.
Slackware oublie de gérer les dépendances, ce qui est un peu&amp;nbsp;peu.&lt;/p&gt;
&lt;h4 id="paquets-sources"&gt;Paquets&amp;nbsp;sources&lt;/h4&gt;
&lt;p&gt;Pourquoi se contenter de paquets tout prêts qui font des choix à notre place alors qu&amp;#8217;il est possible de tout compiler avec vos options choisies avec amour&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gentoo.org/"&gt;Gentoo&lt;/a&gt; propose&amp;nbsp;ça.&lt;/p&gt;
&lt;p&gt;Concrètement, l&amp;#8217;intérêt est uniquement pédagogique, pour comprendre le fonctionnement d&amp;#8217;un&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;La plupart du temps, une béquille est proposée sous forme de paquet binaire, pour gérer des monstres comme Libreoffice ou&amp;nbsp;Firefox.&lt;/p&gt;
&lt;h4 id="paquets-binaires"&gt;Paquets&amp;nbsp;binaires&lt;/h4&gt;
&lt;p&gt;Les paquets binaires sont déjà compilés et s&amp;#8217;installent beaucoup plus rapidement.
Des systèmes spécifiques de paquet apparaissent, comme&amp;nbsp;les &lt;code&gt;.deb&lt;/code&gt; (techniquement &lt;code&gt;dpkg&lt;/code&gt;, en 1994)&amp;nbsp;et &lt;code&gt;.rpm&lt;/code&gt; (en 1997).
D&amp;#8217;autres formats apparaitront plus&amp;nbsp;tard.&lt;/p&gt;
&lt;p&gt;Les paquets civilisés apportent les fonctionnalités suivantes&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gestion des paquets dépendants ou&amp;nbsp;recommandés&lt;/li&gt;
&lt;li&gt;Gestion des conflits de&amp;nbsp;paquets&lt;/li&gt;
&lt;li&gt;Suppression du paquet et des paquets dépendants&amp;nbsp;orphelins&lt;/li&gt;
&lt;li&gt;Signature cryptographique pour ne plus craindre la&amp;nbsp;falsification&lt;/li&gt;
&lt;li&gt;Proposition d&amp;#8217;une configuration par&amp;nbsp;défaut&lt;/li&gt;
&lt;li&gt;Redémarrage des services&amp;nbsp;liés&lt;/li&gt;
&lt;li&gt;Possibilité de reconstruire le paquet à partir d&amp;#8217;un paquet&amp;nbsp;source&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bibliotheques"&gt;Bibliothèques&lt;/h2&gt;
&lt;p&gt;Pour mutualiser la place (et historiquement la mémoire), les systèmes d&amp;#8217;exploitation utilisent des &lt;a href="https://en.wikipedia.org/wiki/Shared_library"&gt;bibliothèques partagées&lt;/a&gt;, un bout de code qui pourra être utilisé par plusieurs applications (et même d&amp;#8217;autres&amp;nbsp;bibliothèques).&lt;/p&gt;
&lt;div class="explication"&gt;
&lt;p&gt;La plupart des &lt;span class="caps"&gt;UNIX&lt;/span&gt; (dont Linux) utilise le standard &lt;a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format"&gt;&lt;span class="caps"&gt;ELF&lt;/span&gt;&lt;/a&gt; qui utilise le&amp;nbsp;format &lt;code&gt;shared objects&lt;/code&gt;,&amp;nbsp;aka &lt;code&gt;.so&lt;/code&gt;, pour ses bibliothèques&amp;nbsp;dynamiques.&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;commande &lt;code&gt;ldd&lt;/code&gt; permet de lister qui est lié à&amp;nbsp;quoi.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Découverte d&amp;#8217;une faille de sécurité OpenSSL ? Hop, il suffit de mettre à&amp;nbsp;jour &lt;code&gt;libssl&lt;/code&gt; et de redémarrer toutes les applications qui l&amp;#8217;utilisent avec un&amp;nbsp;intuitif &lt;code&gt;lsof +c 0 | grep libssl | awk '{print $1}' | sort | uniq&lt;/code&gt; ou en faisant confiance à votre gestionnaire de&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Le comportement des bibliothèques est similaire pour la plupart des langages, mais les langages interprétés, mais pas ceux qui compilent en&amp;nbsp;statique.&lt;/p&gt;
&lt;h3 id="spaghettis-de-liens"&gt;Spaghettis de&amp;nbsp;liens&lt;/h3&gt;
&lt;p&gt;Avec des cascades de bibliothèques, il est possible de créer des drames grandioses, comme un &lt;span class="caps"&gt;CVE&lt;/span&gt; niveau 10 avec la tant redoutée &lt;a href="https://euvd.enisa.europa.eu/vulnerability/Cve-2024-3094"&gt;&lt;span class="caps"&gt;CVE&lt;/span&gt;-2024-3094&lt;/a&gt; qui&amp;nbsp;infect &lt;code&gt;liblzma&lt;/code&gt; utilisé&amp;nbsp;par &lt;code&gt;libsystemd&lt;/code&gt; utilisé&amp;nbsp;par &lt;code&gt;openssh&lt;/code&gt;.
Les détails gores sont &lt;a href="https://www.openwall.com/lists/oss-security/2024/03/29/4"&gt;là&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="micro-bibliotheques"&gt;Micro-bibliothèques&lt;/h3&gt;
&lt;p&gt;Les langages modernes fournissent un ensemble de bibliothèques classiques, pour avoir un socle commun et surtout une qualité constante (indispensable pour les bibliothèques liées à la sécurité).
On parle de &amp;#8220;bibliothèque standard&amp;#8221; ou &amp;#8220;piles&amp;nbsp;incluses&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Pour ne pas assumer ce travail, certains langages ont fait le choix des micro-bibliothèques qui ne font pas grand-chose et devrait pouvoir être remplacé facilement en cas&amp;nbsp;d&amp;#8217;abandon.&lt;/p&gt;
&lt;p&gt;Concrètement, chaque micro-bibliothèque dépend de beaucoup de bibliothèques.
Chaque projet se retrouve alors avec une ribambelle de dépendances.
La quantité est telle qu&amp;#8217;il y aura forcément une partie des dépendances mal maintenue ou devenant&amp;nbsp;incompatible.&lt;/p&gt;
&lt;p&gt;On ne nommera pas le langage le plus enfoncé dans ce choix, mais un&amp;nbsp;simple &lt;code&gt;npm audit&lt;/code&gt; vous donnera l&amp;#8217;état d&amp;#8217;un de ses&amp;nbsp;projets.&lt;/p&gt;
&lt;h3 id="paquets-de-bibliotheques"&gt;Paquets de&amp;nbsp;bibliothèques&lt;/h3&gt;
&lt;p&gt;Emballer les bibliothèques partagées est un choix évident, et il est même possible de proposer diverses variantes, mais une seule version (ce qui est de moins en moins&amp;nbsp;vrai).&lt;/p&gt;
&lt;p&gt;Les langages interprétés utilisent, eux aussi, des bibliothèques, mais dans leur propre&amp;nbsp;format.&lt;/p&gt;
&lt;p&gt;La compilation statique (les bibliothèques sont intégrées au binaire) permet de résoudre (violemment) le souci des versions épinglées.
Go et Rust compilent systématiquement leurs bibliothèques en statique, tant pis pour la taille du binaire, mais au moins, on évite les pinaillages de compatibilité de&amp;nbsp;versions.&lt;/p&gt;
&lt;p&gt;Des distributions comme Debian mettent un point d&amp;#8217;honneur à pouvoir recompiler n&amp;#8217;importe quel paquet sans dépendre de sites tiers.
Démarche honorable, mais hors C/C++, la plupart des applications utilisent des pelletées de bibliothèques (pas forcément dans la même version), et ne corrigent les failles de sécurité que dans la version&amp;nbsp;courante.&lt;/p&gt;
&lt;p&gt;Donc, même s&amp;#8217;il est possible d&amp;#8217;empaqueter des applications sans dépendances, l&amp;#8217;obligation de fournir des paquets sources des bibliothèques utilisées pour les compiler vire rapidement au&amp;nbsp;drame.&lt;/p&gt;
&lt;p&gt;Techniquement, le &lt;em&gt;vendoring&lt;/em&gt; (copie des sources des bibliothèques dans le projet) peut résoudre la possibilité de recompiler sans dépendre de sites tiers, mais en remplissant le disque dur de multiples&amp;nbsp;copies.&lt;/p&gt;
&lt;h3 id="paquets-tiers"&gt;Paquets&amp;nbsp;tiers&lt;/h3&gt;
&lt;p&gt;Certaines applications ne rentrent pas dans les clous d&amp;#8217;une distribution, comme on l&amp;#8217;a vu&amp;nbsp;précédemment.&lt;/p&gt;
&lt;p&gt;Les outils pour créer des paquets sont fournis avec la distribution, et pour les formats de paquet contemporains, la tache est&amp;nbsp;triviale.&lt;/p&gt;
&lt;p&gt;Le premier intérêt de fournir des paquets éditeurs et la liberté de faire des montées en versions fonctionnelles à son rythme, pas une fois par&amp;nbsp;an.&lt;/p&gt;
&lt;p&gt;La gestion des bibliothèques dans les paquets tiers est compliquée (versions, options&amp;nbsp;différentes…).&lt;/p&gt;
&lt;p&gt;Différents paquets pour différentes distributions dans différentes versions (et différentes architectures) va rapidement devenir pénible (même si une &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; facilitera beaucoup le travail).
La création, même laborieuse, est faisable, par contre, la gestion de bugs spécifiques ou de bibliothèques dont les versions sont incompatibles, va rapidement devenir infernal.
La distribution en paquets systèmes d&amp;#8217;application avec le support technique d&amp;#8217;un éditeur ne se fera que pour quelques&amp;nbsp;distributions.&lt;/p&gt;
&lt;p&gt;Les entrepôts de paquets tiers, fort pratique, n&amp;#8217;ont pas forcément les mêmes performances et disponibilité qu&amp;#8217;un entrepôt de&amp;nbsp;distribution.&lt;/p&gt;
&lt;p&gt;Les mises à jour de sécurité devraient être équivalente, mais la non garantie de figer les fonctionnalités peut amener des surprises lors des mises à jour.
Chose que devrait attraper les tests (intégrations et fonctionnels) dans la &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="deployer-une-application-metier-dans-des-paquets-systemes"&gt;Déployer une application métier dans des paquets&amp;nbsp;systèmes&lt;/h3&gt;
&lt;p&gt;Il ne faut plus compiler du code métier sur le serveur de production.
Rails avec sa compilation des &lt;em&gt;assets&lt;/em&gt; l&amp;#8217;a démontré dans la&amp;nbsp;douleur.&lt;/p&gt;
&lt;p&gt;Un artefact est créé à partir des sources de l&amp;#8217;application, puis déployé en production.
Ne vous faites pas de mal, ne déployez jamais d&amp;#8217;application métier dans&amp;nbsp;un &lt;code&gt;.deb&lt;/code&gt;,&amp;nbsp;jamais.&lt;/p&gt;
&lt;h2 id="coherence-des-versions-et-mises-a-jour"&gt;Cohérence des versions et mises à&amp;nbsp;jour&lt;/h2&gt;
&lt;p&gt;Les distributions garantissent plus ou moins la cohérence entre les paquets.
Plus pour &lt;a href="https://debian.org"&gt;Debian&lt;/a&gt;, moins pour &lt;a href="https://archlinux.org/"&gt;Archlinux&lt;/a&gt; (et&amp;nbsp;Slackware).&lt;/p&gt;
&lt;p&gt;Debian a fait le choix de l&amp;#8217;hyper-cohérence entre les paquets et refuse de changer la version d&amp;#8217;un paquet dans une&amp;nbsp;distribution.&lt;/p&gt;
&lt;div class="example large"&gt;
&lt;p&gt;Des aplications utilisent des bibliothèques dans des versions très récentes, modifiées ou compilées avec des options&amp;nbsp;spécifiques.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;exemple classique est &lt;a href="https://v8.dev/"&gt;v8&lt;/a&gt;, le moteur Javascript de Google connu pour ses montées en versions frénétiques.
Chromium, Nodejs et Mongodb (quand il était encore libre) utilisent des v8 différents, ce qui les excluent de fait des entrepôts&amp;nbsp;Debian.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Debian fait bien sûr rigoureusement les mises à jour de sécurité, mais en rapiéçant (patch) le code, pour ne &lt;span class="caps"&gt;RIEN&lt;/span&gt; changer au niveau fonctionnel, et donc de n&amp;#8217;amener aucunes surprises aux applications qui utilisent ces&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Globalement, l&amp;#8217;engagement de stabilité fonctionnelle est&amp;nbsp;tenu.&lt;/p&gt;
&lt;h3 id="monorepo-solution-ultime-pour-la-coherence-et-la-fraicheur"&gt;Monorepo, solution ultime pour la cohérence et la&amp;nbsp;fraicheur&lt;/h3&gt;
&lt;p&gt;Le monorepo (une seule source de code versionné), principalement aux échelles titanesques de Google ou Meta, permet d&amp;#8217;avoir une cohérence rigoriste entre les bibliothèques partagées par les différents&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Dès que l&amp;#8217;on pousse une modification de code, non seulement ses tests sont lancés, mais surtout, déclenche les tests toutes les applications avec la nouvelle version de la bibliothèque.
La modification n&amp;#8217;est pas validée tant que tous les tests concernés ne passent pas.
Une fois validé, le déploiement continu se déclenche (progressivement, avec comparaison des métriques avant et après la mise à jour).
S&amp;#8217;il y a une régression dans une application utilisant la bibliothèque, c&amp;#8217;est à elle de corriger (et de blinder ses tests pour la prochaine&amp;nbsp;fois).&lt;/p&gt;
&lt;p&gt;Le déploiement passera par un artefact dans tous les cas, et à une époque, Facebook le faisait en pair à pair (son artefact avait une taille&amp;nbsp;déraisonnable).&lt;/p&gt;
&lt;h3 id="fraicheur-dune-distribution"&gt;Fraicheur d&amp;#8217;une&amp;nbsp;distribution&lt;/h3&gt;
&lt;p&gt;La durée de vie d&amp;#8217;une distribution se compte en années, et bien plus pour les versions &lt;span class="caps"&gt;LTS&lt;/span&gt; (support à long terme).
La fréquence de sortie de nouvelles versions d&amp;#8217;application n&amp;#8217;est clairement pas le&amp;nbsp;même.&lt;/p&gt;
&lt;div class="example"&gt;
&lt;p&gt;Il y a eut des drames avec Clamav, par exemple, qui a stoppé le support de l&amp;#8217;ancien format de description de virus, obligeant Debian à créer un nouvel entrepôt de&amp;nbsp;paquets.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Le problème se pose pour les applications, mais aussi les bibliothèques, les interpréteurs (hors Perl qui est stable, lui), les outils de&amp;nbsp;développement.&lt;/p&gt;
&lt;p&gt;Développer avec des outils vieillissants est une&amp;nbsp;punition.&lt;/p&gt;
&lt;p&gt;Les navigateurs web sont les plus agressifs sur les montées en version, contraint par les soucis de sécurité, mais aussi par les fonctionnalités.
De toute façon, la course à la version se fait sur les smartphones, Windows et MacOS, ce serait dommage que Linux se fasse distancer.
Les applications web sont &lt;span class="caps"&gt;LE&lt;/span&gt; système universel pour distribuer des applications, surtout avec l&amp;#8217;arrivée&amp;nbsp;de &lt;code&gt;webassembly&lt;/code&gt;, et pour ça il faut un socle sain, homogène et&amp;nbsp;performant.&lt;/p&gt;
&lt;p&gt;Ubuntu, pragmatique, ne fournit plus de&amp;nbsp;paquets &lt;code&gt;.deb&lt;/code&gt; pour Firefox/Chromium.
Enfin si, le&amp;nbsp;paquet &lt;code&gt;.deb&lt;/code&gt; se contente d&amp;#8217;installer le navigateur avec un autre système de paquet, sacrilège pour les&amp;nbsp;puritains.&lt;/p&gt;
&lt;h4 id="mise-a-jour-roulante"&gt;Mise à jour&amp;nbsp;roulante&lt;/h4&gt;
&lt;p&gt;Plutôt que de fournir des versions gravées dans le marbre tous les ans (voir plus), des distributions choisissent de ne jamais figer les versions, d&amp;#8217;avancer&amp;nbsp;continuellement.&lt;/p&gt;
&lt;p&gt;Archlinux, une sorte de Gentoo civilisé, ne sort jamais de version, enfin, pas jamais, tout le temps, suivant le principe de mise à jour roulante (&lt;em&gt;rolling update&lt;/em&gt;).
La possibilité de faire une mise à jour n&amp;#8217;est pas garanti, il faut les faire régulièrement pour limiter les&amp;nbsp;drames.&lt;/p&gt;
&lt;p&gt;Amateur de Debian Sid, voici un nouveau&amp;nbsp;challenge.&lt;/p&gt;
&lt;h3 id="distribution-immuable"&gt;Distribution&amp;nbsp;immuable&lt;/h3&gt;
&lt;p&gt;Les distributions immuables (comme feu &lt;a href="https://en.wikipedia.org/wiki/Container_Linux"&gt;CoreOS&lt;/a&gt;) garantissent une cohérence ultime.
Pas de gestion de paquets, donc ni ajout ni suppression ni mises à jour possibles.
Tout est gravé dans le marbre.
Pour protéger le marbre, la partition racine est montée en lecture&amp;nbsp;seule.&lt;/p&gt;
&lt;div class="explication"&gt;
&lt;p&gt;L&amp;#8217;image est construite à partir de paquets (pour ne pas en utiliser ensuite).
CoreOS utilisait les paquets de Gentoo, et&amp;nbsp;des &lt;code&gt;rpm&lt;/code&gt; dans sa reprise par Redhat, &lt;a href="https://fedoraproject.org/coreos/"&gt;Fedora-CoreOS&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Le choix est extrême, mais l&amp;#8217;idée est de servir de socle pour une abstraction de plus haut niveau.
Imaginé pour les conteneurs, ils peuvent aussi gérer des machines&amp;nbsp;virtuelles.&lt;/p&gt;
&lt;p&gt;Pour mettre à jour, il suffit de redémarrer sur une nouvelle version.
Techniquement, il y a deux partitions bootables, la courante, en lecture seule, la version suivante sur laquelle on a appliqué des patchs, soit n et n+1.
Si le redémarrage de mise à jour se passe mal, rembobinage sur la dernière version connue comme&amp;nbsp;stable.&lt;/p&gt;
&lt;p&gt;Ce type de distribution n&amp;#8217;est pas réservée aux serveurs, il en existe des &lt;a href="https://itsfoss.com/immutable-linux-distros/"&gt;dédiées aux bureaux&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="meta-paquets"&gt;Méta-paquets&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Furoshiki"&gt;&lt;img alt="L'art d'emballer du furoshiki 風呂敷" class="right" src="/images/furoshiki.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Entre le tout paquet des distributions classiques et le sans paquets des distributions immuables, il y a une place pour des solutions plus&amp;nbsp;souple.&lt;/p&gt;
&lt;p&gt;Une application métier va s&amp;#8217;appuyer sur un ensemble d&amp;#8217;outils systèmes, soit un tout petit bout d&amp;#8217;un serveur&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Oui, certaines technologies isolationnistes permettent de s&amp;#8217;en&amp;nbsp;passer.&lt;/p&gt;
&lt;p&gt;Une application dans une image ne va pas dépendre du système hôte (distribution et version), mais juste des capacités du&amp;nbsp;noyau.&lt;/p&gt;
&lt;h3 id="environnements-virtuels-specifiques-aux-langages"&gt;Environnements virtuels spécifiques aux&amp;nbsp;langages&lt;/h3&gt;
&lt;p&gt;Tous les langages de script savent utiliser un environnement spécifique à l&amp;#8217;application (et pas global).
Les bibliothèques utilisées seront spécifiques à cet environnement, dans des versions précises.
Tout sauf utiliser les bibliothèques empaquetées par la&amp;nbsp;Distribution.&lt;/p&gt;
&lt;div class="example"&gt;
&lt;ul&gt;
&lt;li&gt;Python a &lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ruby a &lt;a href="https://bundler.io/"&gt;Bundler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; a &lt;a href="https://getcomposer.org/"&gt;Composer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Golang, Rust, Nodejs le font par&amp;nbsp;défaut&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Partir d&amp;#8217;une liste de bibliothèques dans des versions précises est l&amp;#8217;approche recommandée par les outils qui chassent les versions trouées (comme le Dependabot de Github mais surtout l&amp;#8217;&lt;a href="https://osv.dev/"&gt;&lt;span class="caps"&gt;OSV&lt;/span&gt;&lt;/a&gt;, comme évoqué dans mon billet &lt;a href="la-maison-blanche-veut-securiser-Internet-1.html"&gt;Sécuriser Internet&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;La plupart des langages utilisent une description des dépendances avec des contraintes sur les versions, puis un autre fichier qui contient les versions gelées, celles choisies lors de la dernière montée en&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;utilisation de bibliothèques partagées&amp;nbsp;(des &lt;code&gt;.so&lt;/code&gt;) va un peu casser l&amp;#8217;universalité de cette&amp;nbsp;solution.&lt;/p&gt;
&lt;p&gt;Pour avoir un environnement d&amp;#8217;exécution déterministe et donc normalisé, du poste du développeur à la production en passant par la &lt;span class="caps"&gt;CI&lt;/span&gt;, il va falloir utiliser quelque chose de plus&amp;nbsp;costaud.&lt;/p&gt;
&lt;h3 id="images-immuables-pour-la-virtualisation"&gt;Images immuables pour la&amp;nbsp;virtualisation&lt;/h3&gt;
&lt;p&gt;Netflix a communiqué sur sa tactique de création d&amp;#8217;images immuables démarrées dans &lt;span class="caps"&gt;EC2&lt;/span&gt;.
Celui qui n&amp;#8217;a pas souffert avec &lt;a href="https://www.hashicorp.com/fr/products/packer"&gt;Packer&lt;/a&gt; ne peut imaginer à quel point cette approche est laborieuse et pénible.
Même avec &lt;a href="https://cloudinit.readthedocs.io/en/latest/index.html"&gt;Cloud-init&lt;/a&gt;.&lt;/p&gt;
&lt;div class="explication"&gt;
&lt;p&gt;&lt;strong&gt;Cloud-init&lt;/strong&gt; permet de configurer une machine virtuelle au démarrage qu&amp;#8217;elle que soit&amp;nbsp;l&amp;#8217;hébergeur.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Le &lt;a href="kernel-less.html"&gt;kernel-less&lt;/a&gt; profite aussi de la (para)virtualisaiotn et réduit à l&amp;#8217;os la taille de l&amp;#8217;image disque, mais avec des contraintes radicales.
On en a déjà parlé sur ce&amp;nbsp;blog.&lt;/p&gt;
&lt;h3 id="images-oci-aka-docker-et-k8s"&gt;Images &lt;span class="caps"&gt;OCI&lt;/span&gt; (aka Docker et&amp;nbsp;k8s)&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;image disque n&amp;#8217;est pas la bonne échelle pour déployer une application (ça va mieux en le&amp;nbsp;redisant).&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;conteneurs&lt;/em&gt; utilisent des &lt;em&gt;images&lt;/em&gt; avec un bout d&amp;#8217;arborescence Linux, indépendamment du choix de Distribution.
L&amp;#8217;arborescence est construite en couche d&amp;#8217;oignons, mutualisés entre les conteneurs, avec tout en bas une image minimaliste basée sur une distribution&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;application dans un conteneur est lancé dans un contexte isolé par diverses fonctionnalités du noyau&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Docker en a prouvé la faisabilité, &lt;span class="caps"&gt;OCI&lt;/span&gt; l&amp;#8217;a normalisé, et Kubernetes l&amp;#8217;a déployé en cluster.
Redhat l&amp;#8217;a remis en cause avec &lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt;, qui ne comprend pas l&amp;#8217;intérêt d&amp;#8217;avoir un service pour gérer les conteneurs alors qu&amp;#8217;il y a déjà&amp;nbsp;Systemd.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;image est construite avec des paquets (Alpine, Debian…) si l&amp;#8217;application n&amp;#8217;est pas un gros binaire&amp;nbsp;statique.&lt;/p&gt;
&lt;p&gt;Les courageux démarrent le conteneur en lecture seule, ce qui rend le conteneur encore plus prévisible.
C&amp;#8217;est bien au niveau sécurité (pas moyen de corrompre l&amp;#8217;application), et aussi pour les écritures intempestives (log, debug ?) qui tenteront de remplir le disque&amp;nbsp;dur.&lt;/p&gt;
&lt;h3 id="paquets-agnostique-pour-le-bureau"&gt;Paquets agnostique pour le&amp;nbsp;bureau&lt;/h3&gt;
&lt;p&gt;Proposer des paquets systèmes pour la pléthore de distribution Linux existante est tout simplement&amp;nbsp;impossible.&lt;/p&gt;
&lt;p&gt;Par contre, reprendre l&amp;#8217;idée&amp;nbsp;des &lt;code&gt;.app&lt;/code&gt; de MacOS (de Next en fait), en ajoutant une couche de sécurité (et une sous-couche mutualisée) permet de distribuer une application sans les contraintes de&amp;nbsp;l&amp;#8217;hôte.&lt;/p&gt;
&lt;p&gt;Les outils de bas niveaux et les fonctionnalités du noyau sont essentiellement celles utilisées par les&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;h4 id="flatpack"&gt;Flatpack&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://flatpak.org/"&gt;Flatpak&lt;/a&gt; permet de distribuer des applications, même propriétaire sous Linux sans se soucier de la distribution et de sa version.
L&amp;#8217;application est dans un &amp;#8220;bac à sable&amp;#8221;, et demandera des droits pour agir avec l&amp;#8217;hôte, comme le font les applications sur&amp;nbsp;téléphones.&lt;/p&gt;
&lt;div class="explication large"&gt;
&lt;p&gt;Flatpack utilise les mêmes outils d&amp;#8217;isolation de Linux que les conteneurs.
Plutôt que du Layering, Flatpack dédoublonne les fichiers avec&amp;nbsp;OStree.&lt;/p&gt;
&lt;p&gt;Il fournit un ensemble de Runtime, sur lesquels on va pouvoir compiler ses applications. Niveau paquet, on est très hardcore, ce sera le&amp;nbsp;classique &lt;code&gt;./configure &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;runtimes&lt;/em&gt; sont fournis par Freedesktop, le grand réconciliateur du monde Linux.
&lt;a href="https://gitlab.com/freedesktop-sdk/freedesktop-sdk"&gt;Freedesktop-sdk&lt;/a&gt; est construit à partie de rien, sans&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Des &lt;em&gt;runtimes&lt;/em&gt; spécifiques sont fournis, comme &lt;em&gt;Gnome&lt;/em&gt; ou &lt;em&gt;&lt;span class="caps"&gt;KDE&lt;/span&gt;&lt;/em&gt;, et il est possible de faire cohabiter différentes&amp;nbsp;versions.&lt;/p&gt;
&lt;p&gt;Fedora, choqué à l&amp;#8217;idée de ne pas utiliser ses &lt;em&gt;rpm&lt;/em&gt; a &lt;a href="https://blog.fishsoup.net/2018/12/04/flatpaks-in-fedora-now-live/"&gt;négocié l&amp;#8217;utilisation des images &lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/a&gt; en plus du OSTree&amp;nbsp;originel.&lt;/p&gt;
&lt;/div&gt;
&lt;h4 id="snap"&gt;Snap&lt;/h4&gt;
&lt;p&gt;Plutôt que de se fatiguer à faire du lobbying comme Redhat pour Flatpack, Unbuntu a créé son &lt;a href="https://snapcraft.io/"&gt;Snap&lt;/a&gt;.&lt;/p&gt;
&lt;div class="explication large"&gt;
&lt;p&gt;Le fonctionnement de Snao est très, très&amp;nbsp;classique.&lt;/p&gt;
&lt;p&gt;Un bout d&amp;#8217;arborescence sur un &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/squashfs.html"&gt;Squashfs&lt;/a&gt; (système de fichier compressé en lecture seule) posé sur une &lt;em&gt;Base&lt;/em&gt; (une Ubuntu &lt;span class="caps"&gt;LTS&lt;/span&gt;) et des droits fins pour accéder à&amp;nbsp;l&amp;#8217;hôte.&lt;/p&gt;
&lt;p&gt;La construction de paquet est isolée par &lt;span class="caps"&gt;LXD&lt;/span&gt; (un outil de conteneur pour les ringards) et utilise&amp;nbsp;des &lt;code&gt;.deb&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;On voit bien l&amp;#8217;intérêt d&amp;#8217;Ubuntu de pouvoir proposer un Firefox frais sans casser la cohérence de sa distribution, tout en s&amp;#8217;appuyant sur son propre écosystème, mais c&amp;#8217;est navrant de voir une dispersion entre Flatpack et Snap qui font, à quelques pinaillages près, la même&amp;nbsp;chose.&lt;/p&gt;
&lt;h2 id="entrepot-dartefacts"&gt;Entrepôt&amp;nbsp;d&amp;#8217;artefacts&lt;/h2&gt;
&lt;p&gt;Il faut pouvoir mettre à disposition les paquets (quelque soit leur forme) que l&amp;#8217;on souhaite utiliser.
Du poste de dev à la prod en passant par la &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Chaque technologie expose son entrepôt en ligne (et doit avoir une facture sympa en &lt;span class="caps"&gt;CDN&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Il est possible de cacher ces entrepôts, au fur et à mesure, ou de créer des miroirs (s&amp;#8217;il est centralisé).
La cache des entrepôts, fort important pour l&amp;#8217;intégration continue sera le sujet d&amp;#8217;un autre&amp;nbsp;billet.&lt;/p&gt;
&lt;h3 id="signatures"&gt;Signatures&lt;/h3&gt;
&lt;p&gt;Pour éviter la falsification des paquets (suite à une compromission du serveur ou via un miroir menteur), il faut signer les&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Techniquement, on calcule le hachage du paquet, qui est signé avec une clef&amp;nbsp;asymétrique.&lt;/p&gt;
&lt;p&gt;Chaque langage a son propre système de signature à base de clefs asymétriques et de chaines de&amp;nbsp;confiance.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;GPG&lt;/span&gt; est souvent utilisé même si le principe de la chaine de confiance est une utopie ratée.
Pour pallier ça, un trousseau de clefs de confiance (qui permettent de signer les clefs des mainteneurs) est fournie avec la distribution ou avec les entrepôts&amp;nbsp;tiers.&lt;/p&gt;
&lt;p&gt;Pour généraliser tout ça et surtout pouvoir signer n&amp;#8217;importe quel artefact sans passer par une poignée d&amp;#8217;autorités de certification (comme le fait &lt;span class="caps"&gt;TLS&lt;/span&gt;), une nouvelle approche universelle est proposée :
&lt;a href="https://www.sigstore.dev/"&gt;Sigstore&lt;/a&gt; (comme évoqué dans le billet &lt;a href="distroless.html"&gt;Distroless&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Qu&amp;#8217;une personne signe un paquet n&amp;#8217;a pas beaucoup de sens.
Un mainteneur peut le faire, car il est le seul responsable de ce paquet.
Pour un développeur, signer un commit est logique, mais comme le code libre est en théorie réalisé à plusieurs, qui est légitime pour signer le paquet ?
Un éventuel &amp;#8220;dictateur bénévole à vie&amp;#8221; qui valide toutes les modifications ?
Une entité regroupant les développeurs (et ceux qui valident les contributions externes) est légitime.
Une fondation ou une entreprise par exemple, et par extension un entrepôt&amp;nbsp;centralisé.&lt;/p&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;CI&lt;/span&gt; peuvent maintenant signer les artefacts en leur nom (Github par&amp;nbsp;exemple).&lt;/p&gt;
&lt;p&gt;Cette confiance peut être chainée, des entrepôts font confiance à la signature de&amp;nbsp;Github.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est le principe du Sigstore de&amp;nbsp;Sigstore.&lt;/p&gt;
&lt;p&gt;Microsoft, propriétaire de Github, se retrouve quand même avec un beau &lt;a href="https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us"&gt;&amp;#8220;All your base are belong to us&amp;#8221;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La gestion de la chaine de confiance dans les entrepôts de paquets (hors paquets des distributions) a longtemps était inexistante ou catastrophique comme l&amp;#8217;a démontré la &lt;a href="https://euvd.enisa.europa.eu/vulnerability/Cve-2022-29176"&gt;&lt;span class="caps"&gt;CVE&lt;/span&gt;-2022-29176&lt;/a&gt; avec un excellent score de &lt;strong&gt;9.9&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="example large"&gt;
&lt;h4 id="git"&gt;Git&lt;/h4&gt;
&lt;p&gt;Git n&amp;#8217;est pas tout à fait un entrepôt de paquets, mais comme il est possible de désigner une bibliothèque avec une &lt;span class="caps"&gt;URL&lt;/span&gt; Git, on peut le qualifier de paquet&amp;nbsp;source.&lt;/p&gt;
&lt;p&gt;Un &lt;code&gt;commit&lt;/code&gt; git peut être signé avec &lt;span class="caps"&gt;GPG&lt;/span&gt;, et, depuis peu, avec Sigstore via &lt;a href="https://docs.sigstore.dev/cosign/signing/gitsign/"&gt;gitsign&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="python"&gt;Python&lt;/h4&gt;
&lt;p&gt;Python &lt;a href="https://www.python.org/downloads/metadata/sigstore/"&gt;signe les artefatcs de CPython, avec Sigstore&lt;/a&gt; et déprécie &lt;span class="caps"&gt;GPG&lt;/span&gt; dans sa &lt;a href="https://peps.python.org/pep-0761/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt;-761&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Python galère depuis une dizaine d&amp;#8217;années malgré de gros financements (voir la &lt;a href="https://peps.python.org/pep-0480/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt;-480&lt;/a&gt; et les discussions&amp;nbsp;attenantes).&lt;/p&gt;
&lt;p&gt;Il est possible de &lt;a href="https://twine.readthedocs.io/en/stable/"&gt;signer les paquets avec &lt;span class="caps"&gt;GPG&lt;/span&gt;&lt;/a&gt; mais sans trousseau de confiance, ça reste&amp;nbsp;aléatoire.&lt;/p&gt;
&lt;p&gt;Plutot de que de mal réinventer la roue, Python choisit l&amp;#8217;approche de Sigstore où &lt;a href="https://packaging.python.org/en/latest/specifications/index-hosted-attestations/"&gt;l&amp;#8217;entrepôt, Pypi, signe son index de paquets&lt;/a&gt; en s&amp;#8217;engageant sur &lt;a href="https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/"&gt;l&amp;#8217;identification des publieurs avec l&amp;#8217;authentification à deux facteurs (forte) obligatoire&lt;/a&gt; depuis fin&amp;nbsp;2023.&lt;/p&gt;
&lt;h4 id="nodejs"&gt;Nodejs&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://docs.npmjs.com/about-registry-signatures"&gt;Npm signe les index de son entrepôt avec &lt;span class="caps"&gt;ECDSA&lt;/span&gt; (et abandonne &lt;span class="caps"&gt;GPG&lt;/span&gt;)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification"&gt;Les publieurs peuvent utiliser l&amp;#8217;authentification à deux&amp;nbsp;facteurs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Npm ne permet pas de signer un&amp;nbsp;paquet.&lt;/p&gt;
&lt;h4 id="golang"&gt;Golang&lt;/h4&gt;
&lt;p&gt;Golang utilise un système non orthodoxe pour la &lt;a href="https://go.dev/doc/modules/publishing"&gt;publication de ses modules&lt;/a&gt;.
Le binaire d&amp;#8217;un module n&amp;#8217;est pas publié, mais uniquement ses sources, &lt;a href="https://go.dev/ref/mod#vcs"&gt;accessible par un tas de gestionnaire de source (&lt;em&gt;&lt;span class="caps"&gt;VCS&lt;/span&gt;&lt;/em&gt;)&lt;/a&gt; :
&lt;a href="https://en.wikipedia.org/wiki/GNU_Bazaar"&gt;bzr&lt;/a&gt; (le machin d&amp;#8217;Ubuntu),
&lt;a href="https://en.wikipedia.org/wiki/Fossil_(software)"&gt;fossil&lt;/a&gt;, git, &lt;a href="https://en.wikipedia.org/wiki/Mercurial"&gt;hg&lt;/a&gt; et &lt;a href="https://en.wikipedia.org/wiki/Apache_Subversion"&gt;svn&lt;/a&gt; (le &lt;span class="caps"&gt;CVS&lt;/span&gt; des dinos à la&amp;nbsp;page).&lt;/p&gt;
&lt;p&gt;Golang propose un &lt;a href="https://proxy.golang.org/"&gt;proxy&lt;/a&gt;, pour ne pas saturer les urls source, un &lt;a href="https://index.golang.org/"&gt;index&lt;/a&gt;, pour connaitre les mises à jour disponible et une &lt;a href="https://sum.golang.org/"&gt;base de données de checksum&lt;/a&gt; (dont les modifications sont auditables) qui contient les hachages des versions des modules, signés par Golang (la clef publique étant connu par la&amp;nbsp;commande &lt;code&gt;go&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Golang fait confiance au service qui héberge le code utilisant des urls de modules non ambigües et&amp;nbsp;universelles.&lt;/p&gt;
&lt;h4 id="dockeroci"&gt;Docker/&lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;L&amp;#8217;entrepôt &lt;span class="caps"&gt;OCI&lt;/span&gt; peu fournir la signature d&amp;#8217;une image.
L&amp;#8217;incontournable Sigstore est utilisé avec l&amp;#8217;outil &lt;a href="https://github.com/sigstore/cosign"&gt;cosign&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il est possible de téléverser et signer des images &lt;span class="caps"&gt;OCI&lt;/span&gt; sur l&amp;#8217;entrepôt open sourcre et éphémère &lt;a href="https://ttl.sh/"&gt;ttls.sh&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="php"&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://composer.github.io/pubkeys.html"&gt;Composer signe ses paquets avec une clef publique&lt;/a&gt; (deux en&amp;nbsp;fait).&lt;/p&gt;
&lt;p&gt;Un paquet Phar peut être &lt;a href="https://www.cnil.fr/fr/securite-chiffrement-hachage-signature"&gt;signé avec un hash (une belle confusion technique comme le rappelle la &lt;span class="caps"&gt;CNIL&lt;/span&gt;)&lt;/a&gt; ou avec &lt;span class="caps"&gt;OPENSSL&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://phar.io/how-it-works.html"&gt;Phar.io&lt;/a&gt; dit le contraire et vante la signature &lt;span class="caps"&gt;GPG&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Pour dire vrai, l&amp;#8217;écosystème &lt;span class="caps"&gt;PHP&lt;/span&gt; ne m&amp;#8217;intéresse plus depuis bien&amp;nbsp;longtemps.&lt;/p&gt;
&lt;h4 id="rust"&gt;Rust&lt;/h4&gt;
&lt;p&gt;Rust tergiverse sur l&amp;#8217;implémentation de signature de paquet et lorgne vers &lt;a href="https://theupdateframework.io"&gt;&lt;span class="caps"&gt;TUF&lt;/span&gt;&lt;/a&gt; dans la &lt;a href="https://github.com/rust-lang/rfcs/pull/3724"&gt;&lt;span class="caps"&gt;RFC&lt;/span&gt; dédiée&lt;/a&gt; et regarde du côté de&amp;nbsp;Sigstore.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="au-dela-des-paquets"&gt;Au delà des&amp;nbsp;paquets&lt;/h2&gt;
&lt;p&gt;Il n&amp;#8217;y a pas une technologie universelle de&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;approche raisonnable est d&amp;#8217;empiler les couches&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Une distribution Linux utilisant des paquets systèmes (au moins pour sa&amp;nbsp;création).&lt;/li&gt;
&lt;li&gt;Un conteneur minimaliste (variante &lt;em&gt;slim&lt;/em&gt;) qui montera en version plus régulièrement que son&amp;nbsp;hôte.&lt;/li&gt;
&lt;li&gt;Une application développée avec un nombre raisonnable de paquets spécifiques au langage, périodiquement audités (par un robot comme &lt;a href="https://github.com/google/osv-scanner"&gt;&lt;span class="caps"&gt;OSV&lt;/span&gt;-scanner&lt;/a&gt;) avec des tests pour vérifier la montée en version des dépendances, puis déployée par la &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les langages qui créent de gros blobs (golang, rust, java…) n&amp;#8217;ont pas forcément besoin de conteneurs, juste d&amp;#8217;isolation (cgroup,&amp;nbsp;namespace…).&lt;/p&gt;
&lt;p&gt;Si vous détestez vraiment les paquets, il vous faut de l&amp;#8217;unikerl (évoqué dans le billet &lt;a href="kernel-less.html"&gt;kernel-less&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Comme dit l&amp;#8217;adage&amp;nbsp;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Les paquets, c&amp;#8217;est&amp;nbsp;cadeau&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="Dev, Ops"></category><category term="package"></category><category term="oci"></category><category term="sigstore"></category><category term="entrepot"></category></entry><entry><title>Apprendre Pyxel : carte de tuiles et collision</title><link href="http://blog.garambrogne.net/pyxel-jones.html" rel="alternate"></link><published>2025-01-12T14:48:00+01:00</published><updated>2025-01-12T14:48:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-01-12:/pyxel-jones.html</id><summary type="html">&lt;p&gt;Nouvel épisode de l&amp;#8217;initiation à Pyxel : votre premier (prototype de) jeu de&amp;nbsp;plateforme&lt;/p&gt;</summary><content type="html">&lt;p&gt;Quatrième chapitre de l&amp;#8217;apprentissage de &lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt;, framework de retro game avec son moteur en Rust (pour la fluidité) et son &lt;span class="caps"&gt;API&lt;/span&gt; en Python (pour la&amp;nbsp;simplicité).&lt;/p&gt;
&lt;p&gt;Cette démo explore le &lt;em&gt;jeu de plateforme&lt;/em&gt; avec une &lt;em&gt;carte de tuiles&lt;/em&gt; créée dans un logiciel&amp;nbsp;spécifique.&lt;/p&gt;
&lt;h1 id="demo"&gt;Démo&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Jones" class="pixel right image-process-article-image" height="64" src="/images/pyxel-jones.png" /&gt;
Le héros, nommé Jones, explore les dangereuses ruines d&amp;#8217;un temple&amp;nbsp;oublié.&lt;/p&gt;
&lt;h2 id="assets"&gt;Assets&lt;/h2&gt;
&lt;p&gt;Jusqu&amp;#8217;à présent, les démos n&amp;#8217;utilisaient que peu de graphismes, mais là, pour expliquer l&amp;#8217;utilisation des &lt;em&gt;cartes&lt;/em&gt; et des &lt;em&gt;collisions&lt;/em&gt;, il va falloir plus de pixels, beaucoup&amp;nbsp;plus.&lt;/p&gt;
&lt;p&gt;Pour le défi, et pour avoir un aspect encore plus vintage, les sprites seront cette fois-ci de 8 pixels de&amp;nbsp;côté.&lt;/p&gt;
&lt;h3 id="carte-de-tuiles"&gt;Carte de&amp;nbsp;tuiles&lt;/h3&gt;
&lt;p&gt;Historiquement, pour gérer l&amp;#8217;affichage avec très peu de &lt;span class="caps"&gt;RAM&lt;/span&gt; et une carte graphique antique, tout était &lt;em&gt;tuiles&lt;/em&gt;.
L&amp;#8217;écran est alors rempli de gros carrés qui peuvent être&amp;nbsp;répétés.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mario Game Boy" class="right" src="/images/mario.png" /&gt;
La &lt;a href="https://fr.wikipedia.org/wiki/Game_Boy"&gt;Game Boy&lt;/a&gt;, par exemple, utilise un &lt;a href="https://hacktix.github.io/GBEDG/ppu/"&gt;Picture Processing Unit&lt;/a&gt; capable de gérer jusqu&amp;#8217;à 40 sprites de 8 x 8 ou 8 x 16 sur un écran de 160 x 144 pixels, sur 3 calques, et seul le dernier permet de positionner le sprite au pixel&amp;nbsp;près.&lt;/p&gt;
&lt;p&gt;Pyxel se contente de rendre hommage aux vieilles consoles, la plupart des contraintes sont des conventions, comme la palette réduite ou le tout petit&amp;nbsp;écran.&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;cartes&lt;/em&gt; permettent de composer une grande image (et même plus grande que l&amp;#8217;écran, permettant ainsi de déplacer la caméra pour un effet de scroll) à partir de &lt;em&gt;tuiles&lt;/em&gt;.
Certaines &lt;em&gt;tuiles&lt;/em&gt; , par convention, peuvent être considérées comme solides, pour que le personnage puisse marcher dessus et ne pas&amp;nbsp;tomber.&lt;/p&gt;
&lt;p&gt;Pyxel (arbitrairement) ne peut gérer que 3&amp;nbsp;cartes.&lt;/p&gt;
&lt;p&gt;Concrètement, les cartes permettent de travailler à 3 sans se marcher sur les&amp;nbsp;pieds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le graphiste crée le jeu de tuiles et le peaufine&amp;nbsp;ensuite.&lt;/li&gt;
&lt;li&gt;Le game designer compose la carte à partir du jeu de tuiles pour avoir quelque chose de jouable et&amp;nbsp;d’intéressant.&lt;/li&gt;
&lt;li&gt;Le développeur utilise la carte pour vérifier les actions (courir, sauter…) et les comportements (tomber, se cogner dans un mur…) du&amp;nbsp;personnage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pyxel fournit un éditeur de carte, mais il est un peu rugueux (comme tout ce que propose son éditeur d&amp;#8217;&lt;em&gt;assets&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt; est un éditeur de cartes un poil plus civilisé, et il est utilisable par quantité de moteurs de jeux.
Attention, Pyxel ne sait pas utiliser toutes les options de Tiled, ni tous les formats de&amp;nbsp;sortie.&lt;/p&gt;
&lt;h4 id="tiled"&gt;Tiled&lt;/h4&gt;
&lt;p&gt;Pyxel gère les cartes &lt;span class="caps"&gt;TMX&lt;/span&gt; avec les calques (pas plus de&amp;nbsp;3).&lt;/p&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;TSX&lt;/span&gt; (jeu de tuiles) doivent être embarqués dans la carte &lt;span class="caps"&gt;TMX&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;tuiles&lt;/em&gt; non définies dans la carte seront remplacées par le premier bloc du &lt;em&gt;jeu de tuiles&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pour que la carte gère la transparence (pour avoir un fond uni, par exemple), il faut utiliser une tuile uniforme, dont la couleur sera utilisée comme&amp;nbsp;transparence.&lt;/p&gt;
&lt;p&gt;La &lt;em&gt;palette de couleurs&lt;/em&gt; utilisée par Pyxel sera issue de la &lt;em&gt;carte&lt;/em&gt;.&lt;/p&gt;
&lt;h5 id="nouvelle-carte"&gt;Nouvelle&amp;nbsp;carte&lt;/h5&gt;
&lt;p&gt;Nouveau fichier&amp;nbsp;: &lt;code&gt;ctrl-N&lt;/code&gt;&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Orientation&lt;/dt&gt;
&lt;dd&gt;orthogonale&lt;/dd&gt;
&lt;dt&gt;Format de calque des&amp;nbsp;tuiles&lt;/dt&gt;
&lt;dd&gt;&lt;span class="caps"&gt;CSV&lt;/span&gt;&lt;/dd&gt;
&lt;dt&gt;Ordre d&amp;#8217;affichage des&amp;nbsp;tuiles&lt;/dt&gt;
&lt;dd&gt;En bas à&amp;nbsp;droite&lt;/dd&gt;
&lt;dt&gt;Taille de la&amp;nbsp;carte&lt;/dt&gt;
&lt;dd&gt;20 tuiles x 15 tuiles, soit 160 x 120&amp;nbsp;pixels&lt;/dd&gt;
&lt;dt&gt;Taille des&amp;nbsp;tuiles&lt;/dt&gt;
&lt;dd&gt;8 x 8&amp;nbsp;pixels&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Le &lt;em&gt;jeu de tuiles&lt;/em&gt; doit être disponible pour créer une carte, mais ne vous inquiétez pas, il est possible de modifier le &lt;span class="caps"&gt;PNG&lt;/span&gt;, Tiled le rechargera tout seul (sinon,&amp;nbsp;un &lt;code&gt;ctrl-r&lt;/code&gt; fera&amp;nbsp;l&amp;#8217;affaire).&lt;/p&gt;
&lt;p&gt;Tout en bas à droite, le&amp;nbsp;bouton &lt;code&gt;Nouveau jeu de tuiles…&lt;/code&gt;&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Nom&lt;/dt&gt;
&lt;dd&gt;mettez ce que vous voulez, sinon, le nom par défaut est basé sur le nom du&amp;nbsp;fichier.&lt;/dd&gt;
&lt;dt&gt;Type&lt;/dt&gt;
&lt;dd&gt;Basé sur l&amp;#8217;image du jeu de&amp;nbsp;tuiles&lt;/dd&gt;
&lt;dt&gt;Embarquer la&amp;nbsp;carte&lt;/dt&gt;
&lt;dd&gt;✔︎&lt;/dd&gt;
&lt;dt&gt;Source&lt;/dt&gt;
&lt;dd&gt;le chemin du &lt;span class="caps"&gt;PNG&lt;/span&gt;, sélectionnable via [ Parcourir…&amp;nbsp;]&lt;/dd&gt;
&lt;dt&gt;Largeur/Hauteur&lt;/dt&gt;
&lt;dd&gt;8&amp;nbsp;pix&lt;/dd&gt;
&lt;dt&gt;Marge/espacement&lt;/dt&gt;
&lt;dd&gt;0&amp;nbsp;pix&lt;/dd&gt;
&lt;dt&gt;Utiliser la couleur&amp;nbsp;transparente&lt;/dt&gt;
&lt;dd&gt;✔︎ puis le second carré, qui fait apparaitre
une mini-carte avec le &lt;span class="caps"&gt;PNG&lt;/span&gt; pour sélectionner la&amp;nbsp;couleur.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;La carte apparait dans le coin bas-droite, microscopique, il faut zoomer avec le menu déroulant, pour atteindre au moins les&amp;nbsp;400%.&lt;/p&gt;
&lt;h5 id="creer-sa-carte"&gt;Créer sa&amp;nbsp;carte&lt;/h5&gt;
&lt;p&gt;La grille dans la zone principale, trop petite et mal&amp;nbsp;centrée.
&lt;code&gt;ctrl-+&lt;/code&gt; et &lt;code&gt;ctrl--&lt;/code&gt; pour zoomer, sinon, on peut tricher&amp;nbsp;avec &lt;code&gt;ctrl-:&lt;/code&gt;.
Pour les adeptes du menu, ces réglages sont rangés&amp;nbsp;dans &lt;code&gt;Vue&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sélectionnez une &lt;em&gt;tuile&lt;/em&gt; dans le &lt;em&gt;jeu de tuiles&lt;/em&gt; en cliquant dessus, elle devient grisée.
Avec&amp;nbsp;l&amp;#8217;outil &lt;code&gt;Tampon&lt;/code&gt; (raccourci&amp;nbsp;: &lt;code&gt;B&lt;/code&gt;), vous pouvez tamponner de la &lt;em&gt;tuile&lt;/em&gt; à volonté sur la carte.
Pour l&amp;#8217;esthétique, pour les plateformes, faites attention à commencer par une tuile &lt;em&gt;début&lt;/em&gt;, terminer par une tuile &lt;em&gt;fin&lt;/em&gt;, et au milieu des tuiles &lt;em&gt;milieu&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Il ne faut pas utiliser le personnage (qui sera utilisé comme &lt;em&gt;sprite&lt;/em&gt;), la carte ne doit contenir que des éléments&amp;nbsp;statiques.&lt;/p&gt;
&lt;p&gt;Enregistrer le&amp;nbsp;fichier, &lt;code&gt;ctrl-s&lt;/code&gt;, au format &lt;span class="caps"&gt;TMX&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tiled" src="/images/pyxel-jones-tiled.png" /&gt;&lt;/p&gt;
&lt;h3 id="tuiles"&gt;Tuiles&lt;/h3&gt;
&lt;p&gt;La carte a besoin d&amp;#8217;éléments regroupés dans un fichier &lt;span class="caps"&gt;PNG&lt;/span&gt;.
Sélectionnez un éditeur spécialisé dans le pixel-art et non un gros éditeur qui sera frustrant (Krita, Gimp,&amp;nbsp;PowerPoint…).&lt;/p&gt;
&lt;p&gt;Comme outil libre et multi-plateformes, je vous propose&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.piskelapp.com/"&gt;Piskel&lt;/a&gt; est un éditeur web. &lt;span class="caps"&gt;OK&lt;/span&gt;, il est abandonné depuis 6 ans, mais il fonctionne bien, et il est possible de sauvegarder les fichiers source en&amp;nbsp;local.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twilightedge.com/mac/pikopixel/"&gt;Pikopixel&lt;/a&gt; est un éditeur conçu pour GNUStep, mais qui fonctionne sur la plupart des&amp;nbsp;plateformes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il doit surement en exister d&amp;#8217;autres, dont certains propriétaires, comme &lt;a href="https://pixenapp.com/"&gt;Pixen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Utilisez une palette de couleur réduite, ça aidera à rendre l&amp;#8217;ensemble cohérent, et ça rend hommage aux anciens.
Il y a des sites pour ça, comme &lt;a href="https://colorkit.co/color-palette-generator/"&gt;Colorkit&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="decors"&gt;Décors&lt;/h4&gt;
&lt;p&gt;Le plus important dans ce type de jeu sont les plateformes.
Il faut donc un début, un milieu, une fin de plateforme.
Pour simplifier la gestion des collisions, il faut que ces blocs utilisent au maximum les 8 pixels des&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Jeu de temples" class="pixel right" src="/images/pyxel-temple.png" width="256" /&gt;&lt;/p&gt;
&lt;p&gt;Il est aussi possible d&amp;#8217;utiliser des &lt;em&gt;tuiles&lt;/em&gt; pour décorer, qui ne gênent pas la progression du&amp;nbsp;personnage.&lt;/p&gt;
&lt;p&gt;Le &lt;em&gt;jeu de tuiles&lt;/em&gt; (32 x 32 pixels) de la démo est organisé par ligne&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La plateforme : début, milieu,&amp;nbsp;fin&lt;/li&gt;
&lt;li&gt;Des décors : herbes, torche,&amp;nbsp;vase&lt;/li&gt;
&lt;li&gt;Le héros : marche 1, marche 2, tombe,&amp;nbsp;saute&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les tuiles sont transparentes, mais pour la lisibilité, le héros a un contour noir (la couleur du fond dans le jeu), pour se détacher du&amp;nbsp;fond.&lt;/p&gt;
&lt;h2 id="les-ruines-vides"&gt;Les ruines&amp;nbsp;vides&lt;/h2&gt;
&lt;p&gt;On va commencer tranquillement, en se contentant d&amp;#8217;afficher le&amp;nbsp;décor.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;PNG&lt;/span&gt; est chargé dans l&amp;#8217;emplacement 1 de la banque&amp;nbsp;d&amp;#8217;images.&lt;/p&gt;
&lt;p&gt;Le calque 0 du &lt;span class="caps"&gt;TMX&lt;/span&gt; est chargé dans l&amp;#8217;emplacement 1 de la banque de&amp;nbsp;cartes.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;image 1 est connectée à la carte&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;Dans &lt;code&gt;draw&lt;/code&gt;, la carte est affichée.
Le troisième élément désigne la carte 1.
Le dernier élément désigne la couleur de&amp;nbsp;transparence.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The temple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;temple.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;incl_colors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tilemaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tilemap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_tmx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;temple.tmx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tilemaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imgsrc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# The map uses this image for its sprites&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="c1"&gt;# x, y, tm, u, v, w, h&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bltm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Le temple vide" src="/images/pyxel-temple-empty.png" /&gt;&lt;/p&gt;
&lt;p&gt;Il y a une astuce pour obtenir la palette et connaitre le rang des couleurs :
Pyxel fournit un raccourci pour sauvegarder la palette de couleurs sous forme de &lt;span class="caps"&gt;PNG&lt;/span&gt;&amp;nbsp;: &lt;code&gt;Ctrl-Shift-Alt-0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Palette" height="32" src="/images/pyxel-jones-palette.png" /&gt;&lt;/p&gt;
&lt;h2 id="tomber-dans-le-vide"&gt;Tomber dans le&amp;nbsp;vide&lt;/h2&gt;
&lt;p&gt;Pour bien isoler les parties du code, une &lt;em&gt;class&amp;nbsp;abstraite&lt;/em&gt; &lt;code&gt;Sprite&lt;/code&gt; est utilisée, ainsi qu&amp;#8217;un moteur (primitif) de&amp;nbsp;physiques.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Collisions&lt;/code&gt; connait la liste des tuiles solides (leurs coordonnées dans le &lt;em&gt;jeu de tuiles&lt;/em&gt;), qui ne peuvent pas être traversées.
Dans ce jeu, point de mur, juste du sol pour atterrir avec grâce.
Pour éviter les références&amp;nbsp;croisées, &lt;code&gt;Collisions&lt;/code&gt; est un attribut&amp;nbsp;de &lt;code&gt;App&lt;/code&gt; confié à la&amp;nbsp;méthode &lt;code&gt;update&lt;/code&gt; du &lt;code&gt;Sprite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le personnage&amp;nbsp;(un &lt;code&gt;Sprite&lt;/code&gt;) va pouvoir s&amp;#8217;avancer dans le vide au bord d&amp;#8217;une plateforme, jusqu&amp;#8217;à ce que son dernier pixel soit au-delà du dernier pixel de la&amp;nbsp;plateforme.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# 1: -&amp;gt; -1: &amp;lt;-&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Physics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;solids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]]):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;List of solid tiles&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solids&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Does the sprite have floor under its feet?&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# the tile before&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# the tile after&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# the tile under the feet of Jones&lt;/span&gt;
        &lt;span class="n"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tilemaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solids&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;too_low&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Does the sprite fall under the bottom of the screen?&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Jones&lt;/code&gt; hérite&amp;nbsp;de &lt;code&gt;Sprite&lt;/code&gt;.
De manière similaire aux chapitres&amp;nbsp;précédents, &lt;code&gt;Jones&lt;/code&gt; va gérer&amp;nbsp;un &lt;code&gt;state&lt;/code&gt; qui va permettre de choisir la bonne image, et de gérer la physique, essentiellement les sauts et les&amp;nbsp;chutes.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est le héros qui va gérer l&amp;#8217;affichage clignotant du tant redouté &lt;em&gt;&lt;span class="caps"&gt;GAME&lt;/span&gt; &lt;span class="caps"&gt;OVER&lt;/span&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;WAITING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;WALKING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;FALLING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;JUMPING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;DEAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Jones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images_right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images_left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;how_high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;death_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images_right&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images_left&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;FALLING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;WALKING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# move legs every 5 frames&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;JUMPING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# WAITING&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;physic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Collisions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DEAD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# there is no physics when you are a soul&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;JUMPING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;how_high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# start falling soon&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;too_low&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;death_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DEAD&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;FALLING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;  &lt;span class="c1"&gt;# soft landing&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FALLING&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WALKING&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;jump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JUMPING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;how_high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Collisions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WAITING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WALKING&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DEAD&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;death_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;physic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DEAD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;GAME OVER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L&amp;#8217;application, &lt;code&gt;App&lt;/code&gt;, est super classique.
Elle va charger les assets, instancier le moteur de physique avec les trois premières tuiles comme étant solides, puis instancier le héros, qui commence la partie sur une&amp;nbsp;chute.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The temple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;temple.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;incl_colors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tilemaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tilemap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_tmx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;temple.tmx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tilemaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imgsrc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# The map uses this image for its sprites&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Physics&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Jones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jones&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="c1"&gt;# x, y, tm, u, v, w, h&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bltm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jones&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les sources de &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/04_jones"&gt;Jones sont disponible sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;!--
Vous pouvez tester la version web de la [démo Jones](https://kitao.github.io/pyxel/wasm/launcher/?run=athoune.pyxel-initiation.04_jones.jones)
--&gt;

&lt;h2 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h2&gt;
&lt;p&gt;Le personnage pourrait courir (en plus de marcher) pour pouvoir faire des sauts plus&amp;nbsp;longs.&lt;/p&gt;
&lt;p&gt;Il pourrait aussi s&amp;#8217;accrocher quand il finit son saut sur un mur, puis remonter sur la plateforme (comme dans &lt;em&gt;Prince of Persia&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Il pourrait s&amp;#8217;arrêter en dérapant juste au bord du vide (comme dans &lt;em&gt;Flashback&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;ambiance, ce serait bien d&amp;#8217;avoir la flamme des torches animées (en recouvrant l&amp;#8217;image de fond par un autre&amp;nbsp;sprite).&lt;/p&gt;
&lt;p&gt;Les herbes pourraient onduler au passage du héros (et continuer quelques secondes&amp;nbsp;après).&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="jeu"></category><category term="débutant"></category><category term="rétro"></category></entry><entry><title>Learning Pyxel: Moving Sprites with the Mouse</title><link href="http://blog.garambrogne.net/pyxel-bat-en.html" rel="alternate"></link><published>2025-01-07T16:48:00+01:00</published><updated>2025-01-07T16:48:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-01-07:/pyxel-bat-en.html</id><summary type="html">&lt;p&gt;Now you know the basics of Pyxel, the retro game Rust framework for Python; let&amp;#8217;s move stuff with the&amp;nbsp;mouse.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here is a new chapter in the &amp;#8220;Learn Pyxel the Soft Way&amp;#8221; book.
&lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt; is a retrogaming framework built with Rust for the smoothness of the games, exposing a Python &lt;span class="caps"&gt;API&lt;/span&gt; for simplicity and&amp;nbsp;expressivity.&lt;/p&gt;
&lt;p&gt;This demo is a bit more complex than the previous one : keyboard and flying saucer.
Don&amp;#8217;t worry; the learning curve is still&amp;nbsp;flat.&lt;/p&gt;
&lt;h1 id="books-for-learning-python"&gt;Books for learning&amp;nbsp;Python&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/learning-python-6th/9781098171292/"&gt;&lt;img alt="Learning Python" class="right book" height="150px" src="/images/learning-python.jpg" /&gt;&lt;/a&gt;
&lt;a href="https://nostarch.com/python-kids-2nd-edition"&gt;&lt;img alt="Python for kids" class="right book" height="150px" src="/images/PythonforKids.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No Starch Press&amp;#8217; &lt;a href="https://nostarch.com/python-kids-2nd-edition"&gt;Python for Kids&lt;/a&gt;, second&amp;nbsp;edition&lt;/li&gt;
&lt;li&gt;O&amp;#8217;Reilly&amp;#8217;s &lt;a href="https://www.oreilly.com/library/view/learning-python-6th/9781098171292/"&gt;Learning Python&lt;/a&gt;, sixth&amp;nbsp;edition&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You already found a great book for learning Python, didn&amp;#8217;t&amp;nbsp;you?&lt;/p&gt;
&lt;p&gt;And you already start to read it&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Please, &lt;span class="caps"&gt;RTFB&lt;/span&gt; (Read The Python Book): you can&amp;#8217;t learn development from Youtube videos or copy-pasting&amp;nbsp;tutorials.&lt;/p&gt;
&lt;h1 id="demo"&gt;Demo&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Bat" class="right image-process-article-image" height="64px" src="/images/pyxel-bat.png" /&gt;
This time, with the mouse, the player will tell a cute bat where to fly in its dark&amp;nbsp;cave.&lt;/p&gt;
&lt;h2 id="assets"&gt;Assets&lt;/h2&gt;
&lt;p&gt;The assets are two sprites of the bat, two square images of 16x16px.
The first one has wings up, the second one has wings&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Éditeur de chauvesouris" src="/images/pyxel-bat-edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can also download the &lt;a href="/assets/pyxel/bat.pyxres"&gt;bat.pyxres&lt;/a&gt;&amp;nbsp;file.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;For clarity, the bat has its own &lt;em&gt;class&lt;/em&gt;&amp;nbsp;: &lt;code&gt;Bat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of a &lt;em&gt;class&lt;/em&gt; is to wrap all its&amp;nbsp;needs.&lt;/p&gt;
&lt;h3 id="a-bat-flapping-its-wings"&gt;A bat flapping its&amp;nbsp;wings&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;frame&lt;/code&gt; &lt;em&gt;attibute&lt;/em&gt; points to the current &lt;em&gt;sprite&lt;/em&gt; : 0 for wings up, 1 for wings&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;swap_wings&lt;/code&gt; &lt;em&gt;methode&lt;/em&gt; switches from one state to the other : up, down, up&amp;nbsp;…&lt;/p&gt;
&lt;p&gt;In&amp;nbsp;the &lt;code&gt;draw&lt;/code&gt; &lt;em&gt;method&lt;/em&gt;, the wings are swapped every 10 &lt;em&gt;frames&lt;/em&gt;, then drawn on the screen.
One flap every 10 seconds is slow, but it&amp;#8217;s a bat, not a&amp;nbsp;hummingbird.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;App&lt;/code&gt; &lt;em&gt;class&lt;/em&gt; is bare-bones: setting the game, starting the main loop.
The only difference with the flying&amp;nbsp;saucer &lt;code&gt;App&lt;/code&gt; is instantiating&amp;nbsp;the &lt;code&gt;Bat&lt;/code&gt; class and asking it to draw&amp;nbsp;itself.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to down.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Every 10 frames, the wings flip&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Load the assets.&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The game just drew a flapping bat in the middle of the screen.
You can do nothing, just quit when hitting the &lt;strong&gt;Q&lt;/strong&gt; key.
Yes, it&amp;#8217;s&amp;nbsp;frustrating.&lt;/p&gt;
&lt;h3 id="a-bat-following-the-mouse-when-you-click"&gt;A bat following the mouse when you&amp;nbsp;click&lt;/h3&gt;
&lt;p&gt;Show the cursor&amp;nbsp;with &lt;code&gt;pyxel.mouse(True)&lt;/code&gt;.
A left click&amp;nbsp;(&lt;code&gt;pyxel.MOUSE_BUTTON_LEFT&lt;/code&gt;) moves the bat to the cursor&amp;nbsp;position.&lt;/p&gt;
&lt;p&gt;If the left click is maintained, the bat follows the movements of the mouse.
You can shake the bat (but it&amp;#8217;s not nice to do&amp;nbsp;that).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to down.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 10 frames, the wings flip&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets.&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Show the mouse&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MOUSE_BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_x&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_y&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Yes! In this step you can do something.
Not so much, but&amp;nbsp;something.&lt;/p&gt;
&lt;h3 id="a-bat-flying-to-the-click-position"&gt;A bat flying to the click&amp;nbsp;position&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s remove the code that links the bat to the&amp;nbsp;mouse.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Coordonnées" class="right" src="/images/polar.png" /&gt;
In this ambitious step, some math (8th grade) will be used to compute the&amp;nbsp;path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;distance&lt;/code&gt; function computes the distance between two points with a little help from our friend&amp;nbsp;Euclid.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;angle&lt;/code&gt; function computes the angle from one point to the other (it&amp;#8217;s&amp;nbsp;trigonometry).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Technically, we switch from Cartesian to Polar&amp;nbsp;coordinates.&lt;/p&gt;
&lt;p&gt;The vector between the actual position and the clicked target is&amp;nbsp;computed.&lt;/p&gt;
&lt;p&gt;Every frame, the bat moves one step (its speed) following the vector.
If the distance between the bat and the target is lower than the step, the bat stops. Here, vectors and steps use real numbers (ℝ aka float), and the screen uses natural numbers (ℕ aka integer), and there are rounding troubles between&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Pyxel uses the top left corner of a sprite as its zero.
 Without correction, the top left corner of the bat will stop on the clicked target. It&amp;#8217;s counterintuitive and&amp;nbsp;ugly.&lt;/p&gt;
&lt;p&gt;The vector must use the center of the&amp;nbsp;sprite: &lt;code&gt;x + width / 2&lt;/code&gt; and &lt;code&gt;y + height / 2&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# self.x is the top left corner of the image,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# computing the position of the center of the bat is more intuitive&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Where does the bat look ?&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# The bat goes to this target.&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s2"&gt;&amp;quot;The bat rotates and will move to the target.&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotate_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;one_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s2"&gt;&amp;quot;The bat moves to its target.&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to down.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 10 frames, the wings swap&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="s2"&gt;&amp;quot;Euclidean distance&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="s2"&gt;&amp;quot;Get the angle between two points with trigonometry.&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets.&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Show the mouse&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MOUSE_BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Mouse click set the target&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, speed&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;one_step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# The bat flies, one step at a time&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The path is computed for each click; you can force the bat to swing chaotically if you click frenetically on the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="2 carrés" class="right" src="/images/carres.png" /&gt;&lt;/p&gt;
&lt;p&gt;In genuine 8-bit games, sprites are squares.
If you need to compute collisions, or if one sprite overlaps the other, don&amp;#8217;t bother with Euclid.
Just check if the pointer is lower than the top of the bat, higher than the bottom, further left than the right side, and further right than the left&amp;nbsp;side.&lt;/p&gt;
&lt;p&gt;You can modify the code for replacing the Euclid + Trigonometry part with the two squares dance as a tribute to the &lt;a href="https://fr.wikipedia.org/wiki/Game_Boy"&gt;Game Boy&lt;/a&gt; with its tiny Sharp Z80 processor, clocked at 4.194304 MHz, and its 8ko of &lt;span class="caps"&gt;RAM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;With the current computers, even a Raspberry Pi, nobody will notice the performance&amp;nbsp;difference.&lt;/p&gt;
&lt;p&gt;The sources of &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/02_bat"&gt;Bat are hosted on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="beyond"&gt;Beyond&lt;/h2&gt;
&lt;p&gt;Own the code, change it, break it.
Add missing&amp;nbsp;behaviors.&lt;/p&gt;
&lt;p&gt;A little bit of randomness in its flight to make it more&amp;nbsp;shaky?&lt;/p&gt;
&lt;p&gt;One more sprite for the standby position, upside down, wings folded&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Some &amp;#8220;flap flap&amp;#8221; sounds when the bat&amp;nbsp;flies?&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="game"></category><category term="newbie"></category><category term="retro"></category></entry><entry><title>Apprendre Pyxel : fuir le danger et game over</title><link href="http://blog.garambrogne.net/pyxel-beholder.html" rel="alternate"></link><published>2025-01-05T18:15:00+01:00</published><updated>2025-01-05T18:15:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2025-01-05:/pyxel-beholder.html</id><summary type="html">&lt;p&gt;Nouvel épisode de l&amp;#8217;initiation à Pyxel : votre héros va tenter de survivre au tir du rayon d&amp;#8217;un&amp;nbsp;beholder.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Troisième chapitre de l&amp;#8217;apprentissage de &lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt;, framework de retro game avec son moteur en Rust (pour la fluidité) et son &lt;span class="caps"&gt;API&lt;/span&gt; en Python (pour la&amp;nbsp;simplicité).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Beholder" class="image-process-article-image right pixel" src="/images/pyxel-beholder.png" /&gt;
Cette démo progresse en difficulté, avec de nouveaux concepts Python, et le risque de se faire tuer son&amp;nbsp;personnage.&lt;/p&gt;
&lt;p&gt;La démo va au-delà du monde des sprites, en traçant une ligne (qui reste heureusement&amp;nbsp;pixelisée).&lt;/p&gt;
&lt;h1 id="demo"&gt;Démo&lt;/h1&gt;
&lt;p&gt;Dans cette démo, un héros explore un donjon, et rencontre un &lt;a href="https://en.wikipedia.org/wiki/Beholder_(Dungeons_%26_Dragons)"&gt;beholder&lt;/a&gt;.
Le monstre va viser, tirer, tandis que le héros va tenter de&amp;nbsp;l&amp;#8217;esquiver.&lt;/p&gt;
&lt;p&gt;Le joueur va utiliser son clavier pour contrôler le&amp;nbsp;héros.&lt;/p&gt;
&lt;h2 id="assets"&gt;Assets&lt;/h2&gt;
&lt;p&gt;&lt;img :=":" alt="Beholder" src="/images/pyxel-beholder-edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;Les sprites sont rangés : par lignes, les personnages (et leurs états), par colonnes, la direction : bas (face), gauche, droite, haut&amp;nbsp;(dos).&lt;/p&gt;
&lt;p&gt;Le guerrier a un sprite de plus :&amp;nbsp;l&amp;#8217;électrocution.&lt;/p&gt;
&lt;h2 id="le-heros-se-balade"&gt;Le héros se&amp;nbsp;balade&lt;/h2&gt;
&lt;p&gt;Le guerrier peut se balader avec les flèches du clavier, il se tourne dans la bonne&amp;nbsp;direction.&lt;/p&gt;
&lt;h3 id="pythoneries"&gt;Pythoneries&lt;/h3&gt;
&lt;h4 id="comprehension-de-liste"&gt;Compréhension de&amp;nbsp;liste&lt;/h4&gt;
&lt;p&gt;Pour créer des collections (dont les &lt;em&gt;listes&lt;/em&gt;), Python dispose d&amp;#8217;une syntaxe&amp;nbsp;condensée.&lt;/p&gt;
&lt;p&gt;Le code suivant&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Est équivalent à&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Techniquement, ajouter des éléments à une liste est plus couteux que la syntaxe&amp;nbsp;dense.&lt;/p&gt;
&lt;h3 id="demo_1"&gt;Démo&lt;/h3&gt;
&lt;p&gt;Une&amp;nbsp;constante, &lt;code&gt;TRANSPARENT&lt;/code&gt;, est utilisée pour désigner la couleur de transparence, sans avoir à la recopier dans tout le code et pour faciliter la&amp;nbsp;lecture.&lt;/p&gt;
&lt;p&gt;Un personnage a un&amp;nbsp;attribut &lt;code&gt;speed&lt;/code&gt; qui correspond à son déplacement en pixels, à chaque &lt;em&gt;frame&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Les images sont ordonnées,&amp;nbsp;l&amp;#8217;attribut &lt;code&gt;angle&lt;/code&gt; décrit simultanément vers où regarde le personnage, et son rang dans la ligne&amp;nbsp;d&amp;#8217;images.&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;méthode &lt;code&gt;move&lt;/code&gt; déplace le personnage d&amp;#8217;un pas&amp;nbsp;(l&amp;#8217;attribut &lt;code&gt;speed&lt;/code&gt;), dans la bonne&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;Pour éviter une ribambelle&amp;nbsp;de &lt;code&gt;if&lt;/code&gt;, un multiplicateur est utilisé&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0 :&amp;nbsp;rien&lt;/li&gt;
&lt;li&gt;1 : en&amp;nbsp;avant&lt;/li&gt;
&lt;li&gt;-1 : en&amp;nbsp;arrière&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par contre, pour éviter au personnage de sortir de l&amp;#8217;écran, il faut une tartine&amp;nbsp;de &lt;code&gt;if&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La &lt;em&gt;class&lt;/em&gt; &lt;code&gt;App&lt;/code&gt; est très classique, les flèches du clavier sont utilisées pour déplacer&amp;nbsp;(avec &lt;code&gt;move&lt;/code&gt;) le personnage dans la bonne&amp;nbsp;direction.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The hero explores&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="le-heros-de-ballade-devant-le-beholder-qui-le-suit-du-regard"&gt;Le héros de ballade devant le beholder qui le suit du&amp;nbsp;regard&lt;/h2&gt;
&lt;h3 id="pythoneries_1"&gt;Pythoneries&lt;/h3&gt;
&lt;h4 id="heritage-de-class"&gt;Héritage de&amp;nbsp;class&lt;/h4&gt;
&lt;p&gt;Deux personnages sont décrits par deux &lt;em&gt;class&lt;/em&gt;, et comme elles ont pas mal de choses en commun (ce sont des &lt;em&gt;sprites&lt;/em&gt; avec une&amp;nbsp;direction).&lt;/p&gt;
&lt;p&gt;Une &lt;em&gt;class&lt;/em&gt; peut hériter d&amp;#8217;une autre &lt;em&gt;class&lt;/em&gt;.
La &lt;em&gt;class&lt;/em&gt; parente définit des attributs et des méthodes, les &lt;em&gt;class&lt;/em&gt; enfants peuvent avoir leurs propres attributs/méthodes ou &lt;em&gt;surcharger&lt;/em&gt; un élément de la &lt;em&gt;class&lt;/em&gt;&amp;nbsp;parente.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wazza&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il est possible pour une &lt;em&gt;méthode surchargée&lt;/em&gt; d&amp;#8217;&lt;em&gt;appeler&lt;/em&gt; la &lt;em&gt;méthode&lt;/em&gt; d&amp;#8217;un &lt;em&gt;ancêtre&lt;/em&gt; avec la&amp;nbsp;fonction &lt;code&gt;super()&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I say &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;, or not&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour obliger à surcharger une &lt;em&gt;méthode&lt;/em&gt;, la &lt;em&gt;méthode&lt;/em&gt; de la &lt;em&gt;class parente&lt;/em&gt;
doit &lt;em&gt;lever&lt;/em&gt; une &lt;em&gt;erreur&lt;/em&gt; &lt;code&gt;NotImplementedError&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;override_me&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="assignation-multiple"&gt;Assignation&amp;nbsp;multiple&lt;/h4&gt;
&lt;p&gt;La syntaxe d&amp;#8217;assignation d&amp;#8217;une variable dispose aussi d&amp;#8217;une syntaxe dense, avec des&amp;nbsp;raccourcis.&lt;/p&gt;
&lt;p&gt;Ici, seul l&amp;#8217;assignation multiple est utilisé, dans sa forme la plus simple&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce qui équivaut à&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="typage"&gt;Typage&lt;/h4&gt;
&lt;p&gt;Python a du &lt;a href="https://docs.python.org/3/library/typing.html"&gt;typage&lt;/a&gt; pour ses valeurs, mais pas de &lt;em&gt;typage fort&lt;/em&gt; : une &lt;em&gt;variable&lt;/em&gt; peut être réassignée avec une &lt;em&gt;valeur&lt;/em&gt; de &lt;em&gt;type&lt;/em&gt;&amp;nbsp;différent.&lt;/p&gt;
&lt;p&gt;Python utilise des &lt;em&gt;indices&lt;/em&gt; (&lt;em&gt;hints&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;) pour expliciter le type d&amp;#8217;une variable.
Python tient à garder le comportement historique : les &lt;em&gt;indices&lt;/em&gt; ne sont pas pris en compte par l&amp;#8217;interpréteur.
Le typage est destiné (entre autres) à l&amp;#8217;analyse statique (un robot lit et donne son avis sur votre&amp;nbsp;code).&lt;/p&gt;
&lt;p&gt;Les éditeurs modernes prennent en compte le typage pour ses suggestions, et pour raler (souligner en rouge) sur les parties de votre code qui ne respectent pas le&amp;nbsp;typage.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="c1"&gt;# a is an integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="operateur-ternaire"&gt;Opérateur&amp;nbsp;ternaire&lt;/h4&gt;
&lt;p&gt;Python dispose d&amp;#8217;une syntaxe dense pour assigner une valeur avec une&amp;nbsp;condition.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Devient:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Valeur&amp;nbsp;1, &lt;code&gt;if&lt;/code&gt;,&amp;nbsp;clause, &lt;code&gt;else&lt;/code&gt;, valeur&amp;nbsp;2.&lt;/p&gt;
&lt;p&gt;Attention à ne pas abuser des syntaxes denses qui peuvent anéantir la&amp;nbsp;lisibilité.&lt;/p&gt;
&lt;h3 id="demo_2"&gt;Démo&lt;/h3&gt;
&lt;p&gt;Les comportements de base&amp;nbsp;de &lt;code&gt;Hero&lt;/code&gt; sont maintenant dans la &lt;em&gt;class&lt;/em&gt; &lt;code&gt;Sprite&lt;/code&gt; qui sera la &lt;em&gt;class parente&lt;/em&gt;&amp;nbsp;de &lt;code&gt;Hero&lt;/code&gt; et &lt;code&gt;Beholder&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La &lt;em&gt;class&lt;/em&gt; &lt;code&gt;Beholder&lt;/code&gt; a un &lt;em&gt;attribut&lt;/em&gt; &lt;code&gt;state&lt;/code&gt;, et utilise 2 lignes&amp;nbsp;d&amp;#8217;images.
&lt;code&gt;state&lt;/code&gt; permet de savoir quelle ligne utiliser, la direction reste la colonne comme dans la démo précédente (utilisée dans la &lt;em&gt;méthode&lt;/em&gt; &lt;code&gt;image&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Le beholder peut faire les gros yeux à une cible avec la &lt;em&gt;méthode&lt;/em&gt; &lt;code&gt;watch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Un peu de maths : on calcule le delta entre les deux personnages sur l&amp;#8217;axe horizontal et&amp;nbsp;vertical, &lt;code&gt;dx&lt;/code&gt; et &lt;code&gt;dy&lt;/code&gt;.
Si la distance horizontale est supérieure à la distance verticale, le beholder regarde à l&amp;#8217;horizontal.&amp;nbsp;Si &lt;code&gt;dx&lt;/code&gt; est positif, il regarde à gauche, sinon à droite.
Même sport pour l&amp;#8217;axe&amp;nbsp;vertical.&lt;/p&gt;
&lt;p&gt;Dans la &lt;em&gt;class&lt;/em&gt; &lt;code&gt;App&lt;/code&gt;, la &lt;em&gt;méthode&lt;/em&gt; &lt;code&gt;update&lt;/code&gt; spécifie que la&amp;nbsp;touche &lt;code&gt;espace&lt;/code&gt; active&amp;nbsp;l&amp;#8217;état &lt;code&gt;LOADING&lt;/code&gt;
du beholder (il regarde en l&amp;#8217;air), et qu&amp;#8217;il surveille le&amp;nbsp;héros.&lt;/p&gt;
&lt;!-- Ajouter un schéma --&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
&lt;span class="c1"&gt;# Beholder states&lt;/span&gt;
&lt;span class="n"&gt;LOADING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;WAITING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# bottom, left, right, top&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# What is the current image&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder can be normal : waiting, aiming, moving…&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder loads its death ray&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The beholder watch the hero&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_SPACE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="le-beholder-essaye-de-foudroyer-le-heros"&gt;Le beholder essaye de foudroyer le&amp;nbsp;héros&lt;/h2&gt;
&lt;h3 id="pythoneries_2"&gt;Pythoneries&lt;/h3&gt;
&lt;p&gt;Pas de nouveautés Python dans cette dernière démo, il y a suffisamment de maths pour remplir les lignes de code&amp;nbsp;supplémentaires.&lt;/p&gt;
&lt;h3 id="demo_3"&gt;Démo&lt;/h3&gt;
&lt;p&gt;Le héros et le monstre ont maintenant l&amp;#8217;&lt;em&gt;attribut&lt;/em&gt; &lt;code&gt;state&lt;/code&gt; qui désigne (évidemment)&amp;nbsp;l&amp;#8217;état.&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;sprites&lt;/em&gt; du beholder sont rangés sur deux&amp;nbsp;lignes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La seconde quand il prépare son tir (il regarde en&amp;nbsp;l&amp;#8217;air)&lt;/li&gt;
&lt;li&gt;La première pour les autres états (attente et&amp;nbsp;tir).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le héros a une colonne de plus, l&amp;#8217;état électrocuté, qui n&amp;#8217;a pas de&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;Tout ça est traité par la&amp;nbsp;méthode &lt;code&gt;image&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour laisser une chance au héros, le beholder vise la position actuelle du héros, dans la&amp;nbsp;méthode &lt;code&gt;aim&lt;/code&gt;, attend un peu, le temps de charger son rayon de la mort, puis tire là où &lt;span class="caps"&gt;ETAIT&lt;/span&gt; le héros,&amp;nbsp;méthode &lt;code&gt;shoot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le rayon est instantané, mais il tire en retard.
Si le héros ne s&amp;#8217;est pas assez déplacé, il&amp;nbsp;meurt.&lt;/p&gt;
&lt;p&gt;Le départ et l&amp;#8217;arrivée du rayon se font depuis le centre des sprites, et non depuis le coin haut&amp;nbsp;gauche.&lt;/p&gt;
&lt;p&gt;Pour savoir si le héros est touché, on ne se contente pas de son centre (touché en plein cœur ?), mais on détermine la face de son sprite qui est face au beholder.
Si le rayon ne touche ne serait-ce qu&amp;#8217;un de ses orteils, le personnage est&amp;nbsp;frit.&lt;/p&gt;
&lt;p&gt;Le rayon est interrompu par le héros, s&amp;#8217;il le touche, sinon, il s&amp;#8217;arrête au bord de&amp;nbsp;l&amp;#8217;écran.&lt;/p&gt;
&lt;p&gt;Pour ne pas se perdre dans&amp;nbsp;des &lt;code&gt;if&lt;/code&gt; illisibles, un tableau de 3x3 cases est utilisé :
 - la ligne du milieu correspond à un tir vertical
 - la colonne du milieu a un tir horizontal
 - le centre a un tir dans son propre pied
 - les autres cases correspondent aux combinaisons de gauche/droite,&amp;nbsp;haut/bas&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est maintenant que débute la platrée de lignes de maths.
On commence par calculer la pente du tir, puis on gère le cas où la pente est nulle, puis on vérifie si le héros est touché latéralement, ou verticalement.
S&amp;#8217;il est touché, le rayon s&amp;#8217;arrête au point d&amp;#8217;impact, le héros passe à&amp;nbsp;l&amp;#8217;état &lt;code&gt;ZAPPED&lt;/code&gt; et le rayon change de&amp;nbsp;couleur.&lt;/p&gt;
&lt;p&gt;Le tir est dessiné avec la&amp;nbsp;fonction &lt;code&gt;line&lt;/code&gt; de&amp;nbsp;Pyxel.&lt;/p&gt;
&lt;p&gt;Dans&amp;nbsp;le &lt;code&gt;App&lt;/code&gt;, point de maths.
Les flèches ne font plus bouger le héros quand il agonise.
Au bout de 30 frames de douleur, le jeu&amp;nbsp;quitte.&lt;/p&gt;
&lt;p&gt;Le beholder cycle toutes les 40 frames, il vise à la frame 10, tire à la frame&amp;nbsp;30.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;

&lt;span class="n"&gt;TRANSPARENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;

&lt;span class="c1"&gt;# Beholder states&lt;/span&gt;
&lt;span class="n"&gt;LOADING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;FIRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;MOVING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;WAITING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# Hero states&lt;/span&gt;
&lt;span class="n"&gt;HEALTHY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ZAPPED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# bottom, left, right, top&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 0: don&amp;#39;t move&lt;/span&gt;
        &lt;span class="c1"&gt;# 1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# -1: move forward&lt;/span&gt;
        &lt;span class="c1"&gt;# angle : ↓←→↑&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
        &lt;span class="c1"&gt;# Can&amp;#39;t escape the screen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# What is the current image&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAITING&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder can be normal : waiting, aiming, moving…&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# The beholder loads its death ray&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# Where the beholder aims&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loading_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_main_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;aim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOADING&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;
        &lt;span class="c1"&gt;# The ray starts avec self.x, self.y&lt;/span&gt;
        &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;  &lt;span class="c1"&gt;# horizontal end of the ray&lt;/span&gt;
        &lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;  &lt;span class="c1"&gt;# vertical end of the ray&lt;/span&gt;

        &lt;span class="c1"&gt;# Center of the shooter&lt;/span&gt;
        &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="c1"&gt;# Center of the target&lt;/span&gt;
        &lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="c1"&gt;# Side hit by the shot&lt;/span&gt;
        &lt;span class="n"&gt;target_side_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;target_side_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

        &lt;span class="c1"&gt;# Where the ray ends when it misses ?&lt;/span&gt;

        &lt;span class="n"&gt;cross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# x, y&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
            &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sgn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# It shoots its own foot&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_aim_dx&lt;/span&gt;
            &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
            &lt;span class="n"&gt;y_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_side_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;x_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;
                &lt;span class="n"&gt;x_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_side_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shooter_x&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_side_y&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_side_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_hit&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;
        &lt;span class="n"&gt;bolt_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shooter_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shooter_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bolt_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_zapped_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRANSPARENT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HEALTHY&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HEALTHY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_zapped_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The beholder and its beam of death&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beholder.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Beholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Everything is frozen when the hero is zapped&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_DOWN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_UP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 50 frames, the beholder aims&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 25 later, it shoots&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;FIRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beholder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ZAPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;GAME OVER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les sources de &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/03_beholder"&gt;Beholder sont disponible sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Vous pouvez tester la version en ligne de
&lt;a href="https://kitao.github.io/pyxel/wasm/launcher/?run=athoune.pyxel-initiation.03_beholder.3_beholder"&gt;Démo Beholder en&amp;nbsp;ligne&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h2&gt;
&lt;p&gt;Le tir tue d&amp;#8217;un coup le héros, on pourrait utiliser un système de barre de vie qui décroit à chaque tir, ainsi qu&amp;#8217;une barre de chargement pour la préparation du tir du&amp;nbsp;beholder.&lt;/p&gt;
&lt;p&gt;Le héros ne peut pas riposter, quelle injustice.
Il faudrait des sprites avec avec une épée qui frappe (une fente avant ?), avec un temps (court) d&amp;#8217;attente lors d&amp;#8217;une attaque qui l&amp;#8217;empêche de se&amp;nbsp;déplacer.&lt;/p&gt;
&lt;p&gt;Un son de &lt;span class="caps"&gt;HUUUUUUM&lt;/span&gt; lors du chargement, puis de &lt;span class="caps"&gt;ZAAAAP&lt;/span&gt; lors du tir, un &lt;span class="caps"&gt;GZZZZZ&lt;/span&gt; lors de l&amp;#8217;électrocution me semble&amp;nbsp;indispensable.&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="jeu"></category><category term="débutant"></category><category term="rétro"></category></entry><entry><title>Apprendre Pyxel : gérer la souris dans son jeu</title><link href="http://blog.garambrogne.net/pyxel-bat.html" rel="alternate"></link><published>2024-12-09T18:48:00+01:00</published><updated>2024-12-09T18:48:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-12-09:/pyxel-bat.html</id><summary type="html">&lt;p&gt;Vous connaissez les bases de Pyxel, étudions maintenant comment faire bouger des trucs avec la&amp;nbsp;souris.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Voici un nouveau chapitre pour apprendre le framework de création de jeux &lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt;.
Oui, on dit cadriciel en français, mais cette traduction est tellement&amp;nbsp;laide.&lt;/p&gt;
&lt;p&gt;Cette nouvelle démo va être un poil plus compliqué que la soucoupe volante gérée au clavier.
Pas d&amp;#8217;inquiétudes, la marche entre les deux projets n&amp;#8217;est pas très&amp;nbsp;haute.&lt;/p&gt;
&lt;h1 id="bouquins-pour-apprendre-python"&gt;Bouquins pour apprendre&amp;nbsp;Python&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/learning-python-6th/9781098171292/"&gt;&lt;img alt="Learning Python" class="left book" height="200px" src="/images/learning-python.jpg" /&gt;&lt;/a&gt;
&lt;a href="https://www.eyrolles.com/Informatique/Livre/python-pour-les-kids-9782416013478/"&gt;&lt;img alt="Python pour les kids" class="right book" height="200px" src="/images/python-pour-les-kids.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;De toute façon, vous avez trouvé un bon bouquin pour apprendre Python et vous avez commencé à le lire, n&amp;#8217;est-ce pas&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Pour les grands, en anglais, il y a le Oreilly : &lt;strong&gt;Learning Python&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Il existe une traduction faite par une &lt;span class="caps"&gt;IA&lt;/span&gt; que je n&amp;#8217;aurai absoluement envie de&amp;nbsp;rire.&lt;/p&gt;
&lt;p&gt;Pour les jeunes, il y a le No Starch, traduit à la main par Eyrolles : &lt;strong&gt;Python pour les kids&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="demo"&gt;Démo&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Bat" class="left image-process-article-image" height="64px" src="/images/pyxel-bat.png" /&gt;
Cette fois-ci, avec sa souris, le joueur va faire voler une charmante chauve-souris dans l&amp;#8217;obscurité de sa&amp;nbsp;grotte.&lt;/p&gt;
&lt;p&gt;Pour ne pas s&amp;#8217;embrouiller entre &lt;strong&gt;chauve-souris&lt;/strong&gt; (le sprite) et &lt;strong&gt;souris&lt;/strong&gt; (le pointeur), on va déclarer que c&amp;#8217;est une &lt;strong&gt;pipistrelle&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="assets"&gt;Assets&lt;/h2&gt;
&lt;p&gt;Les assets contiennent deux sprites de la chauve-souris, deux images de 16 pixels posées côte à côte.
La première avec les ailes en haut, la seconde avec les ailes en&amp;nbsp;bas.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Éditeur de chauve-souris" src="/images/pyxel-bat-edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;Si vous avez la flemme de dessiner, le plus simple est de télécharger le fichier &lt;a href="/assets/pyxel/bat.pyxres"&gt;bat.pyxres&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Le code étant un poil plus compliqué que l&amp;#8217;initiation, la chauve-souris a sa&amp;nbsp;propre &lt;code&gt;class&lt;/code&gt; : &lt;strong&gt;Bat&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Comme tout bon code orienté objet, &lt;strong&gt;Bat&lt;/strong&gt; va gérer tout ce qui le&amp;nbsp;concerne.&lt;/p&gt;
&lt;h3 id="une-chauve-souris-qui-bat-des-ailes"&gt;Une chauve-souris qui bat des&amp;nbsp;ailes&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;attribut &lt;code&gt;frame&lt;/code&gt; indique l&amp;#8217;image courante, 0 pour les ailes en haut, 1 pour en&amp;nbsp;bas.&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;méthode &lt;code&gt;swap_wings&lt;/code&gt; va inverser la position des&amp;nbsp;ailes.&lt;/p&gt;
&lt;p&gt;Dans la&amp;nbsp;méthode &lt;code&gt;draw&lt;/code&gt; on va commencer par inverser la position des ailes toutes les 10 frames, avant de dessiner son sprite. Une fois sur 10 permet d&amp;#8217;avoir un battement lent, pas celui d&amp;#8217;un&amp;nbsp;colibri.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;App&lt;/code&gt; est minimaliste, il prépare le jeu puis démarre la boucle, seule nouveauté, il instancie&amp;nbsp;une &lt;code&gt;Bat&lt;/code&gt; et lui envoie les&amp;nbsp;évènements.&lt;/p&gt;
&lt;p&gt;Dans la première itération du code, il n&amp;#8217;y a pas&amp;nbsp;d&amp;#8217;interactions, &lt;code&gt;App&lt;/code&gt; se contente de demander à l&amp;#8217;instance&amp;nbsp;de &lt;code&gt;Bat&lt;/code&gt; de &lt;code&gt;draw&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to dwon&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Every 10 frames, to wings flip&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le jeu se contente d&amp;#8217;afficher une chauve-souris qui bat des ailes.
La seule interaction possible est de quitter le jeu, en taper sur la touche Q, oui, c&amp;#8217;est&amp;nbsp;frustrant.&lt;/p&gt;
&lt;h3 id="une-chauve-souris-qui-suit-la-souris-quand-on-clique"&gt;Une chauve-souris qui suit la souris quand on&amp;nbsp;clique&lt;/h3&gt;
&lt;p&gt;On commence par afficher le curseur de la souris&amp;nbsp;avec &lt;code&gt;pyxel.mouse(True)&lt;/code&gt;.
Un clique sur le bouton de gauche&amp;nbsp;(&lt;code&gt;pyxel.MOUSE_BUTTON_LEFT&lt;/code&gt;) déplace la pipistrelle à la position du&amp;nbsp;curseur.&lt;/p&gt;
&lt;p&gt;En maintenant le bouton, il est possible de lier la position de la pipistrelle au curseur de la souris, et de la faire gigoter en secouant la&amp;nbsp;souris.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to dwon&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 10 frames, to wings flip&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Show the mouse&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MOUSE_BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_x&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_y&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le peu d&amp;#8217;interaction est gratifiant, mais accrocher une bestiole aux mouvements de la souris reste&amp;nbsp;simpliste.&lt;/p&gt;
&lt;h3 id="une-chauve-souris-qui-volete-vers-un-point-designe-par-un-clic"&gt;Une chauve-souris qui volète vers un point désigné par un&amp;nbsp;clic&lt;/h3&gt;
&lt;p&gt;Commencez par effacer le bout de code qui lie la souris à la&amp;nbsp;pipistrelle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Coordonnées" class="right" src="/images/polar.png" /&gt;
Dans cette ambitieuse version, des maths (niveau 3°) vont être utilisées pour calculer le chemin&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;distance&lt;/code&gt; calcule la distance entre deux points avec l&amp;#8217;aide de notre ami&amp;nbsp;Euclide.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;angle&lt;/code&gt; calcule l&amp;#8217;angle entre deux points, avec de la&amp;nbsp;trigonométrie.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Techniquement, on passe de coordonnés cartésiens à coordonnées&amp;nbsp;polaires.&lt;/p&gt;
&lt;p&gt;Quand on clique, un chemin (un vecteur) est calculé entre la position courante de la chauve-souris et la position cible désignée par le&amp;nbsp;clic.&lt;/p&gt;
&lt;p&gt;À chaque frame, la chauve-souris avance d&amp;#8217;un pas (sa vitesse), sur ce vecteur.
 Si la distance entre la chauve-souris et sa cible est inférieure au pas, elle s&amp;#8217;arrête, ce qui permet de gérer simplement les problèmes de comptes qui ne tombent pas&amp;nbsp;rond.&lt;/p&gt;
&lt;p&gt;Pyxel utilise le coin haut gauche du sprite pour désigner sa position.
 Donc, sans correction, la chauve-souris stopperait quand son coin haut gauche atteint la position du clic, ce qui serait peu intuitif et même&amp;nbsp;laid.&lt;/p&gt;
&lt;p&gt;Pour les calculs de chemin et de distance, il faut utiliser la position du centre du sprite soit&amp;nbsp;: &lt;code&gt;x + width / 2&lt;/code&gt; et &lt;code&gt;y + height / 2&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# The bat uses two images : wings up, wings down&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# self.x is the top left corner of the image,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# computing the position of the center of the bat is more intuitive&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# 0 : bat wings are up, 1 : down&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Where does the bat look ?&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="c1"&gt;# The bat goes to this target&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s2"&gt;&amp;quot;The bat rotate and will move to the target&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotate_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;one_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s2"&gt;&amp;quot;The bat moves to its target&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;The wings switch from up to down&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Draw the sprite&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Every 10 frames, to wings swap&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_wings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="s2"&gt;&amp;quot;Euclidian distance&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="s2"&gt;&amp;quot;Get the angle between two points with trigonometry&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Flying bat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bat.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load the assets&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Spawn a new bat 🦇&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Show the mouse&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts Pyxel loop&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Hit Q to quit&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MOUSE_BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Mouse click set the target&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, speed&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;one_step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# The bat fly, one step at time&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Clear screen&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Draw the bat at its current position&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Comme le calcul du chemin et relancé à chaque clic, il est possible de faire voleter la chauve souris d&amp;#8217;un bout à l&amp;#8217;autre de l&amp;#8217;écran, sans qu&amp;#8217;elle ait le temps de&amp;nbsp;s&amp;#8217;arrêter.&lt;/p&gt;
&lt;p&gt;&lt;img alt="2 carrés" class="right" src="/images/carres.png" /&gt;&lt;/p&gt;
&lt;p&gt;Dans les vrais jeux 8 bits, avec des sprites carrés, pour savoir si deux blocs sont au même endroit, on n&amp;#8217;utilise pas Euclide, mais on vérifie simplement si le pointeur est plus bas que le haut de la pipistrelle, et plus bas que le haut, plus à gauche de sa droite, plus à droite que sa&amp;nbsp;gauche.&lt;/p&gt;
&lt;p&gt;Vous pouvez modifier le code pour rendre hommage à la &lt;a href="https://fr.wikipedia.org/wiki/Game_Boy"&gt;Game Boy&lt;/a&gt; et son processeur Sharp z80 cadencé à 4,194304 MHz, et ses 8ko de &lt;span class="caps"&gt;RAM&lt;/span&gt;.
Sur une machine récente, même un Raspberry Pi, personne ne verra la&amp;nbsp;différence.&lt;/p&gt;
&lt;p&gt;Les sources de &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/02_bat"&gt;Bat sont disponible sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;!--
Vous pouvez tester la version web de la [démo Bat](https://kitao.github.io/pyxel/wasm/launcher/?run=athoune.pyxel-initiation.02_bat.03_bat)
--&gt;

&lt;h2 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h2&gt;
&lt;p&gt;Prenez en main le code, pour rajouter des comportements qui vous semblent&amp;nbsp;indispensables.&lt;/p&gt;
&lt;p&gt;Un peu de random sur sa position pour rendre le vol un peu plus erratique&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Un sprite de plus pour avoir une position statique, la tête en bas, pour qu&amp;#8217;elle puisse se reposer&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Un son de &amp;#8220;flap flap&amp;#8221; joué quand la bête vole&amp;nbsp;?&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="jeu"></category><category term="débutant"></category><category term="rétro"></category></entry><entry><title>Initiation à la création de jeu rétro avec Pyxel</title><link href="http://blog.garambrogne.net/pyxel-initiation.html" rel="alternate"></link><published>2024-11-24T18:04:00+01:00</published><updated>2024-12-16T14:48:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-11-24:/pyxel-initiation.html</id><summary type="html">&lt;p&gt;Le framework Rust, Pyxel, permet de créer des jeux rétro en Python. Initiation et prise en&amp;nbsp;main.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Pyxel" class="left" src="/images/pyxel_logo_152x64.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt; permet de créer en Python des jeux rendant hommage aux vieilles consoles, comme la Game Boy Color : de gros pixels, une palette de couleur réduite, un son minimaliste (mais sur 4&amp;nbsp;canaux).&lt;/p&gt;
&lt;p&gt;Ce minimalisme permet de créer rapidement des jeux qui fonctionneront de manière fluide, grâce à sa bibliothèque en Rust, sur n&amp;#8217;importe quelle brouette, et même sur une page web (avec&amp;nbsp;Wasm).&lt;/p&gt;
&lt;h2 id="preparation"&gt;Préparation&lt;/h2&gt;
&lt;p&gt;Pyxel fonctionne sur Linux, Mac et&amp;nbsp;Windows.&lt;/p&gt;
&lt;p&gt;Le développement se passe depuis le &lt;strong&gt;terminal&lt;/strong&gt;, en tapant des&amp;nbsp;commandes.&lt;/p&gt;
&lt;h3 id="editeur-de-texte"&gt;Éditeur de&amp;nbsp;texte&lt;/h3&gt;
&lt;p&gt;Il vous faut un éditeur de texte : &lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt;, &lt;a href="https://www.sublimetext.com/"&gt;Sublime Text&lt;/a&gt;, ou un autre qui vous plait.
Ils sont disponibles en téléchargement depuis leur page web, ou via les appstores des différents &lt;span class="caps"&gt;OS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Sur Mac, ce sera &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;&amp;nbsp;avec &lt;code&gt;brew install vscode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sur Ubuntu, ce sera avec &lt;a href="https://snapcraft.io/"&gt;Snap&lt;/a&gt; disponible depuis la barre&amp;nbsp;d&amp;#8217;outils.&lt;/p&gt;
&lt;h3 id="python"&gt;Python&lt;/h3&gt;
&lt;p&gt;Vérifiez que vous avez Python dans une version décente (&amp;gt;= 3.10)&amp;nbsp;: &lt;code&gt;python3 -V&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Sinon, il faut l&amp;#8217;installer. Pour Mac, ce&amp;nbsp;sera &lt;code&gt;brew install python&lt;/code&gt;, pour Linux, ce&amp;nbsp;sera &lt;code&gt;sudo apt install python3-dev python3-venv python3-pip&lt;/code&gt;, pour Windows, ce sera via &lt;a href="https://www.python.org/downloads/windows/"&gt;python.org&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Pyxel n&amp;#8217;est pas une application, mais un framework Python, avec un éditeur de&amp;nbsp;médias.&lt;/p&gt;
&lt;p&gt;Donc, pour l&amp;#8217;utiliser, il suffit d&amp;#8217;avoir Python sur sa machine,
et de créer des projets, avec leur environnement,&amp;nbsp;(un &lt;code&gt;venv&lt;/code&gt;), puis d&amp;#8217;ajouter la&amp;nbsp;bibliothèque &lt;code&gt;pyxel&lt;/code&gt; avec&amp;nbsp;l&amp;#8217;outil &lt;code&gt;pip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour ne pas mettre le bazar, on va ranger le code dans un&amp;nbsp;dossier.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;pyxel-demo
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyxel-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Puis créer un environnement pour Python, pour pouvoir installer des bibliothèques uniquement pour ce projet et ne pas tout&amp;nbsp;cochonner.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;.pyxel-env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les utilisateurs&amp;nbsp;de &lt;code&gt;zsh&lt;/code&gt; et &lt;code&gt;bash&lt;/code&gt; (les shells par défaut de Linux et &lt;span class="caps"&gt;OS&lt;/span&gt; X) vont&amp;nbsp;utiliser &lt;code&gt;source&lt;/code&gt; :&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.pyxel-env/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour les transgressifs&amp;nbsp;de &lt;code&gt;fish&lt;/code&gt;, &lt;code&gt;venv&lt;/code&gt; sait aussi gérer ce shell&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;. .pyxel-env/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sur Windows, l&amp;#8217;activation utilise une commande&amp;nbsp;différente.&lt;/p&gt;
&lt;p&gt;Installation des bibliothèques. On commence par mettre à jour pip pour ne pas qu&amp;#8217;il râle, puis on&amp;nbsp;installe &lt;code&gt;pyxel&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;pip
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pyxel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="exemples-de-projets-pyxel"&gt;Exemples de projets&amp;nbsp;Pyxel&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;copy_examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pyxel va télécharger son dossier d&amp;#8217;exemple et créer le&amp;nbsp;dossier &lt;code&gt;pyxel_examples&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;commande &lt;code&gt;pyxel run&lt;/code&gt; permet de lancer les&amp;nbsp;démos.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pyxel_examples/02_jump_game.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Jump Game&lt;/em&gt; utilise les flèches droite/gauche pour accélérer/ralentir le&amp;nbsp;personnage.&lt;/p&gt;
&lt;h2 id="lediteur-de-medias-pour-pyxel"&gt;L&amp;#8217;éditeur de médias pour&amp;nbsp;Pyxel&lt;/h2&gt;
&lt;p&gt;Pyxel fournit un éditeur de médias (&lt;em&gt;sprites&lt;/em&gt;, &lt;em&gt;tilemap&lt;/em&gt;, &lt;em&gt;sound&lt;/em&gt;, &lt;em&gt;music&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Pyxel utilise des formats traditionnels (et même&amp;nbsp;antiques).&lt;/p&gt;
&lt;p&gt;Les images sont en &lt;span class="caps"&gt;PNG&lt;/span&gt; (la transparence est gérée).
Les jeux vidéo ont l&amp;#8217;habitude d&amp;#8217;avoir le zéro dans le coin haut gauche.
Il est possible de glisser/déposer des images &lt;span class="caps"&gt;PNG&lt;/span&gt;/&lt;span class="caps"&gt;GIF&lt;/span&gt;/&lt;span class="caps"&gt;JPG&lt;/span&gt; dans la fenêtre de&amp;nbsp;l&amp;#8217;éditeur.&lt;/p&gt;
&lt;p&gt;Les polices de caractères (les fonts, quoi) sont en pixel et au format &lt;span class="caps"&gt;BDF&lt;/span&gt;, éditables avec&amp;nbsp;FontForge.&lt;/p&gt;
&lt;p&gt;Les maps sont au format &lt;span class="caps"&gt;TMX&lt;/span&gt;, éditables avec &lt;a href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt; (plus de formats sont disponibles sur &lt;a href="https://github.com/mapeditor/tiled/releases"&gt;Github&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;L&amp;#8217;éditeur Pyxel crée des&amp;nbsp;archives &lt;code&gt;pyxres&lt;/code&gt; qui sont des fichiers &lt;span class="caps"&gt;ZIP&lt;/span&gt; avec un gros &lt;span class="caps"&gt;TOML&lt;/span&gt; à l&amp;#8217;intérieur, avec les binaires gérés comme des listes&amp;nbsp;d&amp;#8217;entiers.&lt;/p&gt;
&lt;p&gt;Le son est dans un format&amp;nbsp;spécifique.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;edit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="demo"&gt;Démo&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Space ship" class="image-process-article-image right" src="/images/pyxel-spaceship.png" /&gt;&lt;/p&gt;
&lt;p&gt;Demo party&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;Une fois ce premier test réussi, et quelques bidouillages pour se l&amp;#8217;approprier, il faudra lire &lt;a href="https://github.com/kitao/pyxel/blob/main/README.md"&gt;la doc, une longue page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le code est typé, mais non commenté, ce qui permet d&amp;#8217;avoir quelques indices depuis son&amp;nbsp;éditeur.&lt;/p&gt;
&lt;h3 id="editeur-de-medias"&gt;Éditeur de&amp;nbsp;médias&lt;/h3&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;edit&lt;span class="w"&gt; &lt;/span&gt;initiation.pyxres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dessinez deux vaisseaux spatiaux, de 16 pixels de&amp;nbsp;côté.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est votre vaisseau, vous dessinez bien ce que vous voulez, tant que vous respectez les&amp;nbsp;consignes.&lt;/p&gt;
&lt;p&gt;Le premier regarde à droite, le second, à&amp;nbsp;gauche.&lt;/p&gt;
&lt;p&gt;Par convention, la transparence est la première couleur (noir), et correspond à la valeur 0 dans le&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pyxel" src="/static/pyxel_edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;Si vous galérez, vous pouvez simplement télécharger le fichier &lt;a href="/assets/pyxel/spaceship.pyxres"&gt;initiation.pyxres&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="code"&gt;Code&lt;/h3&gt;
&lt;p&gt;Éditez un&amp;nbsp;fichier &lt;code&gt;initiation.py&lt;/code&gt;, puis adaptez le code d&amp;#8217;exemple (plus bas) à vos&amp;nbsp;besoins.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Uniquement sur UNIX&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;initiation.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="un-vaisseau-immobile"&gt;Un vaisseau&amp;nbsp;immobile&lt;/h4&gt;
&lt;p&gt;L&amp;#8217;application est rangée dans&amp;nbsp;une &lt;code&gt;class&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lors de la création de&amp;nbsp;la &lt;code&gt;class&lt;/code&gt;, on choisit la taille de l&amp;#8217;écran et son titre.&amp;nbsp;Les &lt;code&gt;assets&lt;/code&gt; sont&amp;nbsp;chargés.&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;méthode &lt;code&gt;update&lt;/code&gt; gère les entrées (le clavier dans ce&amp;nbsp;cas).&lt;/p&gt;
&lt;p&gt;La&amp;nbsp;méthode &lt;code&gt;draw&lt;/code&gt; va entièrement redessiner&amp;nbsp;l&amp;#8217;écran.&lt;/p&gt;
&lt;!--
La fonction pour dessiner un lutin (un *sprite*, quoi) utilise un long `tuple` ne facilitant pas la lecture.
J'ai utilisé une pythonerie `pyxel.blt(self.player_x, self.player_y, 0, *self.ship)`, l'étoile au début de la variable permet de compléter le `tuple` avec le contenu du `tuple` désigné par la variable.
--&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO static&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Demandez à Pyxel de surveiller votre&amp;nbsp;jeu.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;watch&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;initiation.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le vaisseau s&amp;#8217;affiche au milieu du vide de l&amp;#8217;espace, et on peut juste quitter l&amp;#8217;application avec la touche&amp;nbsp;Q.&lt;/p&gt;
&lt;h4 id="le-vaisseau-est-secoue"&gt;Le vaisseau est&amp;nbsp;secoué&lt;/h4&gt;
&lt;p&gt;Un peu de math, un simple cosinus, pour que le vaisseau rebondisse&amp;nbsp;mollement.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO shaken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# 🛸 bumps slowly 1/16 per tick starting with y = 72&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="le-vaisseau-peut-avancer-dans-un-sens-ou-dans-lautre"&gt;Le vaisseau peut avancer dans un sens ou dans&amp;nbsp;l&amp;#8217;autre&lt;/h4&gt;
&lt;p&gt;Le vaisseau peut pointer d&amp;#8217;un côté ou de l&amp;#8217;autre, grâce à deux&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;Avec les flèches, on peut avancer ou reculer de 2 pixels (sans sortir du&amp;nbsp;cadre).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO exploration&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks -&amp;gt;&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GAMEPAD1_BUTTON_DPAD_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# go -&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_l&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks &amp;lt;-&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GAMEPAD1_BUTTON_DPAD_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# go -&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks -&amp;gt;&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="c1"&gt;# 🛸 bumps slowly 1/16 per tick starting with y = 72&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voilà, vous avez un sprite qui gigote et réagit aux frappes sur un&amp;nbsp;clavier.&lt;/p&gt;
&lt;p&gt;Vous pouvez tester la version web de la &lt;a href="https://kitao.github.io/pyxel/wasm/launcher/?run=athoune.pyxel-initiation.01_spaceship.spaceship"&gt;démo Spaceship&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/01_spaceship"&gt;sources de Spaceship sont sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;!--
#### Démonstration

&lt;style&gt;
    .pyxel-screen {
      position: unset;
    left: unset;
    top: unset;
    }
    canvas.canvas {
    width: 640px;
    height: 480px;
    position: unset;
    left: unset;
    top: unset;
    image-rendering: pixelated;
    }
&lt;/style&gt;
&lt;script src="https://www.pyxelstudio.net/wasm/pyxel.js" type="text/javascript" charset="utf-8"&gt;&lt;/script&gt;

&lt;div id="pyxel-screen"&gt;
    &lt;pyxel-play
        root="/static/pyxel-demo"
        name="initiation.pyxapp"
        gamepad="enabled"
        &gt;&lt;/pyxel-play&gt;
        --&gt;
&lt;/div&gt;
&lt;h2 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h2&gt;
&lt;p&gt;Quelle sera la suite ? Du bruit, un décor avec des étoiles, des tirs de laser, des antagonistes&amp;nbsp;?&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="game"></category><category term="débutant"></category><category term="rétro"></category></entry><entry><title>Introduction to retrogame programming with Pyxel</title><link href="http://blog.garambrogne.net/pyxel-initiation-en.html" rel="alternate"></link><published>2024-11-24T18:04:00+01:00</published><updated>2024-12-16T14:48:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-11-24:/pyxel-initiation-en.html</id><summary type="html">&lt;p&gt;Lobsters : mm1bss/introduction_retrogame_programming
Summary: The Rust framework Pyxel has a simple Python &lt;span class="caps"&gt;API&lt;/span&gt; for building retrogames. Code your first demo!
Bluesky: 3lbymgiaofs2t
Thumbnail: /images/pyxel-spaceship.png
Thumbnail_small: images/thumbnail/pyxel-spaceship.png
Thumbnail_width: 158
Thumbnail_height:&amp;nbsp;158&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pyxel" class="left" src="/images/pyxel_logo_152x64.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt; is a tribute to game consoles of the golden age, like the &lt;span class="caps"&gt;SNES&lt;/span&gt; or …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lobsters : mm1bss/introduction_retrogame_programming
Summary: The Rust framework Pyxel has a simple Python &lt;span class="caps"&gt;API&lt;/span&gt; for building retrogames. Code your first demo!
Bluesky: 3lbymgiaofs2t
Thumbnail: /images/pyxel-spaceship.png
Thumbnail_small: images/thumbnail/pyxel-spaceship.png
Thumbnail_width: 158
Thumbnail_height:&amp;nbsp;158&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pyxel" class="left" src="/images/pyxel_logo_152x64.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kitao/pyxel"&gt;Pyxel&lt;/a&gt; is a tribute to game consoles of the golden age, like the &lt;span class="caps"&gt;SNES&lt;/span&gt; or the Game Boy Color: large pixels, few colors, and minimalistic sounds (on 4&amp;nbsp;channels).&lt;/p&gt;
&lt;p&gt;With such a minimalistic way, you can build games quickly, and they will run smoothly on a dog-slow computer (thanks, Rust), on every &lt;span class="caps"&gt;OS&lt;/span&gt;, even on a web page (with &lt;span class="caps"&gt;WASM&lt;/span&gt;).&lt;/p&gt;
&lt;h2 id="setup"&gt;Setup&lt;/h2&gt;
&lt;p&gt;Pyxel is tested on Linux, Mac, and&amp;nbsp;Windows.&lt;/p&gt;
&lt;p&gt;You have to use the &lt;strong&gt;terminal&lt;/strong&gt; and type commands the geeky&amp;nbsp;way.&lt;/p&gt;
&lt;h3 id="text-editor"&gt;Text&amp;nbsp;editor&lt;/h3&gt;
&lt;p&gt;You have to pick a text editor, like &lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt;, &lt;a href="https://www.sublimetext.com/"&gt;Sublime Text&lt;/a&gt;, or whatever works.
You can download them from their webpage or through an app&amp;nbsp;store.&lt;/p&gt;
&lt;p&gt;On Mac, you will use &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;vscode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Ubuntu, you will use &lt;a href="https://snapcraft.io/"&gt;Snap&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="python"&gt;Python&lt;/h3&gt;
&lt;p&gt;Check you have a decent Python installed (&amp;gt;=&amp;nbsp;3.10).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-V
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you don&amp;#8217;t have Python, or an old version, you have to install a fresh&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;On Mac, you will&amp;nbsp;type&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Linux, you will&amp;nbsp;type&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python3-dev&lt;span class="w"&gt; &lt;/span&gt;python3-venv&lt;span class="w"&gt; &lt;/span&gt;python3-pip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Windows, you will click-click on &lt;a href="https://www.python.org/downloads/windows/"&gt;python.org&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Pyxel is not an application but a framework. A media editor is&amp;nbsp;provided.&lt;/p&gt;
&lt;p&gt;So, to use it, you must have Python installed, create a project with its own virtual environment&amp;nbsp;(a &lt;code&gt;venv&lt;/code&gt;), install the &lt;em&gt;pyxel&lt;/em&gt; library with&amp;nbsp;the &lt;code&gt;pip&lt;/code&gt; tool.&lt;/p&gt;
&lt;p&gt;Don&amp;#8217;t be messy; put all your code in a dedicated&amp;nbsp;folder.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;pyxel-demo
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyxel-demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create a virtual environment in order to install libraries locally, only for this&amp;nbsp;project.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;.pyxel-env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Main shells used by Linux and &lt;span class="caps"&gt;OS&lt;/span&gt; X&amp;nbsp;are &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;zsh&lt;/code&gt; and&amp;nbsp;use &lt;code&gt;source&lt;/code&gt; to activate&amp;nbsp;the &lt;code&gt;venv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.pyxel-env/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fancy users&amp;nbsp;use &lt;code&gt;fish&lt;/code&gt; shell.
The command to activate&amp;nbsp;the &lt;code&gt;env&lt;/code&gt; is handled&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;. .pyxel-env/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Windows, the activation command will be different (but&amp;nbsp;similar).&lt;/p&gt;
&lt;p&gt;Install the library.
First,&amp;nbsp;update &lt;code&gt;pip&lt;/code&gt;, or it will yell, and then&amp;nbsp;install &lt;code&gt;pyxel&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;pip
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pyxel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="pyxel-examples"&gt;Pyxel&amp;nbsp;examples&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;copy_examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pyxel will download its own examples in&amp;nbsp;the &lt;code&gt;pyxel_examples&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;pyxel run&lt;/code&gt; to start&amp;nbsp;demos.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pyxel_examples/02_jump_game.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;Jump Game&lt;/em&gt; uses the right/left arrows to accelerate or decelerate the&amp;nbsp;hero.&lt;/p&gt;
&lt;h2 id="pyxel-media-editor"&gt;Pyxel Media&amp;nbsp;Editor&lt;/h2&gt;
&lt;p&gt;Pyxel provides a media editor for &lt;em&gt;sprites&lt;/em&gt;, &lt;em&gt;tilemap&lt;/em&gt;, &lt;em&gt;sound&lt;/em&gt; and &lt;em&gt;music&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pyxel handles traditional data&amp;nbsp;formats.&lt;/p&gt;
&lt;p&gt;Pictures are &lt;span class="caps"&gt;PNG&lt;/span&gt; (with transparency).
Be careful, video games used to put the zero in the top left corner.
You can upload a &lt;span class="caps"&gt;PNG&lt;/span&gt;/&lt;span class="caps"&gt;GIF&lt;/span&gt;/&lt;span class="caps"&gt;JPG&lt;/span&gt; file with a drag and drop in the editor&amp;nbsp;window.&lt;/p&gt;
&lt;p&gt;Fonts are pixel only and use the &lt;span class="caps"&gt;BDF&lt;/span&gt; format (you can edit them with&amp;nbsp;FontForge).&lt;/p&gt;
&lt;p&gt;Maps use the &lt;span class="caps"&gt;TMX&lt;/span&gt; format, editable with &lt;a href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pxyel editor creates&amp;nbsp;a &lt;code&gt;pyxres&lt;/code&gt; archive (a &lt;span class="caps"&gt;ZIP&lt;/span&gt; file with a fat &lt;span class="caps"&gt;TOML&lt;/span&gt; file&amp;nbsp;inside).&lt;/p&gt;
&lt;p&gt;Pyxel sounds use elderly synthetizer instructions using a specific&amp;nbsp;format.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;edit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="demo"&gt;Demo&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Space ship" class="image-process-article-image right" src="/images/pyxel-spaceship.png" /&gt;&lt;/p&gt;
&lt;p&gt;Demo party&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;OK&lt;/span&gt;, it works; you can launch and tweak the examples.
Now you should read the &lt;a href="https://github.com/kitao/pyxel/blob/main/README.md"&gt;doc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code uses type (yeah) without comments (woooh), so the text editor can provide a few&amp;nbsp;hints.&lt;/p&gt;
&lt;h3 id="media-editor"&gt;Media&amp;nbsp;editor&lt;/h3&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;edit&lt;span class="w"&gt; &lt;/span&gt;initiation.pyxres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Draw two spaceships, 16px tall and&amp;nbsp;wide.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s your own spaceship; draw wahtever you like, with the correct&amp;nbsp;sizes.&lt;/p&gt;
&lt;p&gt;The first spaceship looks to the right side and the second spaceship looks&amp;nbsp;left.&lt;/p&gt;
&lt;p&gt;The first color of the palette is transparent; it&amp;#8217;s the 0 value in the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pyxel" src="/static/pyxel_edit.png" /&gt;&lt;/p&gt;
&lt;p&gt;If you are struggling, you can cheat and download the file &lt;a href="/assets/pyxel/spaceship.pyxres"&gt;initiation.pyxres&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="code"&gt;Code&lt;/h3&gt;
&lt;p&gt;Create and edit a&amp;nbsp;file &lt;code&gt;initiation.py&lt;/code&gt;, then write the example code and tweak it as you&amp;nbsp;will.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# only for UNIX&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;initiation.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="a-static-spaceship"&gt;A static&amp;nbsp;spaceship&lt;/h4&gt;
&lt;p&gt;The application is put in&amp;nbsp;a &lt;code&gt;class&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In&amp;nbsp;the &lt;code&gt;__init__&lt;/code&gt; method, you pick a screen size and its name.&amp;nbsp;The &lt;code&gt;assets&lt;/code&gt; are&amp;nbsp;loaded.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;update&lt;/code&gt; method handles inputs (the keyboard,&amp;nbsp;here).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; method draws the full&amp;nbsp;screen.&lt;/p&gt;
&lt;!--
La fonction pour dessiner un lutin (un *sprite*, quoi) utilise un long `tuple` ne facilitant pas la lecture.
J'ai utilisé une pythonerie `pyxel.blt(self.player_x, self.player_y, 0, *self.ship)`, l'étoile au début de la variable permet de compléter le `tuple` avec le contenu du `tuple` désigné par la variable.
--&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO static&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ask Pyxel to watch the source of your&amp;nbsp;game.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyxel&lt;span class="w"&gt; &lt;/span&gt;watch&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;initiation.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The spaceship is in the middle of the void of space.
You can only quit the application with the Q&amp;nbsp;key.&lt;/p&gt;
&lt;h4 id="the-spaceship-is-shaken"&gt;The spaceship is&amp;nbsp;shaken&lt;/h4&gt;
&lt;p&gt;A little bit of match, just a cosine is used to bounce the spaceship&amp;nbsp;smoothly.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO shaken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;

        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# 🛸 bumps slowly 1/16 per tick starting with y = 72&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="the-spaceship-can-move-forward-or-downward"&gt;The spaceship can move forward or&amp;nbsp;downward&lt;/h4&gt;
&lt;p&gt;The spaceship can looks frony or back, using the two&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;Using the arrows, you can move forward or backward, with a step of two pixels.
You can&amp;#8217;t go outside the&amp;nbsp;screen.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyxel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UFO exploration&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# width, height, title&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initiation.pyxres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 X position&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 Y position&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# x, y, width, height, transparent color&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks -&amp;gt;&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btnp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_Q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_LEFT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GAMEPAD1_BUTTON_DPAD_LEFT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# go -&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_l&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks &amp;lt;-&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_RIGHT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GAMEPAD1_BUTTON_DPAD_RIGHT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# go -&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spaceship_r&lt;/span&gt;  &lt;span class="c1"&gt;# 🛸 looks -&amp;gt;&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="c1"&gt;# 🛸 bumps slowly 1/16 per tick starting with y = 72&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# clear screen with color 0&lt;/span&gt;

        &lt;span class="c1"&gt;# Draw 🛸&lt;/span&gt;
        &lt;span class="n"&gt;pyxel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ship&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here it is; you have a wiggling spaceship, and you can drive it with your&amp;nbsp;keyboard.&lt;/p&gt;
&lt;p&gt;Sources of &lt;a href="https://github.com/athoune/pyxel-initiation/tree/main/01_spaceship"&gt;Spaceship are hosted on Github&lt;/a&gt;.&lt;/p&gt;
&lt;!--
#### Démonstration

&lt;style&gt;
    .pyxel-screen {
      position: unset;
    left: unset;
    top: unset;
    }
    canvas.canvas {
    width: 640px;
    height: 480px;
    position: unset;
    left: unset;
    top: unset;
    image-rendering: pixelated;
    }
&lt;/style&gt;
&lt;script src="https://www.pyxelstudio.net/wasm/pyxel.js" type="text/javascript" charset="utf-8"&gt;&lt;/script&gt;

&lt;div id="pyxel-screen"&gt;
    &lt;pyxel-play
        root="/static/pyxel-demo"
        name="initiation.pyxapp"
        gamepad="enabled"
        &gt;&lt;/pyxel-play&gt;
        --&gt;
&lt;/div&gt;
&lt;h2 id="to-infinity-and-beyond"&gt;To infinity and&amp;nbsp;beyond&lt;/h2&gt;
&lt;p&gt;What&amp;#8217;s next?
Some noise, a background with stars, some laser beams, some&amp;nbsp;villains?&lt;/p&gt;</content><category term="Dev"></category><category term="pyxel"></category><category term="python"></category><category term="game"></category><category term="newbie"></category><category term="retro"></category></entry><entry><title>Kernel-less : allégez drastiquement vos VMs en supprimant le kernel</title><link href="http://blog.garambrogne.net/kernel-less.html" rel="alternate"></link><published>2024-08-05T09:04:00+02:00</published><updated>2024-08-05T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-08-05:/kernel-less.html</id><summary type="html">&lt;p&gt;Plutôt qu&amp;#8217;empiler les noyaux, faites confiance au noyau de l&amp;#8217;hôte et simplifier vos VMs en fusionnant votre application dans un noyau&amp;nbsp;minimaliste.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Les machines virtuelles sont fort pratiques pour utiliser efficacement les serveurs en les découpant en tranche de la bonne taille.
Un partage sans trop de soucis de sécurité (la surface d&amp;#8217;attaque est restreinte), permettant d&amp;#8217;avoir du multitenant (différents clients, potentiellement agressifs, partagent une&amp;nbsp;machine).&lt;/p&gt;
&lt;p&gt;À cause de Moore (enfin, de la fin de sa loi), les processeurs (génériques, aka &lt;span class="caps"&gt;CPU&lt;/span&gt;) prennent du retard face aux progrès du réseau, du stockage, et l&amp;#8217;arrivée d&amp;#8217;unités de calculs spécialisés (&lt;span class="caps"&gt;GPU&lt;/span&gt;, &lt;span class="caps"&gt;NPU&lt;/span&gt;, &lt;span class="caps"&gt;TPU&lt;/span&gt;…).&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://en.wikipedia.org/wiki/Kernel_(operating_system)"&gt;noyau&lt;/a&gt; a la charge de gérer le matériel et d&amp;#8217;arbitrer leur&amp;nbsp;accès.&lt;/p&gt;
&lt;p&gt;Quand le &lt;span class="caps"&gt;CPU&lt;/span&gt; pédalait plus vite que le réseau et le stockage, les latences du noyau était de différent ordre de grandeurs inférieures à celle du matériel, et donc anecdotiques.
Mais avec le réseau en 100G, le NVMe, et tous plein de nouveaux jouets qui essayent de saturer le PCIe, les latences du noyau deviennent hors de prix.
L&amp;#8217;article fondateur, &lt;a href="https://dl.acm.org/doi/10.1145/3015146"&gt;Attack of
the Killer
Microseconds&lt;/a&gt;, explique très bien tout ça, et j&amp;#8217;en ai déjà parlé dans le billet &lt;a href="tcpless.html"&gt;tcpless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Avec une machine virtuelle, on empile les noyaux : celui de l&amp;#8217;hôte (qui gère effectivement le matériel), et celui de l&amp;#8217;invité (qui fait un peu de la&amp;nbsp;figuration).&lt;/p&gt;
&lt;p&gt;Avec deux noyaux en cascade, il y a le risque de faire deux fois le travail (ou de prendre deux fois une décision sur un même&amp;nbsp;point).&lt;/p&gt;
&lt;p&gt;Grâce à &lt;a href="https://www.oasis-open.org/committees/virtio/"&gt;virtio&lt;/a&gt;, le noyau invité se fait le plus discret possible, avec un accès le plus simple et le plus direct avec le matériel, parfois même sans passer par le noyau de l&amp;#8217;hôte (en utilisant les fameux &lt;em&gt;rings&lt;/em&gt; et du &lt;em&gt;zero copy&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Le noyau invité a la charge de gérer les utilisateurs et les process dans la machine virtuelle.
Avec des micromachines virtuelles, comme dans un conteneur, il n&amp;#8217;y aura qu&amp;#8217;un seul utilisateur, une seule application (peut être même asynchrone, sans&amp;nbsp;threads).&lt;/p&gt;
&lt;p&gt;La tentation est donc grande de continuer d&amp;#8217;élaguer et de réduire la place du noyau dans les machines&amp;nbsp;virtuelles.&lt;/p&gt;
&lt;p&gt;On peut réduire un kernel générique au maximum, comme le propose &lt;a href="https://smolbsd.org/"&gt;smolbsd (avec NetBSD)&lt;/a&gt; ou comme le suggère &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/rootfs-and-kernel-setup.md"&gt;Firecracker (avec Linux)&lt;/a&gt;, mais on peut aussi faire le grand pas vers&amp;nbsp;l&amp;#8217;unikernel.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.isere-tourisme.com/equipements/le-tour-du-lac-petichet"&gt;&lt;img alt="Lac de Petichet" class="image-process-article-image" src="/images/lac-petichet.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="unikernel"&gt;unikernel&lt;/h2&gt;
&lt;p&gt;Le concept, audacieux, d&amp;#8217;un &lt;a href="https://en.wikipedia.org/wiki/Unikernel"&gt;unikernel&lt;/a&gt;, est d&amp;#8217;embarquer l&amp;#8217;application dans un kernel minimaliste, taillé sur mesure.
Une application classique a son code, ses bibliothèques, ses appels système.
Dans un unikernel, tout est bibliothèque, l&amp;#8217;application (compilé en statique) comme les appels système nécessaires, et forme un seul binaire, &lt;em&gt;bootable&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="isolation"&gt;Isolation&lt;/h3&gt;
&lt;p&gt;Comme l&amp;#8217;hôte assure l&amp;#8217;isolation de la machine virtuelle, celle-ci peut se permettre de ne rien isoler (pas d&amp;#8217;espace mémoire, pas de process, pas d&amp;#8217;utilisateur…), comme il n&amp;#8217;y a qu&amp;#8217;une seule application, elle n&amp;#8217;a rien à craindre de&amp;nbsp;quiconque.&lt;/p&gt;
&lt;h3 id="minimalisme"&gt;Minimalisme&lt;/h3&gt;
&lt;p&gt;Les unikernel ciblent la virtualisation (bien que des foufous fassent booter des Raspberry Pi avec des unikernels), ce qui simplifie&amp;nbsp;tout.&lt;/p&gt;
&lt;p&gt;Pour la gestion des drivers, il suffit de mettre du &lt;em&gt;virtio&lt;/em&gt; à tous les étages (ou du&amp;nbsp;Xen).&lt;/p&gt;
&lt;p&gt;Cette simplicité permet un temps de boot réduit (qui pousse à préférer Firecracker à Qemu, trop long à démarrer), des images légères, une consommation de mémoire moindre, et de très bonnes&amp;nbsp;performances.&lt;/p&gt;
&lt;p&gt;Quand on parle de simplicité, on est au niveau &amp;#8220;Linux embarqué&amp;#8221;, ces gens considèrent que &lt;em&gt;musl&lt;/em&gt; est une grosse &lt;em&gt;libc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Toutefois, les unikernels commencent par promettre un minimalisme absolu, des APIs uniques et parfaites, puis exposent une couche de compatibilité &lt;span class="caps"&gt;POSIX&lt;/span&gt;, pour finalement lancer des applications au format &lt;em&gt;elf&lt;/em&gt; (Le format&amp;nbsp;Linux).&lt;/p&gt;
&lt;h3 id="multicur"&gt;Multicœur&lt;/h3&gt;
&lt;p&gt;Le &lt;a href="https://en.wikipedia.org/wiki/Symmetric_multiprocessing"&gt;&lt;span class="caps"&gt;SMP&lt;/span&gt;&lt;/a&gt; (la gestion des multiples processeurs et cœurs) est un sujet complexe, surtout la partie coordination pour les accès exclusifs.
La virtualisation peut aisément aggraver le problème : si vCPU pose un verrou sur un &lt;span class="caps"&gt;CPU&lt;/span&gt; physique et que l&amp;#8217;hyperviseur le gèle pour le confier à une autre machine virtuelle (en cas de sur allocation), il va bloquer le verrou pour un temps imprévisible (et pourrir la gigue, &lt;em&gt;jitter&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Le papier &lt;a href="http://www.betriebssysteme.org/Aktivitaeten/Treffen/2008-Garching/Programm/docs/Abstract_Friebel.pdf"&gt;How to Deal with Lock Holder Preemption&lt;/a&gt; explique le problème et propose une&amp;nbsp;solution.&lt;/p&gt;
&lt;p&gt;Pour un &lt;span class="caps"&gt;OS&lt;/span&gt; spécifiquement conçu pour la virtualisation (comme l&amp;#8217;est un unikernel), il est possible de se passer de ce genre de&amp;nbsp;verrou.&lt;/p&gt;
&lt;h3 id="systeme-de-fichiers"&gt;Système de&amp;nbsp;fichiers&lt;/h3&gt;
&lt;p&gt;Autre sujet chaud, les systèmes de fichier, les unikernels détestent ça, il faut implémenter &lt;span class="caps"&gt;VFS&lt;/span&gt;, gérer les descripteurs de fichiers, puis les spécificités du système de fichiers, et paraphraser &lt;a href="https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)"&gt;&lt;span class="caps"&gt;LVM&lt;/span&gt;&lt;/a&gt; si on est&amp;nbsp;ambitieux.&lt;/p&gt;
&lt;p&gt;Pour avoir un ordre de grandeur, &lt;a href="https://e2fsprogs.sourceforge.net/"&gt;e2fsprogs&lt;/a&gt;, qui permet de gérer ext[2-4] en espace utilisateur, son&amp;nbsp;dossier &lt;code&gt;lib&lt;/code&gt; contient 197 fichiers en C pour 65 426 lignes de code seul&amp;nbsp;(du &lt;code&gt;sloc&lt;/code&gt; en jargon), sans tirer de dépendance tierce (d&amp;#8217;après le paquet Debian &lt;a href="https://packages.debian.org/bookworm/libext2fs2"&gt;libext2fs2&lt;/a&gt;).
En comparaison pour Nanos, il y a 1457 lignes pour &lt;span class="caps"&gt;TFS&lt;/span&gt;, leur &lt;span class="caps"&gt;FS&lt;/span&gt; maison et 814 lignes pour &lt;a href="https://wiki.qemu.org/Documentation/9p"&gt;9pfs&lt;/a&gt; (utilisé pour accéder à un disque de&amp;nbsp;l&amp;#8217;hôte).&lt;/p&gt;
&lt;p&gt;Sinon, un &lt;em&gt;ramfs&lt;/em&gt; suffit pour avoir&amp;nbsp;un &lt;code&gt;/tmp&lt;/code&gt; est le minimum pour assurer une&amp;nbsp;compatibilité.&lt;/p&gt;
&lt;p&gt;La réponse fantasmatique ultime serait que l&amp;#8217;application parle directement le NVMe, en écrivant ses blocs, sans l&amp;#8217;abstraction d&amp;#8217;un système de fichier.
Voici les &lt;a href="https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0c-2022.10.03-Ratified-1.pdf"&gt;spécifications&lt;/a&gt;, bon&amp;nbsp;courage.&lt;/p&gt;
&lt;p&gt;Utiliser un stockage clef/valeur peut être une abstraction plus légère qu&amp;#8217;un système de fichier (le stockage, lui, causera aux blocs). &lt;span class="caps"&gt;DPDK&lt;/span&gt; a le &lt;a href="https://spdk.io/doc/blob.html"&gt;blobstore&lt;/a&gt; et Ceph a &lt;a href="https://docs.ceph.com/en/latest/dev/config-key/"&gt;confg-key&lt;/a&gt; (bien moins ambitieux que le précédant, mais ils ont aussi exploré base clef/valeur comme LevelDB) par exemple, mais l&amp;#8217;intégration à unikernel reste&amp;nbsp;théorique.&lt;/p&gt;
&lt;p&gt;La solution raisonnable serait plutôt d&amp;#8217;utiliser &lt;a href="https://virtio-fs.gitlab.io/"&gt;virtio-fs&lt;/a&gt;, qui expose un dossier de l&amp;#8217;hôte (qui, lui, va gèrer un système de fichiers) via le protocole &lt;em&gt;zero copy&lt;/em&gt; de &lt;em&gt;virtio&lt;/em&gt;.
Basé sur &lt;em&gt;&lt;span class="caps"&gt;FUSE&lt;/span&gt;&lt;/em&gt;, mais avec le daemon en espace utilisateur sur l&amp;#8217;hôte, utilisé depuis l&amp;#8217;invité, avec les commandes &lt;span class="caps"&gt;FUSE&lt;/span&gt; qui utilisent le protocole de virtio, à base de &lt;em&gt;ring&lt;/em&gt; et de &lt;em&gt;mmap&lt;/em&gt;.
L&amp;#8217;unikernel Hermit, par exemple, peut utiliser du&amp;nbsp;virtio-fs.&lt;/p&gt;
&lt;p&gt;Virtio-fs est plébiscité par Kata, parfait pour du Serverless (et utilisé par AWSLambda), mais nécessite de la collaboration depuis l&amp;#8217;hôte, chose impossible si l&amp;#8217;on souhaite s&amp;#8217;appuyer sur l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;VM&lt;/span&gt; de son Cloud préféré qui va se contenter de connecter un disque de blocs à une machine&amp;nbsp;virtuelle.&lt;/p&gt;
&lt;p&gt;Techniquement, du &lt;a href="https://docs.ceph.com/en/latest/cephfs/"&gt;CephFS&lt;/a&gt; pourrait faire le job, mais je n&amp;#8217;ai aucune idée du surcout pour une image (code, mémoire, processeur), et du temps de connexion.
Une recherche rapide ne donne pas grand-chose : soit c&amp;#8217;est trop novateur, soit c&amp;#8217;est une mauvaise&amp;nbsp;idée.&lt;/p&gt;
&lt;p&gt;En attendant OSv embarque &lt;span class="caps"&gt;ZFS&lt;/span&gt; (qu&amp;#8217;on aurait du mal à qualifier de minimaliste), Nanos a son système de fichier maison (nommé &lt;span class="caps"&gt;TFS&lt;/span&gt;) et Unikraft a la&amp;nbsp;flemme.&lt;/p&gt;
&lt;p&gt;La tradition reste la mauvaise foi en affirmant &amp;#8220;les VMs sans état, c&amp;#8217;est la pureté&amp;#8221;.
Cette approche radicale est tout à fait légitime, c&amp;#8217;est un des piliers du dogme&amp;nbsp;Serverless.&lt;/p&gt;
&lt;h3 id="temps-de-demarrage"&gt;Temps de&amp;nbsp;démarrage&lt;/h3&gt;
&lt;p&gt;Démarrer une machine virtuelle classique prends du temps :
Il faut rapatrier l&amp;#8217;image de référence sur le serveur qui va instancier la machine avec différentes stratégies : téléchargement sur le disque local, puis éventuelle création d&amp;#8217;une instance en &lt;span class="caps"&gt;COW&lt;/span&gt; (copy on write), ou utilisation en flot depuis un disque distant depuis un &lt;span class="caps"&gt;SAN&lt;/span&gt;, avec là aussi un potentiel &lt;span class="caps"&gt;COW&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;La taille compte, plus l&amp;#8217;image sera de taille raisonnable, plus vite on pourra commencer à&amp;nbsp;l&amp;#8217;utiliser.&lt;/p&gt;
&lt;p&gt;Ensuite vient la partie matérielle, que la virtualisation simplifie grandement, le &lt;span class="caps"&gt;BIOS&lt;/span&gt;/&lt;span class="caps"&gt;UEFI&lt;/span&gt; a déjà fait le&amp;nbsp;travail.&lt;/p&gt;
&lt;p&gt;Le démarrage du kernel, là aussi, la partie drivers est normalisée par la virtualisation, et enfin l&amp;#8217;init, avec &lt;em&gt;systemd&lt;/em&gt;, un bon gros &lt;em&gt;cloud-init&lt;/em&gt; qui va mettre à jour des paquets, et enfin la ribambelle de services qui vont accompagner l&amp;#8217;application&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Allez jeter un œil au &lt;a href="https://depot.dev/blog/faster-ec2-boot-time"&gt;billet de depot.dev qui bataille pour booter en 5s, sans utiliser de pool d&amp;#8217;images&lt;/a&gt;
qui explique comme passer d&amp;#8217;un correct 40s pour démarrer à un excellent&amp;nbsp;5s.&lt;/p&gt;
&lt;p&gt;Pour un temps de démarrage qui se mesure en dizaines de secondes, les &lt;span class="caps"&gt;VMM&lt;/span&gt; (gestionnaires de machines virtuelles) sont tous suffisamment&amp;nbsp;rapides.&lt;/p&gt;
&lt;p&gt;Sauf qu&amp;#8217;en travaillant le temps de démarrage, pour du serverless par exemple, on passe à un temps en dixième de secondes avec Firecracker, comme le rappel &lt;a href="https://jvns.ca/blog/2021/01/23/firecracker--start-a-vm-in-less-than-a-second/"&gt;Julia Evans qui souhaite instancier des VMs à la demande pour ses joueurs&lt;/a&gt;.
Il y a ensuite d&amp;#8217;autres stratégies comme le stream d&amp;#8217;image de &lt;span class="caps"&gt;AWS&lt;/span&gt; Lambda, comme j&amp;#8217;en avais parlé dans mon billet &lt;a href="/stream-my-image.html"&gt;Flot d’images disque&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Un unikernel a tous les atouts pour démarrer super rapidement : une taille ascétique, une couche système minimaliste, et un seul&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Tous les efforts des unikernels pour démarrer rapidement peuvent être ruinés par les autres services requis pour instancier une machine&amp;nbsp;virtuelle.&lt;/p&gt;
&lt;h3 id="securite"&gt;Sécurité&lt;/h3&gt;
&lt;p&gt;Les unikernels vantent la sécurité de leur minimalisme et l&amp;#8217;isolation forte de la&amp;nbsp;virtualisation.&lt;/p&gt;
&lt;p&gt;Ils ne s&amp;#8217;arrêtent pas en si bon chemin et implémentent au mieux les fonctions de sécurité disponible sur les processeurs.
Pour ensuite pinailler sur le cout en performance, et le bienfondé de cette protection dans le cadre précis d&amp;#8217;une machine virtuelle avec un process, un utilisateur, un espace&amp;nbsp;mémoire.&lt;/p&gt;
&lt;p&gt;Certains unikernels peuvent même cibler des architectures exotiques dédiées à la sécurité, comme les enclaves Intel &lt;span class="caps"&gt;SGX&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="debug"&gt;Débug&lt;/h3&gt;
&lt;p&gt;Le service devient une fonction dans le noyau.
Tout le rangement d&amp;#8217;un &lt;span class="caps"&gt;UNIX&lt;/span&gt; classique avec des process et des espaces mémoire réservé, des appels systèmes saute.
Quand un bug apparait, il va falloir pouvoir déterminer qui accuser, le code système, ou le code&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Le problème de la virtualisation, c&amp;#8217;est que ça isole fort.
Pour que l&amp;#8217;invité puisse raconter ses soucis, il va falloir passer par un port série (aka la console) ou du&amp;nbsp;réseau.&lt;/p&gt;
&lt;p&gt;Par le réseau, on peut faire passer le&amp;nbsp;débugueur &lt;code&gt;gdb&lt;/code&gt;, et profiter de l&amp;#8217;interactivité (ça marche même depuis un &lt;span class="caps"&gt;IDE&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Normalement, on commence par vérifier que son application fonctionne en local (ou dans un conteneur), hors contexte unikernel, puis on teste dans&amp;nbsp;l&amp;#8217;unikernel.&lt;/p&gt;
&lt;p&gt;Le debug unikernel ne devrait être utilisé uniquement quand on tripote des paramètres de l&amp;#8217;unikernel, pas pour du code métier.
Ou alors, c&amp;#8217;est qu&amp;#8217;il y a un bug dans l&amp;#8217;unikernel ou une de ses bibliothèques système, et qu&amp;#8217;il faut comprendre l&amp;#8217;incident pour faire un ticket&amp;nbsp;circonstancié.&lt;/p&gt;
&lt;p&gt;Tous les outils permettant d&amp;#8217;envoyer la stack trace d&amp;#8217;une erreur, comme
Sentry, fonctionneront dans un&amp;nbsp;unikernel.&lt;/p&gt;
&lt;p&gt;Certains unikernels ont des options lors de la compilation pour que la fonction contenant le code métier soit emballée dans un try/catch et déclenche une action en cas de crash (comme afficher un dernier log dans la console&amp;nbsp;série).&lt;/p&gt;
&lt;p&gt;Les cas justifiant un debug en prod doivent être rare, et si ça arrive, bah ce sera pénible, comme un bug de qemu.
Il reste toujours la possibilité de router une partie du trafic sur une instance qui sera outillée pour un debug dans les conditions de la prod, soit un gdb depuis l&amp;#8217;hôte, soit, euh, l&amp;#8217;application hors du contexte unikernel&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Les offres serverless ne proposent pas de debug live (pas de serveur : pas de debug), ça doit juste marcher.
Avec Firecracker, &lt;span class="caps"&gt;AWS&lt;/span&gt; a dû assumer tous les bugs de virtualisation, mais la machine virtuelle est utilisé sans privilège (sans root) par le client, avec une distribution Linux maison.
Là, avec les unikernels, l&amp;#8217;utilisateur va devoir assumer la partie système de la virtualisation, avec la &lt;span class="caps"&gt;CI&lt;/span&gt; pour&amp;nbsp;recetter.&lt;/p&gt;
&lt;h3 id="observabilite"&gt;Observabilité&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;unikernel, par principe, méprise la notion de process et&amp;nbsp;son &lt;code&gt;/proc&lt;/code&gt; qui va avec, tout comme les &lt;em&gt;syscall&lt;/em&gt; (et&amp;nbsp;son &lt;code&gt;strace&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;analyse de traces dynamiques avec, &lt;em&gt;DTrace&lt;/em&gt;, &lt;em&gt;uprobe&lt;/em&gt; et &lt;a href="https://ebpf.io"&gt;&lt;em&gt;ebpf&lt;/em&gt;&lt;/a&gt; ne sont évidement pas disponible, mais les unikernels peuvent proposer la notion de traces et de hooks avec des &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;spécifiques.&lt;/p&gt;
&lt;p&gt;Les échantillonneurs, et leur fameux flamegraph, ne vont pas fonctionner du premier coup, car ils sont souvent un outil tiers qui va analyser un process, comme &lt;a href="https://github.com/benfred/py-spy"&gt;py-spy&lt;/a&gt;.
Les profileurs sous forme de bibliothèque, eux, vont fonctionner normalement, comme &lt;em&gt;pprof&lt;/em&gt;, dans sa version &lt;a href="https://pkg.go.dev/net/http/pprof"&gt;Golang&lt;/a&gt; ou &lt;a href="https://docs.rs/pprof/latest/pprof/"&gt;Rust&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Globalement, tous les outils embarqués dans le code vont fonctionner dans un unikernel : les journaux, traces, métriques, tout ce que propose &lt;a href="https://opentelemetry.io/"&gt;opentelemetry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour avoir des informations en boite noire, il faudra forcément passer par  l&amp;#8217;hôte, et l&amp;#8217;on pourra voir ce que consomme la machine virtuelle, mais guère plus.
Techniquement, tout ce qui cible &lt;em&gt;qemu&lt;/em&gt; devrait fonctionner, comme &lt;a href="https://margin.re/2022/05/cannoli-the-fast-qemu-tracer/"&gt;Cannoli&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="unikernels-monolangage"&gt;Unikernels&amp;nbsp;monolangage&lt;/h3&gt;
&lt;p&gt;Pour pousser un cran plus loin la sécurité, certains unikernels sont dédiés à un langage rigoriste&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Erlang avec &lt;a href="http://erlangonxen.org/"&gt;&lt;span class="caps"&gt;LING&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OCaml avec &lt;a href="https://mirage.io/"&gt;Mirage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Haskell avec &lt;a href="http://galois.com/project/halvm/"&gt;Galois&lt;/a&gt;&amp;nbsp;✞&lt;/li&gt;
&lt;li&gt;Wasm avec &lt;a href="https://github.com/JonasKruckenberg/k23"&gt;k23&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rust &lt;a href="https://hermitcore.org/"&gt;Hermit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;D&amp;#8217;autres ciblent des langages plus&amp;nbsp;fantaisistes&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FreePascal avec &lt;a href="https://torokernel.io/"&gt;Toro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Javascript avec &lt;a href="http://runtimejs.org/"&gt;runtime.js&lt;/a&gt;&amp;nbsp;✞&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cette hyperspécialisation est étrange, car un unikernel basé sur du C pourra lier n&amp;#8217;importe quel langage compilé, ou embarquer l&amp;#8217;interpréteur de n&amp;#8217;importe quel langage de script.
Utiliser un langage avec une forte abstraction par rapport à l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; permet de masquer à l&amp;#8217;utilisateur les efforts (fort couteux) de&amp;nbsp;portage.&lt;/p&gt;
&lt;p&gt;Dans l&amp;#8217;autre sens, des unikernels spécialisés peuvent s&amp;#8217;ouvrir à d&amp;#8217;autres langages : Hermit, dédié au calcul haute performance (&lt;span class="caps"&gt;HPC&lt;/span&gt;), peut gérer du C/C++, Fortran, Go et discuter en&amp;nbsp;OpenMP.&lt;/p&gt;
&lt;p&gt;Pour les fans de méta, les &amp;#8220;one ring to rule them all&amp;#8221;, il y a
&lt;a href="https://github.com/Solo5/solo5"&gt;Solo5&lt;/a&gt;, commencé comme un portage de Mirage pour &lt;span class="caps"&gt;KVM&lt;/span&gt; (depuis Xen), puis devient une abstraction imaginée comme pérenne pour utiliser différents unikernels.
&lt;span class="caps"&gt;IRL&lt;/span&gt;, Solo5 fonctionne avec &lt;strong&gt;Mirage&lt;/strong&gt; et &lt;a href="https://www.includeos.org/"&gt;IncludeOS&lt;/a&gt; (fan de C++). Les benchmarks ne sont pas flatteurs pour&amp;nbsp;Solo5.&lt;/p&gt;
&lt;h3 id="unikernels-generalistes"&gt;Unikernels&amp;nbsp;généralistes&lt;/h3&gt;
&lt;p&gt;Dans les unikernels généralistes, beaucoup sont cités (et comparer), mais peu sont encore bien vivants.
La short list semble être &lt;strong&gt;Osv&lt;/strong&gt;, &lt;strong&gt;Nanos&lt;/strong&gt; et &lt;strong&gt;Unikraft&lt;/strong&gt;, mais d&amp;#8217;autres conservent leur aura, comme &lt;strong&gt;Lumpin&lt;/strong&gt;, &lt;strong&gt;Rump&lt;/strong&gt; ou &lt;strong&gt;Solo5&lt;/strong&gt;.
&lt;strong&gt;Hermit&lt;/strong&gt; est encore très jeune, mais&amp;nbsp;prometteur.&lt;/p&gt;
&lt;h2 id="pret-pour-la-prod"&gt;Prêt pour la&amp;nbsp;prod&lt;/h2&gt;
&lt;p&gt;Les unikernels étaient une fantaisie pour chercheurs qui bootent, mais ils commencent à intéresser les&amp;nbsp;entreprises.&lt;/p&gt;
&lt;p&gt;Les unikernels ne sont pas apparus ex nihilo, ils sont la suite logique des µ conteneurs, qui sont la suite des conteneurs (qui sont la suite des &lt;span class="caps"&gt;WAR&lt;/span&gt; de Java, l&amp;#8217;hypothèse d&amp;#8217;une parenté avec les chroots est trop&amp;nbsp;banale).&lt;/p&gt;
&lt;p&gt;Les unikernels s&amp;#8217;insèrent facilement dans les workflows actuels, ils ne sont pas si disruptifs que&amp;nbsp;ça.&lt;/p&gt;
&lt;p&gt;Une &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; avec moult tests, puis de la métrologie (journaux, métriques, traces, rapports d&amp;#8217;erreurs) sont indispensables, mais bon, comme n&amp;#8217;importe quel service depuis au moins une&amp;nbsp;décennie.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;instant, 3 usages types émergent&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serverless&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;HPC&lt;/span&gt; (pour profiter du gain en&amp;nbsp;performance)&lt;/li&gt;
&lt;li&gt;Hébergement dense avec scale to zero (du mutu moderne,&amp;nbsp;quoi)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="osv"&gt;OSv&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://osv.io/"&gt;OSv&lt;/a&gt; a envoyé du rêve, en proposant un unikernel vantant sa compatibilité avec les binaires&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;OSv documente ses choix (et citent des white papers), et a fait le choix hétérodoxe d&amp;#8217;un système de fichier luxueux : &lt;span class="caps"&gt;ZFS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Petit souci, l&amp;#8217;entreprise derrière, &lt;a href="https://www.cloudius-systems.com/"&gt;Cloudius&lt;/a&gt;, a lâché l&amp;#8217;hébergement cloud pour tout miser sur &lt;a href="https://www.scylladb.com/"&gt;ScyllaDB&lt;/a&gt; (une base de données orientée colonnes, libre, clone de Cassandra, concurrente de&amp;nbsp;DynamoDB).&lt;/p&gt;
&lt;p&gt;Le projet est dorénavant maintenu par sa communauté, avec un rythme bien plus&amp;nbsp;mou.&lt;/p&gt;
&lt;p&gt;Unikraft remercie OSv pour les bouts de code qu&amp;#8217;ils ont&amp;nbsp;repris.&lt;/p&gt;
&lt;h3 id="nanos"&gt;Nanos&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://nanovms.com/"&gt;NanoVMs&lt;/a&gt; affirme avec aplomb que les unikernels vont buter les conteneurs d&amp;#8217;ici 5 ans, mais aussi qu&amp;#8217;ils permettent de se passer de&amp;nbsp;devops.&lt;/p&gt;
&lt;p&gt;De manière très classique, leur offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;, propriétaire, s&amp;#8217;appuie sur du code plus bas niveau, libre : un kernel et un cli permettant le build et le&amp;nbsp;déploiement.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nanos.org/"&gt;Nanos&lt;/a&gt; est l&amp;#8217;unikernel (sous licence Apache-2), avec sa chaine de build, &lt;a href="https://github.com/nanovms/ops"&gt;ops&lt;/a&gt; (sous licence &lt;span class="caps"&gt;MIT&lt;/span&gt;).&lt;/p&gt;
&lt;h4 id="en-local"&gt;En&amp;nbsp;local&lt;/h4&gt;
&lt;p&gt;De gros efforts ont été fournis pour faciliter la prise en main de Nanos : la documentation est conséquente,&amp;nbsp;et &lt;code&gt;ops&lt;/code&gt; rends bien des services à toutes les étapes du projet, que ce soit le build, le run (en local) et le&amp;nbsp;déploiement.&lt;/p&gt;
&lt;p&gt;Les langages compilés sont utilisés comme avant, il suffit de produire un binaire pour Linux.
La doc s&amp;#8217;amuse à fournir des exemples exotiques (forth, haskell, pascal, crystal, ada, nim,&amp;nbsp;odin…).&lt;/p&gt;
&lt;p&gt;Pour les langages interprétés, il suffit d&amp;#8217;utiliser un paquet disponible (une image dans un registre, quoi), pour construire son image.
La doc, de nouveau, s&amp;#8217;amuse avec des exemples exotiques, comme &lt;span class="caps"&gt;PHP&lt;/span&gt; ou&amp;nbsp;Perl.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;objectif est clairement de déployer efficacement son code métier, et non de batailler/pinailler avec les problématiques des&amp;nbsp;unikernels.&lt;/p&gt;
&lt;p&gt;Les images Nanos ciblent &lt;span class="caps"&gt;KVM&lt;/span&gt; (qemu et firecracker), mais aussi &lt;a href="https://bhyve.org/"&gt;Bhyve&lt;/a&gt; (sur FreeBSD), Xen, Hyper-V (sur Windows), VSphere, VMware ou même&amp;nbsp;Virtualbox.&lt;/p&gt;
&lt;p&gt;Un beau travail d&amp;#8217;intégration a été fait pour &lt;a href="https://docs.ops.city/ops/hypervisors/debugging"&gt;débuguer&lt;/a&gt;&amp;nbsp;avec &lt;code&gt;gdb&lt;/code&gt;, soit en cli (depuis la &lt;span class="caps"&gt;VM&lt;/span&gt; vers l&amp;#8217;hôte, donc), soit intégré dans son éditeur préféré (comme&amp;nbsp;VSCode).&lt;/p&gt;
&lt;h4 id="generation-cloud"&gt;Génération&amp;nbsp;Cloud&lt;/h4&gt;
&lt;p&gt;Nanos cible les différentes offres Cloud du marché pour déployer ses images.
Il cible les gros du Cloud (&lt;span class="caps"&gt;AWS&lt;/span&gt;, &lt;span class="caps"&gt;GCE&lt;/span&gt;, Azure), mais aussi les hébergeurs pour budgets serrés : Vultr, Linode, DigitalOcean, OpenStack (et donc &lt;span class="caps"&gt;OVH&lt;/span&gt;)…&lt;/p&gt;
&lt;p&gt;Le&amp;nbsp;cli, &lt;code&gt;ops&lt;/code&gt; est capable de lister, pousser, instancier les&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Il est possible de spécialiser ses images pour un hébergeur spécifique, pour utiliser correctement le service de machines virtuelles, avec les &lt;a href="https://docs.ops.city/ops/klibs"&gt;klibs&lt;/a&gt;, permettant ainsi d&amp;#8217;émettre des métriques ou du log, ou même profiter de cloud-init pour avoir de la config au boot du&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Ops sait manipuler les volumes distants (en utilisant les APIs de l&amp;#8217;hébergeur), et les brancher/débrancher, même à chaud sur une instance de&amp;nbsp;nanoVM.&lt;/p&gt;
&lt;p&gt;Pour pouvoir utiliser des disques en mode bloc, Nanos a fait l&amp;#8217;effort de créer son propre système de fichier, &lt;span class="caps"&gt;TFS&lt;/span&gt;.
Un utilitaire permettant de monter les images avec &lt;span class="caps"&gt;FUSE&lt;/span&gt; est fourni, permettant de trifouiller le&amp;nbsp;contenu.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TFS&lt;/span&gt; est un système de fichier nébuleux, &lt;a href="https://github.com/nanovms/nanos/blob/master/src/fs/tfs.c"&gt;en lisant les sources&lt;/a&gt;, on comprend qu&amp;#8217;il est journalisé (&lt;a href="https://github.com/nanovms/nanos/blob/master/src/fs/tlog.c"&gt;src/fs/tlog.c&lt;/a&gt;), et ne possède pas les fonctionnalités ambitieuses d&amp;#8217;un système de fichier moderne, comme le &lt;span class="caps"&gt;RAID&lt;/span&gt;, ou la création&amp;nbsp;d&amp;#8217;instantanés.&lt;/p&gt;
&lt;p&gt;Étonnement, Nanos n&amp;#8217;est pas super à l&amp;#8217;aise avec Kubernetes; oui, il va fonctionner avec &lt;a href="https://kubevirt.io/"&gt;KubeVirt&lt;/a&gt;, mais il estime que ça va dégrader son niveau de&amp;nbsp;sécurité.&lt;/p&gt;
&lt;p&gt;Nanos a une approche granboutienne, il considère qu&amp;#8217;un unikernel est une petite &lt;span class="caps"&gt;VM&lt;/span&gt;, et non un super conteneur, et qu&amp;#8217;il doit s&amp;#8217;intégrer dans l&amp;#8217;écosystème des machines virtuelles, et surtout pas se compromettre avec K8s (qui lui est dans la team&amp;nbsp;conteneur).&lt;/p&gt;
&lt;p&gt;Avec cette approche, Nanos s&amp;#8217;interdit les joies du serverless, ou alors il devra proposer un service avec un pool de serveurs bare-metal pour y&amp;nbsp;arriver.&lt;/p&gt;
&lt;p&gt;Avec son système de fichiers maison, &lt;span class="caps"&gt;TFS&lt;/span&gt;, Nanos fait un choix pragmatique pour gérer la persistance maintenant, partout, sur les offres Cloud&amp;nbsp;majeures.&lt;/p&gt;
&lt;p&gt;Sauf qu&amp;#8217;un système de fichier est par définition terrifiant, en dessous de 10 ans d&amp;#8217;âge, on ne le considère même pas, et ensuite, il faut assumer des pinaillages incessants sur les performances dont vont se plaindre les développeurs de bases de données, les ext4 vs &lt;span class="caps"&gt;XFS&lt;/span&gt;, les BtrFS vs &lt;span class="caps"&gt;ZFS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;virtio-fs&lt;/strong&gt; en confiant la persistance à l&amp;#8217;hôte contourne le problème, mais est-ce bien raisonnable d&amp;#8217;exiger qu&amp;#8217;un service &lt;span class="caps"&gt;HTTP&lt;/span&gt; gère les blocs de son disque dur alors que le reste de la gestion matérielle est abstraite par la&amp;nbsp;virtualisation?&lt;/p&gt;
&lt;h3 id="unikraft"&gt;Unikraft&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://unikraft.org/"&gt;Unikraft&lt;/a&gt; a une approche plus universitaire que Nanos, ce qui permet d&amp;#8217;avoir de chouette &lt;a href="https://unikraft.org/community/papers"&gt;white papers&lt;/a&gt; à lire, comme &lt;a href="https://arxiv.org/pdf/2104.12721.pdf"&gt;Unikraft: Fast, Specialized Unikernels the Easy Way&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le projet rassemble plusieurs universités, et bénéficie de l&amp;#8217;aide du Google Summer Of Code.
Unikraft a été adoubé par la prestigieuse fondation Linux (c&amp;#8217;est comme le &lt;a href="https://www.cncf.io/"&gt;&lt;span class="caps"&gt;CNCF&lt;/span&gt;&lt;/a&gt;, mais en plus&amp;nbsp;grand).&lt;/p&gt;
&lt;p&gt;Dans la tradition &lt;span class="caps"&gt;BSD&lt;/span&gt;, une startup complète la partie recherche, en proposant une offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;: &lt;a href="https://unikraft.io"&gt;Unikraft.io&lt;/a&gt; basé en Allemagne (en zone &lt;span class="caps"&gt;RGPD&lt;/span&gt;, donc, même si peu de détails apparaissent sur la matérialité de&amp;nbsp;l&amp;#8217;hébergement).&lt;/p&gt;
&lt;p&gt;Unikraft cible pour l&amp;#8217;instant Xen et &lt;span class="caps"&gt;KVM&lt;/span&gt; (qemu et Firecracker), pour les architectures x86_64, arm64 et&amp;nbsp;arm.&lt;/p&gt;
&lt;p&gt;Il est possible de créer des images pour des Clouds existants, fort pratiques pour faire des benchmarks, mais la documentation ne dit pas comment le faire.
Rien n&amp;#8217;est prévu pour avoir de la persistance hors virtio (pas de iscsi ni de NVMe-&lt;span class="caps"&gt;OF&lt;/span&gt;, pas de systèmes de fichiers en stock), impossible donc d&amp;#8217;utiliser les volumes&amp;nbsp;distants.&lt;/p&gt;
&lt;p&gt;Unikraft ne croit pas à l&amp;#8217;intégration aux offres Cloud actuelles, il souhaite repartir sur un socle simple et pour ça cible le bare metal, sans se soucier des offres&amp;nbsp;existantes.&lt;/p&gt;
&lt;p&gt;Unikraft a une relation avec les Clouds du marché opposée à celle de&amp;nbsp;Nanos.&lt;/p&gt;
&lt;h4 id="construire-une-image"&gt;Construire une&amp;nbsp;image&lt;/h4&gt;
&lt;p&gt;Unikraft a bien sûr son cli à tout&amp;nbsp;faire, &lt;code&gt;kraft&lt;/code&gt;, un gros binaire autonome, qui, en plus d&amp;#8217;une ressemblance assumée&amp;nbsp;à &lt;code&gt;docker&lt;/code&gt; pour lancer des instances, permet de construire une&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;instant, la seule cible est &lt;span class="caps"&gt;KVM&lt;/span&gt; (via &lt;span class="caps"&gt;QEMU&lt;/span&gt; ou Firecracker), d&amp;#8217;autres cibles sont prévus : Xen, &lt;a href="https://github.com/unikraft/unikraft/pull/614"&gt;VMWare&lt;/a&gt; et &lt;a href="https://github.com/unikraft/unikraft/issues/61"&gt;Hyper-V&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La présentation d&amp;#8217;Unikraft insiste bien sur les bienfaits de la compilation spécifique (et minimaliste), mais la documentation commence par expliquer comment créer un unikernel à partir d&amp;#8217;un binaire Linux, en utilisant un loader comme &lt;a href="https://github.com/unikraft/app-elfloader"&gt;app-elfloader&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le plus simple est de cannibaliser une image Docker, basée sur Debian ou Alpine selon l&amp;#8217;inspiration (ou la compatibilité&amp;nbsp;avec &lt;code&gt;musl&lt;/code&gt; plutôt&amp;nbsp;que &lt;code&gt;glibc&lt;/code&gt;).
Avec du multi-&lt;span class="caps"&gt;FROM&lt;/span&gt;, on recopie le strict nécessaire d&amp;#8217;une image officielle (pour déléguer la maintenance du binaire à un tiers de confiance), puis le build va recopier l&amp;#8217;arborescence minimaliste.
Oui, tout le travail se fait dans le Dockerfile, ce qui simplifie grandement la &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Le build va embarquer ce petit bout d&amp;#8217;arborescence dans le noyau, avec un &amp;#8220;embeded initrd&amp;#8221; en &lt;span class="caps"&gt;VO&lt;/span&gt;, &lt;a href="https://unikraft.org/docs/cli/filesystem#embedded-initial-ramdisk-filesystems-einitrds"&gt;einitrds&lt;/a&gt; pour les intimes, qui sera exposé comme &lt;em&gt;ramfs&lt;/em&gt; (en lecture seule) lors du démarrage de&amp;nbsp;l&amp;#8217;image.&lt;/p&gt;
&lt;p&gt;Pour des langages compilés traditionnels, c&amp;#8217;est un peu plus sport, il faut entrer dans le&amp;nbsp;conteneur, &lt;code&gt;strace&lt;/code&gt; le service pour découvrir ce&amp;nbsp;qu&amp;#8217;il &lt;code&gt;openat&lt;/code&gt;,&amp;nbsp;puis &lt;code&gt;ldd&lt;/code&gt; pour connaitre les bibliothèques liées, et avec ces indices créer une image&amp;nbsp;ascétique.&lt;/p&gt;
&lt;p&gt;Le&amp;nbsp;fichier &lt;code&gt;Kraftfile&lt;/code&gt;, qui malgré son nom hommage&amp;nbsp;à &lt;code&gt;(Make|Docker)file&lt;/code&gt; ne décrit aucune actions et ne contient que de la configuration, la magie se passant soit dans des options de compilation mystique pour du natif, ou un lien vers un Dockerfile qui aura tout&amp;nbsp;préparé.&lt;/p&gt;
&lt;p&gt;Allez voir le &lt;a href="https://github.com/unikraft/catalog"&gt;catalogue&lt;/a&gt; officiel, il est rempli d&amp;#8217;exemple, et vous pourrez juger de la lisibilité du&amp;nbsp;combo &lt;code&gt;Dockerfile&lt;/code&gt; + &lt;code&gt;Kraftfile&lt;/code&gt;.
Vous verrez aussi que la gestion des images arm n&amp;#8217;est pas encore systématique, bien que &lt;a href="https://unikraft.org/docs/internals/cross-compiling"&gt;la compilation croisée soit documentée&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour du C, C++, rust, vous pouvez aussi construire un vrai unikernel de puriste, dit &lt;em&gt;natif&lt;/em&gt; : votre application devient une bibliothèque de votre noyau.
Allez voir le dossier &lt;a href="https://github.com/unikraft/catalog/tree/main/native"&gt;native&lt;/a&gt; dans le&amp;nbsp;catalogue.&lt;/p&gt;
&lt;p&gt;Bien sûr, il est possible d&amp;#8217;activer le mode verbeux, puis, en désespoir, d&amp;#8217;utiliser l&amp;#8217;indispensable &lt;a href="https://unikraft.org/docs/internals/debugging"&gt;debug&amp;nbsp;avec &lt;code&gt;gdb&lt;/code&gt;&lt;/a&gt; pour comprendre pourquoi ça&amp;nbsp;résiste.&lt;/p&gt;
&lt;p&gt;Pour gérer des images dans des formats spécifiques, Unikraft fournit un &lt;a href="https://unikraft.org/docs/getting-started/integrations/hcp-packer"&gt;module pour Packer&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="ecrire-des-fichiers"&gt;Écrire des&amp;nbsp;fichiers&lt;/h4&gt;
&lt;p&gt;Unikraft reconnait que les systèmes de fichiers peuvent être utiles, pour une base de données par exemple.
Pour l&amp;#8217;instant, unikraft ne gère que &lt;em&gt;ramfs&lt;/em&gt; (pour avoir&amp;nbsp;un &lt;code&gt;/&lt;/code&gt; ou &lt;code&gt;/tmp&lt;/code&gt;) et &lt;a href="https://en.wikipedia.org/wiki/9P_(protocol)"&gt;9pfs&lt;/a&gt;, qui est un service réseau issu du mythique projet plan 9 du laboratoire Bell, dont on retrouve des petits bouts un peu&amp;nbsp;partout.&lt;/p&gt;
&lt;p&gt;Un &lt;a href="https://github.com/unikraft/unikraft/issues/10"&gt;ticket github&lt;/a&gt; évoque la gestion de systèmes de fichiers classique comme ext4 : ouvert en 2019, à part du rangement, il ne s&amp;#8217;est rien passé.
Cette indécision sur l&amp;#8217;intégration d&amp;#8217;un système de fichier dans un unikernel est légitime.
Déléguer ce point à l&amp;#8217;hôte est tellement plus simple, surtout que 9pfs (déjà disponible dans unikernel) le fait déjà, passer à &lt;em&gt;virtio-fs&lt;/em&gt; semble logique, avec un gain en performance à la&amp;nbsp;clé.&lt;/p&gt;
&lt;p&gt;En attendant, si vous voulez profiter d&amp;#8217;un volume distant, ce sera via l&amp;#8217;hôte, qui devra le monter, puis le&amp;nbsp;partager.&lt;/p&gt;
&lt;p&gt;KraftCloud expose des &lt;a href="https://docs.kraft.cloud/guides/features/volumes/"&gt;volumes&lt;/a&gt;, précise qu&amp;#8217;ils ont un système de fichier à leur création, en se gardant bien de préciser lequel. Comme tout est compilé hors de Kraftcloud, point d&amp;#8217;arme secrète, ce devrait être du&amp;nbsp;9pfs.&lt;/p&gt;
&lt;h4 id="exploitation"&gt;Exploitation&lt;/h4&gt;
&lt;p&gt;Les fans des &lt;a href="https://12factor.net/"&gt;12 facteurs&lt;/a&gt; seront&amp;nbsp;rassurés&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le build est prédictif, avec le&amp;nbsp;combo &lt;code&gt;Dockerfile&lt;/code&gt; + &lt;code&gt;Kraftfile&lt;/code&gt;, et adapté à une &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Le contrat avec l&amp;#8217;hôte est clair : &lt;span class="caps"&gt;KVM&lt;/span&gt; ou&amp;nbsp;Xen&lt;/li&gt;
&lt;li&gt;Déploiement sans admin : presque, ça reste une image de &lt;span class="caps"&gt;VM&lt;/span&gt;, par contre, la compatibilité cloud n&amp;#8217;est pas une priorité. L&amp;#8217;intégration avec &lt;a href="https://www.packer.io/"&gt;Packer&lt;/a&gt; permet au moins de gérer les différents formats&amp;nbsp;d&amp;#8217;images.&lt;/li&gt;
&lt;li&gt;Pour le scale, on reste dans les clous : service sans états que l&amp;#8217;on peut multiplier pour gérer la&amp;nbsp;charge.&lt;/li&gt;
&lt;li&gt;Configuration via les ENVs avec le module &lt;a href="https://github.com/unikraft/unikraft/tree/staging/lib/posix-environ"&gt;posix-environ&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Pour les logs, tout écrire dans &lt;span class="caps"&gt;STDOUT&lt;/span&gt;/&lt;span class="caps"&gt;STDERR&lt;/span&gt; ne va pas suffire. Cette recommandation est ringardisée par l&amp;#8217;arrivée d&amp;#8217;&lt;a href="https://opentelemetry.io/"&gt;opentelemtry&lt;/a&gt;, votre application va devoir communiquer à un service tiers ses journaux, traces, rapports d&amp;#8217;erreurs et diverses métriques. Le bon moment pour essayer &lt;a href="https://github.com/quickwit-oss/quickwit"&gt;Quickwit&lt;/a&gt;&amp;nbsp;?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;kraft&lt;/code&gt; Fournis les commandes équivalents à ce que propose Docker pour le cycle de vie des machines virtuelles, et il gère même les&amp;nbsp;fichiers &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour des architectures multi serveurs, il faudra passer par leur offre KraftCloud, ou&amp;nbsp;Kubernetes.&lt;/p&gt;
&lt;h5 id="ecosysteme-containerd-et-k8s"&gt;Écosystème containerd et&amp;nbsp;K8s&lt;/h5&gt;
&lt;p&gt;Unikraft fournit son propre runtime compatible &lt;a href="https://opencontainers.org/"&gt;&lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/a&gt; (&lt;a href="https://containerd.io/"&gt;Containerd&lt;/a&gt; ou cri-o)&amp;nbsp;: &lt;code&gt;runu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En créant des images d&amp;#8217;unikernel au format &lt;span class="caps"&gt;OCI&lt;/span&gt;, il est &lt;a href="https://unikraft.org/docs/getting-started/integrations/container-runtimes"&gt;possible de déployer des unikernels Unikraft dans un cluster Kubernetes&lt;/a&gt;, et même de panacher avec des conteneurs&amp;nbsp;classiques.&lt;/p&gt;
&lt;p&gt;Il est techniquement possible d&amp;#8217;envisager du serverless via k8s (Knative et ses amis), mais pour l&amp;#8217;instant, c&amp;#8217;est &lt;em&gt;qemu&lt;/em&gt; qui est utilisable, pas &lt;em&gt;Fireworks&lt;/em&gt;, il faut donc bien surveiller les temps de&amp;nbsp;démarrage.&lt;/p&gt;
&lt;h5 id="as-a-service"&gt;As A&amp;nbsp;Service&lt;/h5&gt;
&lt;p&gt;Unikraft.io est une offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;, Unikraft ayant fait le choix opposé de Nanos,
en daubant  la compatibilité historique, préférant cibler le bare metal, en gardant la main sur l&amp;#8217;hôte et la coordination.
D&amp;#8217;après ce que laisse transparaitre la doc, Unikraft.io est basé sur &lt;em&gt;Firecracker&lt;/em&gt;, &lt;em&gt;k8s&lt;/em&gt;, &lt;a href="https://www.crossplane.io/"&gt;crossplane&lt;/a&gt; et &lt;em&gt;Prometheus&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt;, utilisable depuis le cli en local ou une &lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Loadbalancer&lt;/li&gt;
&lt;li&gt;Autoscale&lt;/li&gt;
&lt;li&gt;Scale to&amp;nbsp;zero&lt;/li&gt;
&lt;li&gt;Déploiement continu (les outils sont conçus pour fonctionner depuis unne &lt;span class="caps"&gt;CI&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Mise à jour progressive (déploiement&amp;nbsp;bleu/vert…)&lt;/li&gt;
&lt;li&gt;Sondes&amp;nbsp;Prometheus&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Déploiement de certificats, ou création à la demande avec Lets&amp;nbsp;Encrypt&lt;/li&gt;
&lt;li&gt;Volumes&lt;/li&gt;
&lt;li&gt;Registre&amp;nbsp;d&amp;#8217;images&lt;/li&gt;
&lt;li&gt;Gestion de&amp;nbsp;docker-compose.yml&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Certains services paraissent pour l&amp;#8217;instant frustes (comme le load&amp;nbsp;balancer).&lt;/p&gt;
&lt;p&gt;Il manque des bouts&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stockage sans fond de type&amp;nbsp;S3&lt;/li&gt;
&lt;li&gt;Load balancing&amp;nbsp;interne&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par contre, Unikraft.io reprend la promesse d&amp;#8217;élasticité du Cloud : tu payes ce que les utilisateurs utilisent, de 0 à beaucoup.
Ce qui signifierait que les load balancers attendent gratuitement.
C&amp;#8217;est gentil, mais les crawlers assurent un trafic minimum&amp;nbsp;constant.&lt;/p&gt;
&lt;h2 id="les-unikernels-sont-ils-universels"&gt;Les Unikernels sont-ils universels&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;L&amp;#8217;assonance est belle, mais la technologie commence à peine à émerger des laboratoires après une petite décennie de&amp;nbsp;gestation.&lt;/p&gt;
&lt;p&gt;On va dire que les étoiles s&amp;#8217;alignent, ce qui permet l&amp;#8217;émergence de cette&amp;nbsp;technologie.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;KVM&lt;/span&gt; normalise l&amp;#8217;accès à l&amp;#8217;hôte avec &lt;em&gt;virtio&lt;/em&gt;, avec des services en zero copy, baissant encore le surcout de la virtualisation.
Rust s&amp;#8217;engouffre dans ce domaine, permettant l&amp;#8217;arrivée de Firecracker, fasciné par le démarrage rapide de machines&amp;nbsp;virtuelles.&lt;/p&gt;
&lt;p&gt;Moore se fait distancer par le stockage, le réseau et les *&lt;span class="caps"&gt;PU&lt;/span&gt; : le &lt;span class="caps"&gt;CPU&lt;/span&gt; devient le boulet des architectures contemporaines, ce qui remet en avant les langages compilés, domaine qui a aussi fait des efforts pour proposer des langages mieux bordés, avec un cycle de développement plus&amp;nbsp;raisonnable.&lt;/p&gt;
&lt;p&gt;La télémétrie a fait un bon en avant (merci Grafana), tout comme la notion de &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; : il est maintenant normal de ne plus pouvoir corriger une prod avec&amp;nbsp;un &lt;code&gt;vim&lt;/code&gt; en &lt;code&gt;ssh&lt;/code&gt; après une analyse rapide&amp;nbsp;avec &lt;code&gt;tail&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://webassembly.org/"&gt;Webassembly&lt;/a&gt;, conçu pour pouvoir recompiler Doom et lancer dans un navigateur web, s&amp;#8217;avère être finalement un redoutable concurrent aux unikernels, avec son bac à sable rigoureux et ses bonnes&amp;nbsp;performances.&lt;/p&gt;
&lt;p&gt;Le cloud a promis l&amp;#8217;élasticité il y a fort longtemps, et il est possible que ce soit les unikernels qui pourront tenir cette vieille&amp;nbsp;promesse.&lt;/p&gt;
&lt;p&gt;Les machines virtuelles en cascades fonctionnent mal, pour profiter des unikernels, il faut du bare metal, et les serveurs contemporains, pour rentabiliser la place dans les datacenters, sont très souvent énormes.
&lt;span class="caps"&gt;OVH&lt;/span&gt; sait rester raisonnable avec des processeurs spécifiques aux dédiés (&lt;span class="caps"&gt;AMD&lt;/span&gt; a une gamme pour ça), avec juste 12 cœurs délicats, alors qu&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt; tabasse directement avec des machines à 128 cœurs.
La moindre maquette va manger 3 serveurs, pour avoir de la redondance et des élections non&amp;nbsp;ambigües.&lt;/p&gt;
&lt;p&gt;La virtualisation, que ce soit &lt;span class="caps"&gt;KVM&lt;/span&gt; ou Xen a réussi l&amp;#8217;exploit de devenir transparent, on sait que ça existe, mais on ne l&amp;#8217;accuse jamais de bug ou de brimer la performance. Pour les bugs, on accuse le kernel et on est confiant qu&amp;#8217;une mise à jour corrigera, pour les performances, bah, on a la surallocation et le &lt;span class="caps"&gt;CPU&lt;/span&gt; steal comme bouc émissaire.
Les unikernels n&amp;#8217;ont pas encore ce totem d&amp;#8217;immunité, la virtualisation, &lt;span class="caps"&gt;OK&lt;/span&gt;, c&amp;#8217;est la même qu&amp;#8217;une bonne &lt;span class="caps"&gt;VM&lt;/span&gt; à l&amp;#8217;ancienne, mais le peu de code système de l&amp;#8217;unikernel, qui va-t-on blâmer? Pas les devs Linux, ils n&amp;#8217;y sont pour rien.
Comment gérer un éventuel incident en&amp;nbsp;prod?&lt;/p&gt;
&lt;h3 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h3&gt;
&lt;p&gt;Dans un monde parfait, la recherche de l&amp;#8217;efficience, et donc de la densification serait un objectif, mais bon, le dédié a encore beaucoup de succès, tout comme les technologies avec un cout &lt;span class="caps"&gt;CPU&lt;/span&gt;/utilisateur&amp;nbsp;désastreux.&lt;/p&gt;
&lt;p&gt;Ce n&amp;#8217;est pas une raison pour lâcher l&amp;#8217;affaire, la densification avance, ne serait-ce que pour des histoires de foncier ou de puissance disponible, le minimalisme est une des&amp;nbsp;pistes.&lt;/p&gt;
&lt;p&gt;La notion d&amp;#8217;Unikernel date des années 90, mais il a fallu la déferlante des conteneurs (et son incapacitant à faire du multitenant), puis le renouveau de la paravirtualisation (avec Firecracker) poussé le Serverless pour que des produits matures émergent&amp;nbsp;enfin.&lt;/p&gt;
&lt;p&gt;Il manque l&amp;#8217;épreuve du feu aux Unikernel pour gagner la confiance des utilisateurs, et pour ça, il faut que des utilisateurs utilisent pour trouver les bugs les plus vicieux, et surtout polissent les workflows de compilation.
Unikernel est presque prêt pour le grand public, et peut déjà servir d&amp;#8217;arme secrète pour les&amp;nbsp;aventureux.&lt;/p&gt;
&lt;p&gt;Ma prédiction est que les unikernels vont être utilisés pour des services en interne, très denses, et sans états : comme des load balancers, des services liés à la télémétrie, du&amp;nbsp;memcache.&lt;/p&gt;
&lt;p&gt;Première interaction avec les utilisateurs : du serverless, sans stockage, sans débug, avec pour commencer des runtimes prêt-à l&amp;#8217;emploi (python, ruby, node…), puis de l&amp;#8217;image unikernel issu d&amp;#8217;une &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;On peut imaginer que de gros utilisateurs veuillent faire baisser leur facture d&amp;#8217;hébergement en profitant des gains en performance et en densité des unikernels.
Comme ce qu&amp;#8217;a permis, sans trop d&amp;#8217;effort, les processeurs ARMs, plus efficients et aux performances stables (sans variations de fréquence pour gérer la&amp;nbsp;chaleur).&lt;/p&gt;
&lt;p&gt;Le stockage est pour l&amp;#8217;instant un caillou dans la chaussure d&amp;#8217;unikernel.
Soit l&amp;#8217;unikernel gère le disque distant (NVMeOF ?) et un système de fichier, soit l&amp;#8217;hôte &lt;em&gt;mount&lt;/em&gt; un volume distant dans un &lt;em&gt;namespace&lt;/em&gt; partagé avec la &lt;span class="caps"&gt;VM&lt;/span&gt; et l&amp;#8217;expose avec &lt;em&gt;virtio&lt;/em&gt; (choix que fait Lambda avec Firecracker), soit un portage de CephFS pour déporter le souci sur&amp;nbsp;Ceph.&lt;/p&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;API&lt;/span&gt; clouds pour les machines virtuelles ne sont pas entièrement adaptés aux unikernel, il manque une optimisation hyper agressive sur le temps de démarrage et une stratégie plus interventionniste sur l&amp;#8217;hôte s’il doit gérer les systèmes de fichiers pour&amp;nbsp;l&amp;#8217;invité.&lt;/p&gt;
&lt;p&gt;Les unikernels vont dans le sens de la simplification des architectures système, ce qui est toujours une bonne idée.
Il est temps que le droit d&amp;#8217;inventaire de la (para)virtualisation soit enfin effectué.
Je trouve personnellement réjouissant de pouvoir se passer du tentaculaire &lt;em&gt;systemd&lt;/em&gt; sans revenir à l&amp;#8217;infâme &lt;em&gt;initd&lt;/em&gt;, même si supprimer la notion même de kernel semble un peu excessif pour y&amp;nbsp;arriver.&lt;/p&gt;
&lt;p&gt;Small is&amp;nbsp;beautiful.&lt;/p&gt;</content><category term="Ops"></category><category term="unikernel"></category><category term="kvm"></category><category term="firecracker"></category><category term="serverless"></category><category term="lambda"></category></entry><entry><title>Tcpless ou comment passer le cap des cartes 100Gb</title><link href="http://blog.garambrogne.net/tcpless.html" rel="alternate"></link><published>2024-06-12T09:04:00+02:00</published><updated>2024-06-12T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-06-12:/tcpless.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt; n&amp;#8217;est plus adapté aux gros réseau, mais est-il le seul bloqueur pour accéder aux cartes réseaux 100G? Le &lt;span class="caps"&gt;CPU&lt;/span&gt; et le noyau Linux n&amp;#8217;ont-ils pas aussi leur part&amp;nbsp;?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ça plait bien, le thème du &amp;#8220;less&amp;#8221; en&amp;nbsp;informatique.&lt;/p&gt;
&lt;p&gt;Donc, suite au best seller  &lt;a href="diskless.html"&gt;diskless&lt;/a&gt;, le bouc émissaire  du jour est &lt;span class="caps"&gt;TCP&lt;/span&gt;, et donc ça va causer &lt;strong&gt;tcpless&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Comme entrée en matière, je recommande le brulot &lt;a href="https://arxiv.org/abs/2210.00714"&gt;It&amp;#8217;s Time to Replace &lt;span class="caps"&gt;TCP&lt;/span&gt; in the Datacenter&lt;/a&gt; (publié en 2022, amendé en&amp;nbsp;2023).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article est plaisant, sans plus, mais il fournit des sources (ce que promet la lecture de documents touffus écrits en LaTeX, avec au moins une page de citation à la fin, sans &lt;span class="caps"&gt;URL&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;auteur propose un nouveau protocole remplaçant &lt;span class="caps"&gt;TCP&lt;/span&gt; : &lt;a href="https://homa-transport.atlassian.net/wiki/spaces/HOMA"&gt;&lt;span class="caps"&gt;HOMA&lt;/span&gt;&lt;/a&gt;, dont on reparlera plus&amp;nbsp;tard.&lt;/p&gt;
&lt;p&gt;Dans les arguments, il y a deux chouettes buzzwords de Google&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La taxe&amp;nbsp;datacenter&lt;/li&gt;
&lt;li&gt;L&amp;#8217;attaque de la microseconde&amp;nbsp;tueuse&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;#8217;article &lt;a href="https://dl.acm.org/doi/abs/10.1145/2749469.2750392"&gt;Profiling a warehouse-scale computer&lt;/a&gt; &amp;#8220;profiler un ordinateur de la taille d&amp;#8217;un hangar&amp;#8221;,publié en 2015, analyse 3 ans de mesures sur les services Google, qui suit le précepte &amp;#8220;le datacenter est l&amp;#8217;ordinateur&amp;#8221; (on parle de milliers de serveurs), pour découvrir qu&amp;#8217;entre 22 et 27% du temps des &lt;span class="caps"&gt;CPU&lt;/span&gt; est utilisé par autre chose que le service métier :
bloat rpc, serialisation/deserialisation, compression/décompression, chiffrage/déchiffrage, hashage, allocation mémoire,&amp;nbsp;copies…&lt;/p&gt;
&lt;p&gt;Ce surcout est nommé &amp;#8220;la taxe datacenter&amp;#8221;, et la réponse est de tout miser sur l&amp;#8217;accélération matérielle&amp;nbsp;(l&amp;#8217;offloading).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article &lt;a href="https://dl.acm.org/doi/10.1145/3015146"&gt;Attack of
the Killer
Microseconds&lt;/a&gt; se lamente de la fin de la loi de Moore, les &lt;span class="caps"&gt;CPU&lt;/span&gt; contemporains devraient être 20 fois plus performant alors que le réseau ou le stockage lui a follement progressé.
Les ordinateurs ont été conçus pour gérer des millisecondes (réseau, disque dur…) avec un noyau qui a des latences en microsecondes (changement de contexte, interruptions, discussions entre threads&amp;nbsp;…).&lt;/p&gt;
&lt;p&gt;Tout allait bien jusqu&amp;#8217;à ce que le matériel passe à la microseconde (réseau en 10G et plus, disques &lt;span class="caps"&gt;SSD&lt;/span&gt; puis NVMe…), rendant les latences du noyau agaçantes. La moindre action (pour le code de Google) étant massivement parallélisée (on parle de millier de rpcs pour une recherche), la latence du pire élément du groupe devient la latence du&amp;nbsp;groupe.&lt;/p&gt;
&lt;p&gt;Autre point évoqué dans l&amp;#8217;article : gérer la parallélisation dans le code est une tannée.
Gérer l&amp;#8217;asynchrone avec des callbacks cochonne le code, et une simple abstraction comme des threads légers rendent le code plus concis et compréhensible.
Un développeur javascript qui a vu l&amp;#8217;arrivée de async/await pourra confirmer quel enfer sont les&amp;nbsp;callbacks.&lt;/p&gt;
&lt;p&gt;Ce point explique les choix originels de golang, et tourne le couteau dans la plaie async de&amp;nbsp;Rust.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Lac_de_Paladru"&gt;&lt;img alt="Lac de Paladru" class="image-process-article-image" src="/images/lac_paladru.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="materiel"&gt;Matériel&lt;/h2&gt;
&lt;h3 id="cartes-ethernet"&gt;Cartes&amp;nbsp;Ethernet&lt;/h3&gt;
&lt;p&gt;On est maintenant dans l&amp;#8217;ère du &lt;a href="https://en.wikipedia.org/wiki/Terabit_Ethernet"&gt;terabit Ethernet&lt;/a&gt;, &lt;a href="https://www.intel.com/content/www/us/en/products/details/ethernet.html"&gt;Intel propose du 100G&lt;/a&gt;, &lt;a href="https://www.nvidia.com/en-us/networking/ethernet-adapters/"&gt;NVidia du 400G&lt;/a&gt; (qui fait bien fructifier son rachat de Mellanox).
Le 800G est spécifié, le 1.6T est en travaux.
À partir de 400G, ce sera de l&amp;#8217;agrégation (16 brins de 25G pour la première génération de 400G, 2 brins de 200G dans la&amp;nbsp;dernière)&lt;/p&gt;
&lt;p&gt;Oui, les plus gros débits consomment plus d&amp;#8217;énergie, et il existe une norme qui s&amp;#8217;en inquiète : &lt;a href="https://en.wikipedia.org/wiki/Energy-Efficient_Ethernet"&gt;&lt;span class="caps"&gt;EEE&lt;/span&gt;, Energy Efficient Ethernet&lt;/a&gt;.
L&amp;#8217;idée n&amp;#8217;étant pas la sobriété, mais de densifier le datacenter qui est cappé par l&amp;#8217;énergie disponible, plus que par le&amp;nbsp;foncier.&lt;/p&gt;
&lt;p&gt;Meta cause régulièrement de son matos, logique, car il est fondateur de &lt;a href="https://www.opencompute.org/"&gt;Opencompute&lt;/a&gt; qui fournit des specs libres et travaille en collaboration avec des&amp;nbsp;constructeurs.&lt;/p&gt;
&lt;p&gt;Chez Meta, le &lt;a href="https://engineering.fb.com/2019/03/14/data-center-engineering/f16-minipack/"&gt;100G est généralisé, avec 16 planes en uplink pour un rack, toutes les machines dans un bâtiment sont accessibles en 6 sauts &lt;/a&gt;.
&lt;a href="https://engineering.fb.com/2021/11/09/data-center-engineering/ocp-summit-2021/"&gt;Le 400G est vu (enfin, en 2021) comme spécifique à l&amp;#8217;&lt;span class="caps"&gt;AI&lt;/span&gt; et à l&amp;#8217;apprentissage machine&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="sr-iov"&gt;&lt;span class="caps"&gt;SR&lt;/span&gt;-&lt;span class="caps"&gt;IOV&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Une carte PCIe peut avoir l&amp;#8217;option (capability) &lt;a href="https://en.wikipedia.org/wiki/Single-root_input/output_virtualization"&gt;&lt;span class="caps"&gt;SR&lt;/span&gt;-&lt;span class="caps"&gt;IOV&lt;/span&gt;&lt;/a&gt; lui permettant de présenter à l&amp;#8217;hôte des &lt;a href="https://www.kernel.org/doc/html/latest/PCI/pci-iov-howto.html"&gt;périphériques virtuels, avec chacun son bus et son espace mémoire&lt;/a&gt;, qui pourront être confié à des machines virtuelles.
Azure propose ce genre de chose avec &lt;a href="https://azure.microsoft.com/en-us/blog/linux-and-windows-networking-performance-enhancements-accelerated-networking/"&gt;des VMs disposant d&amp;#8217;un réseau de 25G&lt;/a&gt;, et &lt;a href="https://azure.microsoft.com/en-us/blog/dpdk-data-plane-development-kit-for-linux-vms-now-generally-available/"&gt;l&amp;#8217;accès au &lt;span class="caps"&gt;FPGA&lt;/span&gt; de la carte via &lt;span class="caps"&gt;DPDK&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Alibaba utilise ce découpage matériel pour qu&amp;#8217;une carte réseau puisse être utilisée en espace utilisateur (avec &lt;span class="caps"&gt;DPDK&lt;/span&gt;) et par le noyau Linux, de manière&amp;nbsp;classique.&lt;/p&gt;
&lt;h2 id="cadriciels-reseaux"&gt;Cadriciels&amp;nbsp;réseaux&lt;/h2&gt;
&lt;h3 id="netmap"&gt;Netmap&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/luigirizzo/netmap"&gt;netmap&lt;/a&gt; est un projet de l&amp;#8217;université de Pise pour une gestion du réseau en espace utilisateur.
Des outils sont fournis pour la gestion des switchs et machines virtuelles, ainsi que &lt;a href="http://info.iet.unipi.it/~luigi/research.html"&gt;des papiers de recherche&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://man.freebsd.org/cgi/man.cgi?query=netmap&amp;amp;sektion=4"&gt;FreeBSD a intégré netmap&lt;/a&gt; les autres &lt;span class="caps"&gt;OS&lt;/span&gt; (Linux et Windows) sont moins enthousiastes, même si le code est&amp;nbsp;fourni.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;activité projet est très calme depuis 2019, mais il est souvent comparé à &lt;span class="caps"&gt;DPDK&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="dpdk"&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt;, pour Data Plane Development Kit est un projet fourretout centré sur l&amp;#8217;accélération&amp;nbsp;matérielle.&lt;/p&gt;
&lt;p&gt;Il permet de gérer des choses farfelues comme &lt;a href="https://doc.dpdk.org/guides/regexdevs/index.html"&gt;l&amp;#8217;accélération matérielle d&amp;#8217;expressions régulières&lt;/a&gt; (pour épauler un &lt;a href="https://en.wikipedia.org/wiki/Web_application_firewall"&gt;&lt;span class="caps"&gt;WAF&lt;/span&gt;&lt;/a&gt; ?), mais surtout les cartes&amp;nbsp;réseau.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt; cible le &lt;span class="caps"&gt;SDN&lt;/span&gt; (Software Defined Network) et les switchs virtuels qui accompagnent si bien les machines tout aussi virtuelles.
Il est pour ça &lt;a href="https://github.com/p4lang/p4-dpdk-target"&gt;une des cibles de P4&lt;/a&gt;, le &lt;span class="caps"&gt;DSL&lt;/span&gt; qui parle&amp;nbsp;réseau.&lt;/p&gt;
&lt;p&gt;En plus des possibles accélérations à tous les niveaux, on notera le brutal
&lt;a href="https://doc.dpdk.org/guides/nics/af_packet.html"&gt;AF_PACKET&lt;/a&gt; qui permet d&amp;#8217;envoyer et recevoir des paquets bruts en espace utilisateur,
ainsi que &lt;a href="https://www.kernel.org/doc/html/latest/networking/af_xdp.html"&gt;AF_XDP&lt;/a&gt; qui fait partie de l&amp;#8217;écosystème &lt;a href="https://en.wikipedia.org/wiki/EBPF"&gt;&lt;span class="caps"&gt;EBPF&lt;/span&gt;&lt;/a&gt; et &lt;a href="https://en.wikipedia.org/wiki/Express_Data_Path"&gt;&lt;span class="caps"&gt;XDP&lt;/span&gt;&lt;/a&gt;.
&lt;span class="caps"&gt;EBPF&lt;/span&gt; et &lt;span class="caps"&gt;XDP&lt;/span&gt; permettent de router/bloquer des volumes énormes de paquets avant le noyau, mais la taille du code qui prend les décisions est limité, le routage pourra être fait vers un AF_INET (le classique réseau du noyau) ou un AF_XDP (des paquets bruts en espace utilisateur).
&lt;span class="caps"&gt;AF&lt;/span&gt; pour &lt;em&gt;Address Family&lt;/em&gt; (comme il existe un&amp;nbsp;AF_UNIX).&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt; permet la &lt;a href="https://doc.dpdk.org/guides/howto/flow_bifurcation.html"&gt;bifurcation de flot&lt;/a&gt;, pour répartir le flot réseau entre le noyau et l&amp;#8217;espace utilisateur.
&lt;span class="caps"&gt;SR&lt;/span&gt;-&lt;span class="caps"&gt;IOV&lt;/span&gt; permet de découper la carte en cartes virtuelles (&lt;span class="caps"&gt;SR&lt;/span&gt;-&lt;span class="caps"&gt;IOV&lt;/span&gt; présente différente adresse Mac sur ses ports Ethernet, et route en L2 le flot vers les cartes virtuelles présentées au bus PCIe), mais certaines cartes sont aussi capables de comprendre les couches suivantes du modèle &lt;span class="caps"&gt;OSI&lt;/span&gt; , et de &lt;a href="https://doc.dpdk.org/guides/prog_guide/rte_flow.html"&gt;router en fonction&lt;/a&gt; du &lt;span class="caps"&gt;VLAN&lt;/span&gt;, &lt;span class="caps"&gt;VXLAN&lt;/span&gt;, &lt;span class="caps"&gt;IP&lt;/span&gt;, port… que ce soit en IPv4 ou&amp;nbsp;IPv6.&lt;/p&gt;
&lt;p&gt;Une expertise &amp;#8220;coin de nappe&amp;#8221; permet d&amp;#8217;affirmer que l&amp;#8217;on parle de cartes à partir de 500$ (&lt;a href="https://netronome.com/agilio-smartnics/"&gt;Metronome&lt;/a&gt; &lt;a href="https://www.chelsio.com/nic/unified-wire-adapters/"&gt;Chelsio&lt;/a&gt; par&amp;nbsp;exemple).&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt; fournit une liste &lt;a href="https://core.dpdk.org/supported/#nics"&gt;des cartes réseau supportées&lt;/a&gt;, que vous pourrez explorer avec vos admin sys et vos&amp;nbsp;acheteurs.&lt;/p&gt;
&lt;h4 id="mtcp"&gt;mTCP&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/mtcp-stack/mtcp"&gt;mTCP&lt;/a&gt; a été la référence de &lt;span class="caps"&gt;TCP&lt;/span&gt; en espace utilisateur, et détail mignon, il propose d&amp;#8217;utiliser différents projets pour gérer les cartes réseaux, et pas uniquement le maintenant incontournable &lt;span class="caps"&gt;DPDK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;De manière similaire, ils ont implémenté des alternatives pour la gestion de&amp;nbsp;congestion.&lt;/p&gt;
&lt;p&gt;Le projet est intouché depuis&amp;nbsp;2019.&lt;/p&gt;
&lt;h4 id="ix"&gt;&lt;span class="caps"&gt;IX&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/ix-project/ix"&gt;&lt;span class="caps"&gt;IX&lt;/span&gt;&lt;/a&gt; s&amp;#8217;appuie sur &lt;a href="https://github.com/ix-project/dune"&gt;dune&lt;/a&gt; pour régler les problèmes réseau de&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Dune permet d&amp;#8217;être plus que root en utilisant les fonctionnalités de virtualisation du processeur pour tripoter des trucs en &lt;a href="https://en.wikipedia.org/wiki/Protection_ring"&gt;ring 0&lt;/a&gt;, comme la gestion des&amp;nbsp;interruptions.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;IX&lt;/span&gt; se vante de faire mieux que mTCP, mais date d&amp;#8217;avant ROCEv2 et les cartes&amp;nbsp;100G.&lt;/p&gt;
&lt;p&gt;Le papier qui explique tout ça : &lt;a href="https://www.usenix.org/conference/osdi14/technical-sessions/presentation/belay"&gt;&lt;span class="caps"&gt;IX&lt;/span&gt;: A Protected Dataplane Operating System for High Throughput and Low Latency&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le choix de truander le ring 0 est d&amp;#8217;une audace folle (&lt;em&gt;dune&lt;/em&gt; semble être abandonnée depuis&amp;nbsp;2016).&lt;/p&gt;
&lt;h4 id="f-stack"&gt;f-stack&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://www.f-stack.org/"&gt;F-stack&lt;/a&gt; a été crée pour que le serveur &lt;span class="caps"&gt;DNS&lt;/span&gt; d&amp;#8217;un hébergeur puisse survivre a un &lt;span class="caps"&gt;DDOS&lt;/span&gt; (en &lt;span class="caps"&gt;UDP&lt;/span&gt;&amp;nbsp;donc).&lt;/p&gt;
&lt;p&gt;Leur réponse est simple : transplantation de la stack &lt;span class="caps"&gt;IP&lt;/span&gt; de FreeBSD 11 en espace&amp;nbsp;utilisateur.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;accès au réseau passe par une lib asynchrone utilisant des coroutines ou un truandage pour exposer l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;POSIX&lt;/span&gt; avec&amp;nbsp;epoll/kqeue.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est du réseau en espace utilisateur (basé sur &lt;span class="caps"&gt;DPDK&lt;/span&gt;), mais ça reste de l&amp;#8217;&lt;span class="caps"&gt;IP&lt;/span&gt; traditionnelle, et donc pas du&amp;nbsp;tcpless.&lt;/p&gt;
&lt;h3 id="vpp"&gt;&lt;span class="caps"&gt;VPP&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://s3-docs.fd.io/vpp/24.06/index.html"&gt;Vector Packet Processor&lt;/a&gt; se focalise sur l&amp;#8217;optimisation de la gestion des paquets en vectorisant leur traitement.
Plutôt que de travailler à la pièce, on passe à la palette.
Les différents paquets sont traités par lot, pour bénéficier au maximum du &lt;a href="https://en.wikipedia.org/wiki/CPU_cache"&gt;cache du processeur&lt;/a&gt; dont le D-Cache (potentiellement au détriment du&amp;nbsp;I-Cache).&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;VPP&lt;/span&gt; propose de &lt;a href="https://s3-docs.fd.io/vpp/24.06/aboutvpp/featurelist.html"&gt;pimper tout&lt;/a&gt; le modèle &lt;span class="caps"&gt;OSI&lt;/span&gt;, avec des applications extrêmement diverses (&lt;span class="caps"&gt;DHCP&lt;/span&gt;, Wiregard, V-Host pour virtio, switch virtuel, &lt;span class="caps"&gt;VRRP&lt;/span&gt;, &lt;span class="caps"&gt;VXLAN&lt;/span&gt;, &lt;span class="caps"&gt;QUIC&lt;/span&gt;) et bien sur d&amp;#8217;utiliser les accélérations matérielles fournies par &lt;span class="caps"&gt;DPDK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;VPP&lt;/span&gt; a sa fan base, des ⭐️ et des contributeurs, mais ses sous-projets génèrent nettement moins&amp;nbsp;d&amp;#8217;enthousiasme.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FDio/tldk"&gt;&lt;span class="caps"&gt;TLDK&lt;/span&gt;&lt;/a&gt; propose d&amp;#8217;implémenter la couche L4, (&lt;span class="caps"&gt;UDP&lt;/span&gt; et &lt;span class="caps"&gt;TCP&lt;/span&gt;), avec l&amp;#8217;inévitable fork de Nginx. Pas de commit depuis&amp;nbsp;2021.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://s3-docs.fd.io/vpp/24.06/usecases/contiv/index.html"&gt;Contiv&lt;/a&gt;, officiellement déprécié, proposait un vSwitch par nodes K8s et le tricotage réseau des conteneurs qui allait&amp;nbsp;avec.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;K8s n&amp;#8217;a pas abandonné &lt;span class="caps"&gt;VPP&lt;/span&gt;, c&amp;#8217;est Calico qui a repris le flambeau des mains de Contiv, avec  &lt;a href="https://docs.tigera.io/calico/latest/getting-started/kubernetes/vpp/getting-started"&gt;Calico-&lt;span class="caps"&gt;VPP&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;VPP&lt;/span&gt; se voit comme un hub ultime du réseau en espace utilisateur, avec un système de plugin et de fallback pour atteindre&amp;nbsp;l&amp;#8217;universalisme.&lt;/p&gt;
&lt;p&gt;Même s’il existe &lt;span class="caps"&gt;TLDK&lt;/span&gt;, &lt;span class="caps"&gt;VPP&lt;/span&gt; &lt;a href="https://fd.io/usecases/"&gt;cible clairement l&amp;#8217;infra réseau&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="protocoles"&gt;Protocoles&lt;/h2&gt;
&lt;h3 id="srd"&gt;&lt;span class="caps"&gt;SRD&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; a créé &lt;a href="https://ieeexplore.ieee.org/document/9167399"&gt;&lt;span class="caps"&gt;SRD&lt;/span&gt;&lt;/a&gt;, pour le calcul scientifique, j&amp;#8217;en ai parlé dans l&amp;#8217;article sur le &lt;a href="diskless.html"&gt;diskless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour résumer : nouvelle couche de transport sur &lt;span class="caps"&gt;IP&lt;/span&gt; non connecté, mais livraison garantie bien que non ordonnée, et surtout mise en avant du multipath.
Leur implémentation utilise les buzzwords du moment : accélération matérielle, &lt;span class="caps"&gt;DMA&lt;/span&gt;, gestion de congestion, contournement du noyau.
&lt;span class="caps"&gt;SRD&lt;/span&gt; est utilisé via &lt;strong&gt;libfabric&lt;/strong&gt; pour le calcul scientifique, mais aussi pour tunneler le réseau des machines virtuelles (comme le ferait &lt;span class="caps"&gt;GRE&lt;/span&gt;, &lt;span class="caps"&gt;VXLAN&lt;/span&gt;…), en réordonnant les paquets (classique, &lt;span class="caps"&gt;UDP&lt;/span&gt;, indispensable aux &lt;span class="caps"&gt;VPN&lt;/span&gt;, a aussi besoin de&amp;nbsp;réordonnement).&lt;/p&gt;
&lt;h3 id="snap-et-pony-express"&gt;Snap et pony&amp;nbsp;express&lt;/h3&gt;
&lt;p&gt;Google a rapidement exploité le réseau en espace utilisateur, déjà évoqué dans l&amp;#8217;article précédant sur le &lt;a href="diskless.html"&gt;diskless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour résumer : &lt;a href="https://research.google/pubs/snap-a-microkernel-approach-to-host-networking/"&gt;snap&lt;/a&gt; est le service en espace utilisateur qui gère la carte réseau et du &lt;span class="caps"&gt;DMA&lt;/span&gt;, divers moteurs sont utilisés, les logiciels choisissent le moteur qu&amp;#8217;ils veulent, selon les besoins de latence, de débit et de cout. &amp;#8220;Pony Express&amp;#8221; est le moteur détaillé dans l&amp;#8217;article, qui propose un mix de messages courts et de simili &lt;span class="caps"&gt;RDMA&lt;/span&gt;, avec une gestion de congestion aux petits&amp;nbsp;ognons.&lt;/p&gt;
&lt;h3 id="homa"&gt;&lt;span class="caps"&gt;HOMA&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;implémentation en espace utilisateur, utilisant &lt;span class="caps"&gt;DPDK&lt;/span&gt; est abandonnée, pour une implémentation en espace noyau, &lt;a href="https://github.com/PlatformLab/HomaModule"&gt;&lt;span class="caps"&gt;HOMA&lt;/span&gt; module&lt;/a&gt;.
Le protocole est obnubilé par la notion de &lt;span class="caps"&gt;RPC&lt;/span&gt;, des petits messages avec une faible latence (10 fois moindre qu&amp;#8217;en &lt;span class="caps"&gt;TCP&lt;/span&gt;).
C&amp;#8217;est utilisable en C++, en golang (avec &lt;a href="https://github.com/dpeckett/go-homa"&gt;go-homa&lt;/a&gt;), du Java est promis, et la cible est &lt;em&gt;grpc&lt;/em&gt; (avec une promesse de thrift qui arrivera plus tard).
Les switchs doivent gérer la qualité de service avec &lt;a href="https://en.wikipedia.org/wiki/Differentiated_services"&gt;&lt;span class="caps"&gt;DCSP&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le protocole est un ping pong, le client envoie un paquet &lt;span class="caps"&gt;DATA&lt;/span&gt; (avec la taille totale du message en meta donnée), le serveur valide et annonce la taille du prochain paquet avec un &lt;span class="caps"&gt;GRANT&lt;/span&gt;.
Donc, le protocole fait l&amp;#8217;impasse sur les paquets non ordonnés et à priori le&amp;nbsp;multipath.&lt;/p&gt;
&lt;p&gt;Aucune référence à &lt;span class="caps"&gt;RDMA&lt;/span&gt;, ou à de&amp;nbsp;l&amp;#8217;offloading.&lt;/p&gt;
&lt;h3 id="alibaba"&gt;Alibaba&lt;/h3&gt;
&lt;p&gt;Alibaba a fait évoluer la partie réseau de son stockage en mode bloc en plusieurs&amp;nbsp;étapes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.usenix.org/system/files/fast24-zhang-weidong.pdf"&gt;What’s the Story in &lt;span class="caps"&gt;EBS&lt;/span&gt; Glory: Evolutions and Lessons
in Building Cloud Block Store&lt;/a&gt;&amp;nbsp;(2024)&lt;/p&gt;
&lt;h4 id="rdma"&gt;&lt;span class="caps"&gt;RDMA&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;Pour suivre les performances des disques NVMe, Alibaba s&amp;#8217;est orienté vers le &lt;span class="caps"&gt;RDMA&lt;/span&gt; (du RoCE, de l&amp;#8217;Ethernet donc), qui permet du zero copy via PCIe entre le disque et la carte réseau. La copie entre le disque et la carte réseau les empêchait de saturer une carte&amp;nbsp;100G.&lt;/p&gt;
&lt;p&gt;Après avoir bien lu les publications de Microsoft qui avait déjà déployé du &lt;span class="caps"&gt;RDMA&lt;/span&gt; à grande échelle, ils sont partis sur du &lt;span class="caps"&gt;DPDK&lt;/span&gt;+&lt;span class="caps"&gt;SPDK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;RDMA&lt;/span&gt; a été déployé de manière prudente, juste sur un niveau de switch au dessus des Top Of Rack pour limiter les tempêtes &lt;span class="caps"&gt;PFC&lt;/span&gt;, des racks de stockages, des racks de calcul (c&amp;#8217;était pour leur site de vente en ligne, le ratio stockage par &lt;span class="caps"&gt;CPU&lt;/span&gt; est censé être prévisible).
Le &lt;span class="caps"&gt;RDMA&lt;/span&gt; n&amp;#8217;est activé qu&amp;#8217;entre un serveur de calcul et un serveur de stockage, chaque famille de serveurs parle entre eux en &lt;span class="caps"&gt;TCP&lt;/span&gt; (en espace&amp;nbsp;utilisateur).&lt;/p&gt;
&lt;p&gt;Ils ont commencé avec des &lt;span class="caps"&gt;RNIC&lt;/span&gt; (du Mellanox) où la carte gère le &lt;span class="caps"&gt;RDMA&lt;/span&gt;, ce qui brime la possibilité de corriger des problèmes (comme ceux liés à &lt;span class="caps"&gt;PFC&lt;/span&gt;).
Le &lt;span class="caps"&gt;RNIC&lt;/span&gt; s&amp;#8217;est avéré pénible à l&amp;#8217;usage, chaque constructeur implémente de manière subtilement différente les uns des autres, et un même constructeur ne fera pas les mêmes choix entre ses différentes gammes de&amp;nbsp;cartes.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;TCP&lt;/span&gt; a été utilisé comme fallback pour le &lt;span class="caps"&gt;RDMA&lt;/span&gt;, et l&amp;#8217;isolation entre les deux protocoles n&amp;#8217;a pas été sans&amp;nbsp;heurts.&lt;/p&gt;
&lt;p&gt;Pour profiter du zero-copy, un filesystem spécifique permet d&amp;#8217;écrire directement des blocs, et de faire entre 4 et 10 fois plus d&amp;#8217;&lt;span class="caps"&gt;IOPS&lt;/span&gt; que de l&amp;#8217;ext4.
L&amp;#8217;article cite sur ce point
 &lt;a href="https://dl.acm.org/doi/10.1145/3037697.3037732"&gt;Reflex&lt;/a&gt; (et son &lt;a href="https://github.com/stanford-mast/reflex"&gt;code d&amp;#8217;exemple&lt;/a&gt;)  et &lt;a href="https://www.usenix.org/conference/nsdi20/presentation/hwang"&gt;i10&lt;/a&gt; (et son &lt;a href="https://github.com/i10-kernel/i10-implementation"&gt;code d&amp;#8217;exemple&lt;/a&gt;) qui anticipent NVMe-of en tcp (il n&amp;#8217;était qu&amp;#8217;en &lt;span class="caps"&gt;RDMA&lt;/span&gt; à ce&amp;nbsp;moment).&lt;/p&gt;
&lt;p&gt;Tous les détails sont dans le papier de 2021 : &lt;a href="https://www.usenix.org/conference/nsdi21/presentation/gao"&gt;When Cloud Storage Meets &lt;span class="caps"&gt;RDMA&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Avoir un pool de stockage par routeur ressemble à l&amp;#8217;expérience de Facebook qui était plafonné par la taille maximale d&amp;#8217;un cluster &lt;span class="caps"&gt;HDFS&lt;/span&gt;. Facebook s&amp;#8217;était retrouvé avec une constellation de milliers de pool de stockage par datacenter, mal balancé, avec des rééquilibrages très pénibles.
Voir le papier &lt;a href="https://engineering.fb.com/2021/06/21/data-infrastructure/tectonic-file-system/"&gt;Facebook’s Tectonic Filesystem:
Efficiency from Exascale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je suppose que l&amp;#8217;expérience a dû être similaire chez&amp;nbsp;Alibaba.&lt;/p&gt;
&lt;h4 id="luna-rdma"&gt;Luna + &lt;span class="caps"&gt;RDMA&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;Le papier &lt;a href="https://www.usenix.org/system/files/atc23-zhu-lingjun.pdf"&gt;Deploying User-space &lt;span class="caps"&gt;TCP&lt;/span&gt; at Cloud Scale with Luna&lt;/a&gt;&amp;nbsp;(2023)&lt;/p&gt;
&lt;p&gt;Luna promet 3.5 fois le débit, réduction de 50% des latences par rapport au &lt;span class="caps"&gt;TCP&lt;/span&gt; du noyau&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;RDMA&lt;/span&gt; n&amp;#8217;est pas utilisable pour du multi &lt;span class="caps"&gt;AZ&lt;/span&gt;, et conserver une compatibilité avec &lt;span class="caps"&gt;TCP&lt;/span&gt; est quand même bien&amp;nbsp;pratique.&lt;/p&gt;
&lt;p&gt;Les offres existantes de &lt;span class="caps"&gt;TCP&lt;/span&gt; en user space sont prometteuses (&lt;span class="caps"&gt;IX&lt;/span&gt;, mTCP, &lt;span class="caps"&gt;VPP&lt;/span&gt;) mais ont des soucis de conception :
 * utilisation d&amp;#8217;un thread pour le réseau, un autre pour l&amp;#8217;application qui engendre un surcout de communication
 * absence de zero-copy
 * accès exclusif à la carte&amp;nbsp;réseau&lt;/p&gt;
&lt;p&gt;Partant de ce constat, Luna utilise une boucle évènementielle par thread pour entrelacer la gestion du réseau et l&amp;#8217;application, et du zero&amp;nbsp;copy.&lt;/p&gt;
&lt;p&gt;La carte réseau est partagée avec le noyau via &lt;strong&gt;&lt;span class="caps"&gt;SR&lt;/span&gt;-&lt;span class="caps"&gt;IOV&lt;/span&gt;&lt;/strong&gt;, et, plus raffinée, de la bifurcation de flot (de &lt;span class="caps"&gt;DPDK&lt;/span&gt;) pour choisir pour une &lt;span class="caps"&gt;IP&lt;/span&gt; et un port qui va répondre.
&lt;span class="caps"&gt;LUNA&lt;/span&gt; laisse le kernel gérait la couche Ethernet et les adresses &lt;span class="caps"&gt;ARP&lt;/span&gt;, se contentant d&amp;#8217;aller les lire via &lt;em&gt;netlink&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Gérer une pile réseau en espace utilisateur nécessite (indirectement) des processeurs gérant les &lt;a href="https://wiki.debian.org/Hugepages"&gt;hugepages&lt;/a&gt; (de plus de 2Mo je présume) et une partie de leur flotte ne le gérait pas (en 2017), donc utiliser du classique &lt;span class="caps"&gt;TCP&lt;/span&gt; permettait une compatibilité et un déploiement&amp;nbsp;progressif.&lt;/p&gt;
&lt;h4 id="solar"&gt;Solar&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://dl.acm.org/doi/abs/10.1145/3544216.3544238"&gt;From Luna to Solar: The Evolutions of the Compute-to-Storage
Networks in Alibaba&amp;nbsp;Cloud&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dont on peut trouver le &lt;span class="caps"&gt;PDF&lt;/span&gt; &lt;a href="https://rmiao.github.io/assets/pdf/solar-sigcomm22.pdf"&gt;ici&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Après 5 ans de Luna, Alibaba s&amp;#8217;est remis sur la planche à dessin pour concevoir l&amp;#8217;itération suivante. Ils ont aussi très vraisemblablement étudié le papier de &lt;span class="caps"&gt;AWS&lt;/span&gt; sur &lt;span class="caps"&gt;SRD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;En corrigeant les problèmes de réseaux avec Luna, Alibaba a pu découvrir les limites de leurs abstractions de stockage réseau, ainsi que l&amp;#8217;importance de la rapidité pour reprendre un incident réseau (quelques&amp;nbsp;millisecondes).&lt;/p&gt;
&lt;p&gt;Solar est un protocole &lt;span class="caps"&gt;UDP&lt;/span&gt; conçu pour les besoins de stockage.
Il est utilisé conjointement avec Luna, qui a un usage plus&amp;nbsp;large.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée de base est simple et élégante : un bloc disque est contenu dans un bloc réseau.
Bloc de 4ko, comme ce qu&amp;#8217;utilise le &lt;span class="caps"&gt;SSD&lt;/span&gt;, et donc du &lt;em&gt;jumbo frame&lt;/em&gt; pour le réseau.
Aucune conversion ou assemblage, on a tout ce qu&amp;#8217;il faut pour du zero copy.
L&amp;#8217;arrivée des paquets n&amp;#8217;est pas tenue d&amp;#8217;être ordonnée (et donc de maintenir une connexion), ce qui facilite l&amp;#8217;utilisation du multipath tant vanté par &lt;span class="caps"&gt;SRD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;utilisateur voit un disque NVMe, qui est géré par l&amp;#8217;agent de stockage, qui sait sur quels serveurs écrire, effectuer la réplication, puis prévenir le client que le quorum est&amp;nbsp;atteint.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;agent est dans la carte d&amp;#8217;offloading (l&amp;#8217;équivalent du Nitro d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;), et il va récupérer quelques informations depuis l&amp;#8217;hôte (&lt;span class="caps"&gt;QOS&lt;/span&gt;, chemin), pour le reste il se débrouille, il se comporte comme un disque NVMe, en PCIe, avec donc du &lt;span class="caps"&gt;DMA&lt;/span&gt;.
Les cartes &lt;span class="caps"&gt;ALI&lt;/span&gt;-&lt;span class="caps"&gt;DPU&lt;/span&gt; sont des &lt;span class="caps"&gt;FPGA&lt;/span&gt; (avec l&amp;#8217;idée de passer a de l&amp;#8217;&lt;span class="caps"&gt;ASIC&lt;/span&gt; à un moment), mais ces cartes sont très sensibles au bit-flip (le phénomène que corrige &lt;span class="caps"&gt;ECC&lt;/span&gt; pour la &lt;span class="caps"&gt;RAM&lt;/span&gt;), et donc les calculs de &lt;span class="caps"&gt;CRC&lt;/span&gt; sont validés par le &lt;span class="caps"&gt;CPU&lt;/span&gt; (avec une astuce mathématique pour valider des lots, pas un par&amp;nbsp;un).&lt;/p&gt;
&lt;p&gt;Une partie du &lt;span class="caps"&gt;FPGA&lt;/span&gt; étant squatté par l&amp;#8217;offloading du vswitch (le routage réseau des VMs), Solar a fait le choix du minimalisme.
Choix payant, parce qu&amp;#8217;il est possible d&amp;#8217;envisager d&amp;#8217;implémenter l&amp;#8217;agent avec P4, un &lt;span class="caps"&gt;DSL&lt;/span&gt; pour le réseau, et donc d&amp;#8217;utiliser un &lt;span class="caps"&gt;DPU&lt;/span&gt; du&amp;nbsp;marché.&lt;/p&gt;
&lt;p&gt;Histoire de pinailler, techniquement, Solar ne fait plus de &lt;a href="https://en.wikipedia.org/wiki/Remote_direct_memory_access"&gt;&lt;span class="caps"&gt;RDMA&lt;/span&gt;&lt;/a&gt;, même s’il fait du &lt;span class="caps"&gt;DMA&lt;/span&gt; de chaque côté du tuyau (et du&amp;nbsp;zerocopy).&lt;/p&gt;
&lt;h3 id="quic"&gt;&lt;span class="caps"&gt;QUIC&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;QUIC&lt;/span&gt;, la couche basse de &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3, est un protocole équivalent à &lt;span class="caps"&gt;TCP&lt;/span&gt;, mais plus adapté à Internet et spécifiquement les réseaux mobiles.
Basé sur &lt;span class="caps"&gt;UDP&lt;/span&gt;, il est supporté par les routeurs de plus ou moins mauvaise foi qui forment&amp;nbsp;l&amp;#8217;Internet.&lt;/p&gt;
&lt;p&gt;W3tech, Mozilla et Cloudflare sont d&amp;#8217;accord pour estimer que près de 30% du Web se fait en &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3 (plus qu&amp;#8217;en &lt;span class="caps"&gt;HTTP&lt;/span&gt;/1), et &lt;a href="https://engineering.fb.com/2020/10/21/networking-traffic/how-facebook-is-bringing-quic-to-billions/"&gt;Facebook annonçaient 75% en 2020, pour ses services&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tous les gros acteurs proposent leur implémentation&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/facebook/mvfst"&gt;mvfst&lt;/a&gt; pour&amp;nbsp;Facebook&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/MsQuic"&gt;MsQuic&lt;/a&gt; pour&amp;nbsp;Microsoft&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alibaba/xquic"&gt;&lt;span class="caps"&gt;XQUIC&lt;/span&gt;&lt;/a&gt; pour Alibaba (avec &lt;a href="https://github.com/alibaba/tengine"&gt;tengine&lt;/a&gt; son fork de Nginx pour &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/quiche"&gt;&lt;span class="caps"&gt;QUICHE&lt;/span&gt;&lt;/a&gt; (celui en C++) de Google, utilisé par&amp;nbsp;Envoy&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudflare/quiche"&gt;&lt;span class="caps"&gt;QUICHE&lt;/span&gt;&lt;/a&gt; (celui en Rust) de Cloudflare. (un patch pour Nginx&amp;nbsp;existe)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/s2n-quic"&gt;s2n-quic&lt;/a&gt; de &lt;span class="caps"&gt;AWS&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HAproxy a sa propre implémentation (cherchez &lt;a href="https://github.com/haproxy/haproxy/blob/master/src/quic_sock.c"&gt;les quic_*.c dans les sources&lt;/a&gt;).
OpenSSL n&amp;#8217;implémente pas encore les routines requises par &lt;span class="caps"&gt;QUIC&lt;/span&gt;, mais il y a un fork pour ça : &lt;a href="https://github.com/quictls/openssl"&gt;quictls&lt;/a&gt;.
Golang a bien sûr son implémentation native, &lt;a href="https://github.com/quic-go/quic-go"&gt;quic-go&lt;/a&gt; dont bénéficient &lt;a href="https://github.com/traefik/traefik"&gt;Traefik&lt;/a&gt; et &lt;a href="https://github.com/caddyserver/caddy"&gt;Caddy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tout ça est super, &lt;span class="caps"&gt;QUIC&lt;/span&gt; est le chalengeur officiel de &lt;span class="caps"&gt;TCP&lt;/span&gt;, mais, comme le rappel &lt;a href="https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/"&gt;cUrl, &lt;span class="caps"&gt;QUIC&lt;/span&gt; et &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3, est conçu pour Internet&lt;/a&gt;, et pas pour les datacenter.
L&amp;#8217;établissement rapide des connexions n&amp;#8217;a pas un grand intérêt sur des réseaux à faible latence, et les algos de gestion de congestion sont maintenant disponibles pour &lt;span class="caps"&gt;TCP&lt;/span&gt;.
cURL a aussi constaté que &lt;span class="caps"&gt;HTTP&lt;/span&gt;/3 était limité par le &lt;span class="caps"&gt;CPU&lt;/span&gt;, chose que l&amp;#8217;on ne voit pas quand on est limité par le&amp;nbsp;réseau.&lt;/p&gt;
&lt;h3 id="1rma"&gt;&lt;span class="caps"&gt;1RMA&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Google a constaté que le &lt;span class="caps"&gt;RDMA&lt;/span&gt; avait des performances fort sympathiques (latence, débit, contournement du &lt;span class="caps"&gt;CPU&lt;/span&gt;…), mais qu&amp;#8217;il n&amp;#8217;a jamais été conçu pour le multitenant.
Ajoutons à ça les reproches favoris de Google : gestions des congestions naïves, flasher un firmware (pour corriger ou tuner) c&amp;#8217;est&amp;nbsp;relou.&lt;/p&gt;
&lt;p&gt;Google décrit donc &lt;span class="caps"&gt;1RMA&lt;/span&gt;, pour &amp;#8220;one way &lt;span class="caps"&gt;RMA&lt;/span&gt;&amp;#8221;.
Leur idée est de ne pas avoir de discussions (une connexion, quoi), mais des messages initiés d&amp;#8217;un bout ou l&amp;#8217;autre du&amp;nbsp;tuyau.&lt;/p&gt;
&lt;p&gt;Leur concept est de virer toute la magie de la carte, qu&amp;#8217;elle se contente de délester uniquement des éléments bien spécifiés (et qui n&amp;#8217;évolueront pas) : &lt;span class="caps"&gt;DMA&lt;/span&gt;, réseau, chiffrage et&amp;nbsp;signature.&lt;/p&gt;
&lt;p&gt;Ce qui donne leur liste en 5 points&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;1 - Pas de connexion. C&amp;#8217;est à l&amp;#8217;applicatif de séquencer s’il en a besoin, pas au protocole. La plupart des messages seront indépendants.
Avec un engagement de latence faible, il est possible d&amp;#8217;enchainer des questions/réponses, plutôt que d&amp;#8217;utiliser des trains de paquets comme le fait &lt;span class="caps"&gt;TCP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;2 - Petits paquets. 4ko, comme pour un disque dur.
L&amp;#8217;idée est donc d&amp;#8217;envoyer 3,125 millions de paquets par seconde, sur une carte 100G.
Avec un découpage fin, on peut garantir des latences, gérer de la qualité de service, pour obtenir un bon débit, il suffira d&amp;#8217;user de&amp;nbsp;violence.&lt;/p&gt;
&lt;p&gt;3 - Contrôle de congestion logiciel. Ce n&amp;#8217;est pas au switch de bricoler. La carte va fournir des mesures fines pour connaitre le temps de trajet sur le réseau, hors des fils d&amp;#8217;attente, ce qui permet d&amp;#8217;estimer la saturation. Ce système permet de partager le trafic avec des échanges non &lt;span class="caps"&gt;1RMA&lt;/span&gt;, et de gérer des ralentissements en&amp;nbsp;25µs.&lt;/p&gt;
&lt;p&gt;4 - Allocation des ressources définies par le logiciel.
Avec des petits paquets et en imposant des délais (timeout) très courts, il est possible de gérer le débit au maximum, de garantir des latences faibles et une gestion contenue des incidents.
En maitrisant le flot de paquets, il est possible d&amp;#8217;appliquer des priorités métiers, sans&amp;nbsp;bouchons.&lt;/p&gt;
&lt;p&gt;5 - Sécurité par défaut.
Tous les échanges sont chiffrés et signés par la carte, les petits paquets permettent de gérer la rotation de clés sans passer par le classique &amp;#8220;erreur, la clé a changé,&amp;nbsp;recommences&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Dans leurs tests, le &lt;span class="caps"&gt;1RMA&lt;/span&gt; consomme 1/2 cœur et utilise à plein une carte&amp;nbsp;100G.&lt;/p&gt;
&lt;p&gt;Tous les détails et graphiques sont dans leur papier de 2020 :
&lt;a href="https://dl.acm.org/doi/10.1145/3387514.3405897"&gt;&lt;span class="caps"&gt;1RMA&lt;/span&gt;: Re-envisioning Remote Memory Access for Multi-tenant&amp;nbsp;Datacenters&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="swift"&gt;Swift&lt;/h4&gt;
&lt;p&gt;Google dans son article de 2020 &lt;a href="https://dl.acm.org/doi/pdf/10.1145/3387514.3406591"&gt;Swift: Delay is Simple and Effective for Congestion Control in the Datacenter&lt;/a&gt; décret son algo de gestion de congestion Swift, successeur de Timely (le papier de 2015 &lt;a href="https://research.google/pubs/timely-rtt-based-congestion-control-for-the-datacenter/"&gt;&lt;span class="caps"&gt;TIMELY&lt;/span&gt;: &lt;span class="caps"&gt;RTT&lt;/span&gt;-based Congestion Control for the Datacenter&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;En préambule, il rappelle que pour désagréger le stockage, il faut des latences de 100µs pour du disque Flash, et 10µs pour du NVMe (et 10ms pour un &lt;em&gt;seek&lt;/em&gt; sur un disque à&amp;nbsp;plateaux).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article met un peu de temps à l&amp;#8217;avouer, Swift est utilisé pour du &lt;span class="caps"&gt;1RMA&lt;/span&gt; sur du Pony express, dans Snap. Ce n&amp;#8217;est qu&amp;#8217;un détail, l&amp;#8217;algorithme reprend ce qui se fait avec &lt;span class="caps"&gt;TCP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Comme &lt;span class="caps"&gt;DCTCP&lt;/span&gt; leur a fait des grumeaux en millisecondes, ils en disent pis que&amp;nbsp;pendre.&lt;/p&gt;
&lt;p&gt;Le concept, simple, est d&amp;#8217;adapter le débit aux délais de la connexion. &amp;#8220;Pas plus haut que le bord&amp;#8221; comme on&amp;nbsp;dit.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;algo est basé sur &lt;a href="https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease"&gt;&lt;span class="caps"&gt;AIMD&lt;/span&gt;&lt;/a&gt; &amp;#8220;ajout additif, retrait multiplicatif&amp;#8221; en traduction charabia, &amp;#8220;croissance linéaire, décroissance exponentielle&amp;#8221; en meilleur français.
L&amp;#8217;un des attraits de cet algo est que si chacun des flots calcule son débit avec, on aboutit à une stabilité de&amp;nbsp;l&amp;#8217;ensemble.&lt;/p&gt;
&lt;p&gt;Swift ne veut pas dépendre du switch, ni pour les informations (rejet de paquets ou message &lt;span class="caps"&gt;ECN&lt;/span&gt;), ni pour des obligations de paramétrages (seuil &lt;span class="caps"&gt;ECN&lt;/span&gt;), en se gardant la possibilité d&amp;#8217;un déploiement progressif (remplacement progressif de switch 10G par des&amp;nbsp;100G).&lt;/p&gt;
&lt;p&gt;Swift considère que les délais de commutations des switch sur une route sont fixes, et dépendent du nombre de sauts.
Swift, en connaissant la cible, connait la route, le nombre de switch à parcourir, et donc le&amp;nbsp;délai.&lt;/p&gt;
&lt;p&gt;Pour connaitre le délai de carte à carte, Swift va utiliser l&amp;#8217;horodatage de la carte (après la queue Tx depuis la source, avant la queue Rx sur la cible), puis le temps de traitement.
Les informations nécessaires seront transmises dans un entête du &lt;span class="caps"&gt;ACK&lt;/span&gt; (qui part sans attente, lui).
Pour rappel, l&amp;#8217;article parle de &lt;span class="caps"&gt;1RMA&lt;/span&gt; comme protocole, qui est un protocole avec des messages à &amp;#8220;sens unique&amp;#8221; : un message envoyé, un &lt;span class="caps"&gt;ACK&lt;/span&gt; en retour (ou un&amp;nbsp;timeout).&lt;/p&gt;
&lt;p&gt;Swift prévoit des connexions sans états, mais conserve une notion de flot, le débit entre la source et la cible, pour calculer une &amp;#8220;fenêtre de congestion&amp;#8221;, et utiliser la plus petite des deux (entre la source et la&amp;nbsp;cible).&lt;/p&gt;
&lt;p&gt;Swift va collaborer avec le &lt;span class="caps"&gt;QOS&lt;/span&gt; d&amp;#8217;autres protocoles classiques, et n&amp;#8217;a pas besoin d&amp;#8217;exclusivité sur la connexion (ou la carte&amp;nbsp;réseau).&lt;/p&gt;
&lt;p&gt;Les usages de Swift sont divers, ils peuvent être limités par&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le débit (disques&amp;nbsp;distants)&lt;/li&gt;
&lt;li&gt;la latence (base&amp;nbsp;clé/valeur)&lt;/li&gt;
&lt;li&gt;les &lt;span class="caps"&gt;IOPS&lt;/span&gt; (système de fichiers&amp;nbsp;distants)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Les gains en performance du réseau (et du stockage) sont en complet décalage avec les gains en performance des CPUs, obligés par la fin de Moore à multiplier ses&amp;nbsp;cœurs.&lt;/p&gt;
&lt;p&gt;Brancher des cartes 100G au cul de vos serveurs ne va pas suffire.
Pour profiter des performances monstrueuses du matériel moderne, il faut délester le &lt;span class="caps"&gt;CPU&lt;/span&gt;, et mettre le doigt dans l&amp;#8217;explosion cambrienne des offres d&amp;#8217;offloading en choisissant une abstraction pérenne (&lt;span class="caps"&gt;DPDK&lt;/span&gt;, P4, &lt;span class="caps"&gt;SPDK&lt;/span&gt;…) pour ne pas se verrouiller avec un&amp;nbsp;constructeur.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;DMA&lt;/span&gt; et le zero-copy ont le vent en&amp;nbsp;poupe.&lt;/p&gt;
&lt;p&gt;Le noyau Linux (et ses interruptions) n&amp;#8217;est pas à l&amp;#8217;aise pour gérer efficacement ce nouveau palier de performance matérielle, mais il avance vers la communication non bloquante (avec les ring), et propose des points d&amp;#8217;entrée pour mixer espace noyau et espace utilisateur de manière efficace.
La virtualisation va dans le même sens, et permet d&amp;#8217;exposer des &lt;span class="caps"&gt;API&lt;/span&gt; classiques (dans les machines virtuelles) alors que l&amp;#8217;hôte utilise des &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;modernistes.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article prétend traiter du &lt;em&gt;tcpless&lt;/em&gt;, parce que le nom est&amp;nbsp;porteur.&lt;/p&gt;
&lt;p&gt;Mais avant de s&amp;#8217;attaquer aux protocoles alternatifs, la première étape est d&amp;#8217;utiliser l&amp;#8217;offloading fourni par la carte réseau, puis les outils réseaux en espace utilisateur. &lt;span class="caps"&gt;DPDK&lt;/span&gt; for the&amp;nbsp;win.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;y a pas de nouveaux protocoles réseau libres pour les datacenters (RoCE peut-être?) mais sortir de &lt;span class="caps"&gt;TCP&lt;/span&gt; parait être l&amp;#8217;étape indispensable pour la&amp;nbsp;désagrégation.&lt;/p&gt;
&lt;p&gt;Passer d&amp;#8217;une pile &lt;span class="caps"&gt;IP&lt;/span&gt; en espace noyau, avec les &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;POSIX&lt;/span&gt; a une pile &lt;span class="caps"&gt;IP&lt;/span&gt; en espace utilisateur avec une &lt;span class="caps"&gt;API&lt;/span&gt; éxotique doit être justifié par un vrai gain.
Passer par virtio a au moins le mérite d&amp;#8217;être transparent pour&amp;nbsp;l&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;Tester les gestionnaires de congestions modernes (dctcp, bbr v3) et voir ce que ça change au niveau des latences et des pertes de paquets a le bon gout d&amp;#8217;être bien moins&amp;nbsp;intrusif.&lt;/p&gt;
&lt;p&gt;Écrire son petit protocole réseau à soi est une tache dantesque, sans compter qu&amp;#8217;ensuite il faut le maintenir et gérer les potentiels (inévitables ?) incidents. Puis patcher&amp;nbsp;Ceph.&lt;/p&gt;
&lt;p&gt;J&amp;#8217;aurais peut-être dû nommer l&amp;#8217;article &amp;#8220;l&amp;#8217;ère du post-&lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;#8221;.&lt;/p&gt;</content><category term="Ops"></category><category term="tcp"></category><category term="sdn"></category><category term="dpdk"></category><category term="1rma"></category></entry><entry><title>Diskless, le stockage sans disque</title><link href="http://blog.garambrogne.net/diskless.html" rel="alternate"></link><published>2024-05-29T09:04:00+02:00</published><updated>2024-05-29T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-05-29:/diskless.html</id><summary type="html">&lt;p&gt;Le diskless, sans disques, est une optimisation (radicale) pour gérer un ensemble de serveurs (réels, virtualisés, ou même des conteneurs), en déportant le&amp;nbsp;stockage.&lt;/p&gt;</summary><content type="html">&lt;h1 id="diskless"&gt;Diskless&lt;/h1&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Less is more&amp;#8221; comme disent les inventeurs de&amp;nbsp;buzzwords.&lt;/p&gt;
&lt;p&gt;Diskless, sans disques, est une optimisation (radicale) pour gérer un ensemble de serveurs (réels, virtualisés, ou même des&amp;nbsp;conteneurs).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.starwars.com/databank/death-star-plans"&gt;&lt;img alt="Death Star" class="image-process-article-image" src="/images/death-star-plans.jpg" title="Le disque contenant les plans de l'étoile de la mort" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="desagregation"&gt;Désagrégation&lt;/h2&gt;
&lt;p&gt;Un serveur, c&amp;#8217;est un ensemble processeur (&lt;span class="caps"&gt;CPU&lt;/span&gt;/&lt;span class="caps"&gt;GPU&lt;/span&gt;) / mémoire / stockage avec du réseau.
Les utilisateurs, rien que pour embêter l&amp;#8217;hébergeur, ne vont pas vouloir un ratio fixe entre ces ressources matérielles, et même vouloir changer&amp;nbsp;d&amp;#8217;avis.&lt;/p&gt;
&lt;p&gt;La désagrégation propose d&amp;#8217;exploser ses ressources, et de composer un serveur, en piochant dans son pool de&amp;nbsp;ressources.&lt;/p&gt;
&lt;p&gt;Spoiler alert, même avec les performances monstrueuses que peuvent atteindre les cartes réseau modernes, on ne peut pas encore généraliser la mémoire désagrégée, même si la norme &lt;a href="https://cxl.docs.kernel.org/research.html"&gt;&lt;span class="caps"&gt;CXL&lt;/span&gt;&lt;/a&gt; existe, et des gens s&amp;#8217;amusent avec.
Mais si c&amp;#8217;est techniquement faisable, il n&amp;#8217;y a pas d&amp;#8217;abstractions magiques et universelles.
Les machines multiprocesseurs ont encore des histoires de &lt;a href="https://en.wikipedia.org/wiki/Non-uniform_memory_access"&gt;&lt;span class="caps"&gt;NUMA&lt;/span&gt;&lt;/a&gt;, et il est toujours pertinent de profiter de la localité des données et des différents caches.
Des abstractions de plus haut niveau ont déjà prouvé leur pertinence, comme &lt;a href="https://www.open-mpi.org/"&gt;Open-&lt;span class="caps"&gt;MPI&lt;/span&gt;&lt;/a&gt;, qui évite de lier du code métier à du matériel&amp;nbsp;exotique.&lt;/p&gt;
&lt;p&gt;Externaliser le stockage des ordinateurs existe depuis très longtemps, ajouter des boites reliées par des câbles faisant partit des plaisirs simples de&amp;nbsp;l&amp;#8217;informaticien.&lt;/p&gt;
&lt;h2 id="debit-de-disques"&gt;Débit de&amp;nbsp;disques&lt;/h2&gt;
&lt;p&gt;Les branchements ont d&amp;#8217;abord était cul à cul, puis chainés, switchés, pour enfin utiliser du réseau comme tout le&amp;nbsp;monde.&lt;/p&gt;
&lt;p&gt;Les grosses cartes réseaux proposent maintenant de faire plus que du réseau, et mettent à disposition des processeurs ARMs ou du &lt;span class="caps"&gt;FPGA&lt;/span&gt;.
On parle de SmartNIC, ou de &lt;span class="caps"&gt;NPU&lt;/span&gt;.
Classiquement, la carte propose d&amp;#8217;effectuer le chiffrage, les checksums, ou de filer un coup de main pour la virtualisation réseau (comme vSwitch), de prendre en charge le &lt;a href="https://en.wikipedia.org/wiki/Express_Data_Path"&gt;&lt;span class="caps"&gt;XDP&lt;/span&gt;&lt;/a&gt;.
&lt;a href="https://www.dpdk.org/"&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt;&lt;/a&gt; propose d&amp;#8217;unifier toute cette magie gravée dans le&amp;nbsp;silicium.&lt;/p&gt;
&lt;p&gt;Un réseau &amp;#8220;normal&amp;#8221; est en 10G, sachant que le 100G existe, et que l&amp;#8217;on peut agréger les liens pour additionner les bandes&amp;nbsp;passantes.&lt;/p&gt;
&lt;p&gt;Vous pouvez mettre ce genre de &lt;a href="https://www.opencompute.org/products/91/edgecore-minipack-as8000"&gt;monstre, 128 * 100Gb, soit 12,8Tb en &lt;span class="caps"&gt;4RU&lt;/span&gt; &lt;/a&gt; comme top of rack, mais bon courage pour connecter les racks avec une bande passante&amp;nbsp;suffisante.&lt;/p&gt;
&lt;p&gt;Il est compliqué de comparer les technologies, le débit n&amp;#8217;est qu&amp;#8217;une des métriques (il peut être symétrique ou asymétrique), les &lt;span class="caps"&gt;IOPS&lt;/span&gt; changent complètement entre les disques à plateaux et &lt;span class="caps"&gt;SSD&lt;/span&gt;, les latences changent d&amp;#8217;ordres de grandeur selon les&amp;nbsp;technologies.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;SATA&lt;/span&gt; est encore utilisé pour du stockage massif (comme le propose &lt;a href="https://www.backblaze.com/blog/next-backblaze-storage-pod/"&gt;Backblaze&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;L&amp;#8217;&lt;span class="caps"&gt;USB4&lt;/span&gt; intègre Thunderbolt, mais il n&amp;#8217;est pas conçu pour les datacenter, il est possible de relier des baies de disques en &lt;span class="caps"&gt;SAS&lt;/span&gt; (en cuivre ou en fibre optique), pour former un gros &lt;span class="caps"&gt;SAN&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;le &lt;span class="caps"&gt;CXL&lt;/span&gt; se base sur PCIe 5.0 et 6.0, mais cible la &lt;span class="caps"&gt;RAM&lt;/span&gt;, pas le&amp;nbsp;stockage.&lt;/p&gt;
&lt;p&gt;Samsung arrive à saturer (de peu) les 63G du PCIe 4.0 en x4 avec son &lt;span class="caps"&gt;SSD&lt;/span&gt; &lt;a href="https://semiconductor.samsung.com/ssd/enterprise-ssd/pm1733-pm1735/"&gt;&lt;span class="caps"&gt;PM1735&lt;/span&gt;&lt;/a&gt; et donc utilise du&amp;nbsp;x8.&lt;/p&gt;
&lt;p&gt;La Pmem (mémoire persistante), est connectée comme une barrette de &lt;span class="caps"&gt;RAM&lt;/span&gt;, en &lt;span class="caps"&gt;RDIMM&lt;/span&gt;.
Les performances sont comparables à du PCIe 6.0 en x16.
Le chiffre est pour comparer avec les autres technologies, l&amp;#8217;intérêt de la Pmem est sa latence, et la proximité avec le cache du&amp;nbsp;processeur.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technologie&lt;/th&gt;
&lt;th&gt;Débit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;SATA&lt;/span&gt; 3&lt;/td&gt;
&lt;td&gt;4.8Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.seagate.com/files/www-content/datasheets/pdfs/exos-x16-DS2011-1-1904US-en_US.pdf"&gt;&lt;span class="caps"&gt;HDD&lt;/span&gt; 16To&lt;/a&gt; lecture séquentielle&lt;/td&gt;
&lt;td&gt;2 Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;SAS&lt;/span&gt;-5&lt;/td&gt;
&lt;td&gt;45Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;USB4&lt;/span&gt; Gen 3x2&lt;/td&gt;
&lt;td&gt;40Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;USB4&lt;/span&gt; gen 4&lt;/td&gt;
&lt;td&gt;80Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fibre channel gen 7&lt;/td&gt;
&lt;td&gt;64Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fibre channel gen 8&lt;/td&gt;
&lt;td&gt;128Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infiniband &lt;span class="caps"&gt;NDR&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;106Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIe 4.0 x4&lt;/td&gt;
&lt;td&gt;63Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIe 4.0 x8&lt;/td&gt;
&lt;td&gt;126Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIe 6.0 x16&lt;/td&gt;
&lt;td&gt;968Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pmem&lt;/td&gt;
&lt;td&gt;1000Gbit/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;C&amp;#8217;est rare que le matériel soit utilisé en 1 seul exemplaire, les benchs &lt;span class="caps"&gt;SPDK&lt;/span&gt; sont faits sur un serveur avec 2 processeurs de 28 cœurs, 14 disques &lt;span class="caps"&gt;SSD&lt;/span&gt;, 4 cartes réseau de 100Gbits, pour un résultat maximal de 330Gbit/s (sur 400 théoriques). Un serveur de &lt;a href="https://www.opencompute.org/products?cloud_products%5BrefinementList%5D%5Bhardware.categories.Storage%5D%5B0%5D=Storage+Systems+%28Server+%26+JBOD%29"&gt;stockage à haute densité, c&amp;#8217;est 70 disques&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://boingboing.net/2009/12/11/junkbots-made-from-o.html"&gt;&lt;img alt="junkbots" class="image-process-article-image" src="images/hddrobot.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="disque-reseau"&gt;Disque&amp;nbsp;réseau&lt;/h2&gt;
&lt;p&gt;Un tas de disques utilisé via du réseau par un tas de serveurs s&amp;#8217;appelle un &lt;a href="https://en.wikipedia.org/wiki/Storage_area_network"&gt;&lt;span class="caps"&gt;SAN&lt;/span&gt;&lt;/a&gt;, Storage Area Network, et à pendant longtemps nécessité du matériel spécifique (et&amp;nbsp;cher).&lt;/p&gt;
&lt;h3 id="protocoles"&gt;Protocoles&lt;/h3&gt;
&lt;h4 id="iscsi"&gt;iSCSI&lt;/h4&gt;
&lt;p&gt;L&amp;#8217;un des vénérables protocoles de disque dur, &lt;a href="https://en.wikipedia.org/wiki/SCSI"&gt;&lt;span class="caps"&gt;SCSI&lt;/span&gt;&lt;/a&gt; (première version en 1986 quand même), avec ses gros câbles qui changeaient du tout au tout à chaque nouvelle version majeure, a eu droit à son passage au réseau, avec &lt;a href="https://en.wikipedia.org/wiki/ISCSI"&gt;iSCSI&lt;/a&gt;, au début des années&amp;nbsp;2000.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Fibre_Channel"&gt;Fibre Channel&lt;/a&gt; (fibre et non fiber, pour faire chic) permet (entre autres) de faire passer des commandes &lt;span class="caps"&gt;SCSI&lt;/span&gt; sur un réseau switché (plusieurs ordinateurs peuvent se connecter à plusieurs disques).
Fibre channel est luxueux, il nécessite du matériel et des compétences&amp;nbsp;spécifiques.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée du iSCSI est simple, faire passer les messages &lt;span class="caps"&gt;SCSI&lt;/span&gt; en &lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt;, et permettre ainsi de contrôler des lecteurs de bandes, de changer des cassettes, des &lt;span class="caps"&gt;CD&lt;/span&gt;-ROMs, et aussi des disques durs, sur un réseau&amp;nbsp;classique.&lt;/p&gt;
&lt;p&gt;40 ans de &lt;span class="caps"&gt;SCSI&lt;/span&gt;, 20 ans de iSCSI, sa réputation n&amp;#8217;est plus à faire, même si les disques &lt;span class="caps"&gt;SCSI&lt;/span&gt; n&amp;#8217;ont pas passé l&amp;#8217;an 2000 (remplacés par le &lt;a href="https://en.wikipedia.org/wiki/Serial_Attached_SCSI"&gt;&lt;span class="caps"&gt;SAS&lt;/span&gt;&lt;/a&gt;) avant la déferlante &lt;span class="caps"&gt;SSD&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="nbd"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Network_block_device"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/a&gt; est un protocole vintage, 1997, mais simple, qui permet d&amp;#8217;exposer des blocs en &lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt;.
Datant de l&amp;#8217;époque des disques à plateaux, il ne propose pas de parallélisation, chose que font très bien les&amp;nbsp;SSDs.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt; a gagné le statut de format &amp;#8220;presque standard&amp;#8221; pour exposer des&amp;nbsp;blocs.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pojntfx/go-nbd"&gt;go-nbd&lt;/a&gt; est fort pratique pour bricoler du bloc avec golang, et je vous recommande ma démo &lt;a href="https://github.com/athoune/stream-my-root"&gt;stream-my-root&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;QEMU&lt;/span&gt; l&amp;#8217;utilise pour exposer ses images &lt;span class="caps"&gt;QCOW2&lt;/span&gt; avec &lt;a href="https://www.qemu.org/docs/master/tools/qemu-nbd.html"&gt;qemu-nbd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Autre fan de &lt;span class="caps"&gt;QCOW2&lt;/span&gt;, libguestfs propose un projet enthousiaste, &lt;a href="https://gitlab.com/nbdkit/nbdkit"&gt;nbdkit&lt;/a&gt; pour gérer un protocole&amp;nbsp;placide.&lt;/p&gt;
&lt;h4 id="ceph-rbd"&gt;Ceph &lt;span class="caps"&gt;RBD&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://ceph.com/en/"&gt;Ceph&lt;/a&gt; se positionne comme la solution ultime et universelle du stockage réseau : il gère les 3 modèles : blocs, fichiers, blobs.
Il est rétrocompatible avec l&amp;#8217;existant: système de fichiers &lt;span class="caps"&gt;UNIX&lt;/span&gt;, iSCSI, &lt;span class="caps"&gt;NFS&lt;/span&gt;, S3, et avec ses propres&amp;nbsp;protocoles.&lt;/p&gt;
&lt;p&gt;Ceph est le service bloc standard des gens qui n&amp;#8217;ont pas de &lt;span class="caps"&gt;SAN&lt;/span&gt; iSCSI (Digital Ocean, &lt;span class="caps"&gt;OVH&lt;/span&gt;, Scaleway … Hetzner&amp;nbsp;?).&lt;/p&gt;
&lt;h4 id="nvmeof"&gt;NVMeOF&lt;/h4&gt;
&lt;p&gt;Les premiers &lt;span class="caps"&gt;SSD&lt;/span&gt; avaient une interface &lt;span class="caps"&gt;SATA&lt;/span&gt;, pour faire la transition avec les disques à plateaux, et ont rapidement saturé les performances de ce bus.
Le NVMe est un &lt;span class="caps"&gt;SSD&lt;/span&gt; qui utilise directement du PCIe, de manière non bloquante, tous les cœurs d&amp;#8217;un &lt;span class="caps"&gt;CPU&lt;/span&gt; peuvent discuter avec le disque.
Le NVMe est apparu sous forme de grosses cartes PCIe, puis finalement dans des formats plus pratiques, comme du &lt;a href="https://en.wikipedia.org/wiki/M.2"&gt;M.2&lt;/a&gt; pour le grand publique, et du U.2 ou U.3 pour les serveurs (en conservant les racks utilisés par &lt;span class="caps"&gt;SATA&lt;/span&gt; et &lt;span class="caps"&gt;SAS&lt;/span&gt;, et la connexion à&amp;nbsp;chaud).&lt;/p&gt;
&lt;p&gt;NVMeOF, comme NVMe Over Fabric, reprend l&amp;#8217;astuce du iSCSI et fait passer les messages NVMe prévus pour du PCIe sur du réseau qui lui aussi utilise du PCIe.
Le PCIe utilisé par NVMe et le réseau permettent des optimisations redoutables, comme le &lt;span class="caps"&gt;RDMA&lt;/span&gt;, Remote Direct Memory Access, la carte réseau peut exposer un disque NVMe distant, sans passer par le processeur.
La communication de carte à carte pouvant être faite en Fibre Channel, Infiniband, ou tout simplement en Ethernet (avec &lt;a href="https://en.wikipedia.org/wiki/RDMA_over_Converged_Ethernet"&gt;&lt;span class="caps"&gt;ROCE&lt;/span&gt;&lt;/a&gt; &lt;span class="caps"&gt;RDMA&lt;/span&gt; Over Converged&amp;nbsp;Ethernet).&lt;/p&gt;
&lt;p&gt;NVMeOF peut aussi être géré de manière logicielle (pouvant profiter de l&amp;#8217;accélération matérielle de la carte réseau), côté client ou serveur, sous l&amp;#8217;égide du projet &lt;a href="https://spdk.io/"&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt;&lt;/a&gt;, qui centralise tous les efforts NVMe pour&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Du MVMeOF logiciel ne dépend pas de NVMe et peut donc partager un gros disque &lt;span class="caps"&gt;SATA&lt;/span&gt; ou même un disque&amp;nbsp;dynamique.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://spdk.io/doc/performance_reports.html"&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt; a fait de chouettes benchmarks&lt;/a&gt;, qui mettent bien en valeur le gain en latence de &lt;span class="caps"&gt;RDMA&lt;/span&gt; face à &lt;span class="caps"&gt;TCP&lt;/span&gt; : 6 fois&amp;nbsp;mieux.&lt;/p&gt;
&lt;h3 id="les-blocs-du-noyau-linux"&gt;Les blocs du noyau&amp;nbsp;Linux&lt;/h3&gt;
&lt;p&gt;Le noyau Linux fournit les briques de base pour gérer des disques, que ce soit en espace noyau ou utilisateur, avec sa fascination pour le rangement et le&amp;nbsp;dédoublonnage.&lt;/p&gt;
&lt;h4 id="bdev"&gt;bdev&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://linux-kernel-labs.github.io/refs/heads/master/labs/block_device_drivers.html"&gt;bdev&lt;/a&gt; est la couche de base pour écrire un pilote pour exposer des&amp;nbsp;blocs.&lt;/p&gt;
&lt;h4 id="io_uring"&gt;io_uring&lt;/h4&gt;
&lt;p&gt;Le noyau Linux a galéré pour proposer une &lt;span class="caps"&gt;API&lt;/span&gt; asynchrone pour la gestion du&amp;nbsp;stockage.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://libuv.org/"&gt;Libuv&lt;/a&gt;, grand unificateur des &lt;span class="caps"&gt;API&lt;/span&gt; asynchrones, &lt;a href="https://docs.libuv.org/en/v1.x/design.html#the-i-o-loop"&gt;se contente d&amp;#8217;un pool de threads pour gérer les accès&amp;nbsp;disques&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Il y a eu la tentative &lt;span class="caps"&gt;AIO&lt;/span&gt;, mais sans &lt;span class="caps"&gt;API&lt;/span&gt; équivalente pour les sockets, ça rend les interactions&amp;nbsp;compliquées.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Io_uring"&gt;io_uring&lt;/a&gt; permet une communication non bloquante avec le stockage avec d&amp;#8217;excellente&amp;nbsp;performance.&lt;/p&gt;
&lt;p&gt;En juin 2023, &lt;a href="https://security.googleblog.com/2023/06/learnings-from-kctf-vrps-42-linux.html"&gt;Google a constaté que pour l&amp;#8217;année 2022, 60% des failles Linux soumises à son bug bounty étaient liées à &lt;em&gt;io_uring&lt;/em&gt;&lt;/a&gt;, du coup bannissement massif dans l&amp;#8217;écosystème Google, &lt;a href="https://github.com/moby/moby/commit/891241e7e74d4aae6de5f6125574eb994f25e169"&gt;suivi par Docker&lt;/a&gt; qui en a entrainé d&amp;#8217;autres comme &lt;a href="https://github.com/oven-sh/bun/issues/785"&gt;Bun&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On attend le retour en grâce du très prometteur &lt;em&gt;io_uring&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id="ublk"&gt;ublk&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/block/ublk.html"&gt;ublk&lt;/a&gt; permet aux kernels d&amp;#8217;exposer des blocks, en espace utilisateur.
Ublk utilise &lt;a href="https://github.com/axboe/liburing"&gt;io_uring&lt;/a&gt; pour un échange sans interruption entre les espaces noyau
et utilisateur.
C&amp;#8217;est une bonne nouvelle pour les performances, mais il y a eu de grosses failles de sécurité avec io_uring, ce qui lui a valu un bannissement (temporaire) de Google et Docker.
Il faut donc attendre la bénédiction d&amp;#8217;un acteur majeur avant de pouvoir profiter de cette prometteuse&amp;nbsp;technologie.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ublk-org/ublksrv/"&gt;ublksrv&lt;/a&gt; est le cadriciel en espace utilisateur (le framework en usersapce) pour &lt;em&gt;ublk&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Ublksrv permet par exemple de monter des images &lt;span class="caps"&gt;QCOW2&lt;/span&gt;, et il sera possible de faire du &lt;span class="caps"&gt;NBD&lt;/span&gt; (avec du zero-copy entre le block et le&amp;nbsp;réseau).&lt;/p&gt;
&lt;p&gt;Ublk est aux blocs ce que &lt;span class="caps"&gt;FUSE&lt;/span&gt; est aux&amp;nbsp;fichiers.&lt;/p&gt;
&lt;h4 id="tcmu"&gt;&lt;span class="caps"&gt;TCMU&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span class="caps"&gt;TCM&lt;/span&gt; permet de créer une cible &lt;span class="caps"&gt;SCSI&lt;/span&gt; (un serveur) dans le noyau Linux.
&lt;a href="https://docs.kernel.org/target/tcmu-design.html"&gt;&lt;span class="caps"&gt;TCMU&lt;/span&gt;&lt;/a&gt; est un &lt;span class="caps"&gt;TCM&lt;/span&gt; Utilisateur, et permet donc à une application de se comporter comme un disque &lt;span class="caps"&gt;SCSI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;FUSE&lt;/span&gt; expose des fichiers, &lt;em&gt;ublk&lt;/em&gt; des blocs, &lt;span class="caps"&gt;TCMU&lt;/span&gt; une cible &lt;span class="caps"&gt;SCSI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TCMU&lt;/span&gt; a été utilisé avant l&amp;#8217;arrivée de &lt;em&gt;ublk&lt;/em&gt; ou&amp;nbsp;vhost-user.&lt;/p&gt;
&lt;h4 id="device-mapper"&gt;Device&amp;nbsp;mapper&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/index.html"&gt;Device Mapper&lt;/a&gt; permet de composer sa gestion de blocs en empilant des fonctionnalités plus ou moins exotiques (compression, chiffrage, cache, raid, instantanés, &lt;span class="caps"&gt;COW&lt;/span&gt; …) et même du débug (mesure de performances, ajout de latences…).
Device Mapper sert de base à &lt;span class="caps"&gt;LVM&lt;/span&gt;, Logical Volume&amp;nbsp;Manager.&lt;/p&gt;
&lt;h4 id="vfio"&gt;vfio&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.kernel.org/doc/html/latest/driver-api/vfio.html"&gt;vfio&lt;/a&gt; permet d&amp;#8217;exposer en espace utilisateur le &lt;span class="caps"&gt;DMA&lt;/span&gt; d&amp;#8217;un périphérique, emballé dans un &lt;span class="caps"&gt;IOMMU&lt;/span&gt;. Le périphérique exposé par &lt;span class="caps"&gt;VFIO&lt;/span&gt; peut être confié à une application, ou même un&amp;nbsp;conteneur.&lt;/p&gt;
&lt;h4 id="vdpa"&gt;vDPA&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.kernel.org/doc/html/latest/userspace-api/vduse.html"&gt;vDPA/&lt;span class="caps"&gt;VDUSE&lt;/span&gt;&lt;/a&gt; comme &amp;#8220;virtio data path acceleration&amp;#8221; et &amp;#8220;&lt;span class="caps"&gt;VDPA&lt;/span&gt; Device in USEr space&amp;#8221; est une norme pour avoir des drivers en espace utilisateurs qui bénéficient des spécificités d&amp;#8217;un drivers (accélérations et délestent proposés par une carte réseau futée (smart &lt;span class="caps"&gt;NIC&lt;/span&gt;/&lt;span class="caps"&gt;DPU&lt;/span&gt;), ou du &lt;span class="caps"&gt;DMA&lt;/span&gt; bordé par un &lt;span class="caps"&gt;IOMMU&lt;/span&gt;).
L&amp;#8217;idée de conserver l&amp;#8217;isolation fournie par une machine virtuelle ou un conteneur, mais de bénéficier des spécificités d&amp;#8217;un matériel, sans émulation ou paraphrases.
&lt;span class="caps"&gt;VDUSE&lt;/span&gt; permet d&amp;#8217;utiliser un périphérique virtio dans le kernel hôte, et de le confier à un conteneur.
vDPA permet a &lt;span class="caps"&gt;QEMU&lt;/span&gt; d&amp;#8217;utiliser un matériel spécifique, on parle alors de &amp;#8220;fast path&amp;#8221;, et si l&amp;#8217;on souhaite migrer la machine virtuelle sur un hôte sans l&amp;#8217;accélération matérielle, il basculera en émulation, on parle de &amp;#8220;slow path&amp;#8221; (et ça va commencer à ramer&amp;nbsp;sévèrement).&lt;/p&gt;
&lt;h3 id="les-virtios-de-kvm"&gt;les Virtios de &lt;span class="caps"&gt;KVM&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Virtio normalise (à la hache) des drivers minimalistes pour que les &lt;span class="caps"&gt;OS&lt;/span&gt; invités utilisent des pilotes simple et minimaliste, et quand c&amp;#8217;est possible du &amp;#8220;passage direct&amp;#8221; entre l&amp;#8217;invité et l&amp;#8217;hôte (du pass through,&amp;nbsp;quoi).&lt;/p&gt;
&lt;p&gt;Virtio est utilisé par les différentes solutions de virtualisation basées sur &lt;span class="caps"&gt;KVM&lt;/span&gt;, mais aussi par &lt;a href="https://wiki.xenproject.org/wiki/Virtio_On_Xen"&gt;Xen&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="virtio-scsi-et-virtio-blk"&gt;virtio-scsi et&amp;nbsp;virtio-blk&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;virtio-scsi&lt;/em&gt; permet d&amp;#8217;exposer du bloc comme un disque &lt;span class="caps"&gt;SCSI&lt;/span&gt;, c&amp;#8217;est standard pour l&amp;#8217;invité, mais ça tire la longue histoire de &lt;span class="caps"&gt;SCSI&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;virtio-blk&lt;/em&gt; se contente d&amp;#8217;exposer du&amp;nbsp;bloc.&lt;/p&gt;
&lt;p&gt;Par défaut, côté hôte, les services sont statiques et exposent des images&amp;nbsp;disques.&lt;/p&gt;
&lt;p&gt;Pour fournir dynamiquement une image disque, il est possible de faire des bricolages honteux, en servant l&amp;#8217;image disque via un montage &lt;span class="caps"&gt;FUSE&lt;/span&gt;, comme dans &lt;a href="https://arxiv.org/abs/2305.13162"&gt;l&amp;#8217;article &lt;span class="caps"&gt;AWS&lt;/span&gt; sur le streaming de disque pour firecracker&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="vhost-user"&gt;vhost-user&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://qemu-project.gitlab.io/qemu/interop/vhost-user.html"&gt;vhost-user&lt;/a&gt; est le protocole officiel pour exposer un service en espace utilisateur, à un virtio, coté invité.
&lt;em&gt;vhost-user&lt;/em&gt; utilise un anneau pour discuter sans interruption entre l&amp;#8217;hôte et l&amp;#8217;invité, de manière comparable à ce qu&amp;#8217;utilise &lt;em&gt;io_uring&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;vhost-user peut être utilisé pour discuter avec tous les virtio (comme le &lt;a href="https://github.com/rust-vmm/vhost-device"&gt;vhost-device&lt;/a&gt; de rust-vmm),
mais aussi virtio-blk, et il existe diverses implémentations de backends (comme le &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/3853362520b81efc8ce6559148d023379a5a4da4/docs/api_requests/block-vhost-user.md"&gt;documente Firecracker&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Qemu backend : &lt;em&gt;qemu-storage-daemon&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Cloud Hypervisor&amp;nbsp;backend&lt;/li&gt;
&lt;li&gt;crosvm backend (la &lt;span class="caps"&gt;VM&lt;/span&gt; de ChromeOS, l&amp;#8217;un des fondateurs de&amp;nbsp;rust-vmm)&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt;&amp;nbsp;backend&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="libblkio"&gt;Libblkio&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://libblkio.gitlab.io/libblkio/"&gt;libblkio&lt;/a&gt; est une bibliothèque rust qui se focalise sur son &lt;span class="caps"&gt;API&lt;/span&gt; C, et propose de brancher tout vers tout : io_uring, NVMe, virtio-blk (vhost-user ou même vhost-vdpa et&amp;nbsp;vfio-pci).&lt;/p&gt;
&lt;h3 id="les-blocs-du-cloud"&gt;Les blocs du&amp;nbsp;Cloud&lt;/h3&gt;
&lt;p&gt;Toutes les offres Cloud proposent des disques réseau tellement pratiques à&amp;nbsp;l&amp;#8217;usage.&lt;/p&gt;
&lt;p&gt;Les gros du cloud ont des outils maison, la plupart des autres utilisent Ceph, sans forcément le préciser, alors que &lt;a href="https://www.digitalocean.com/blog/why-we-chose-ceph-to-build-block-storage"&gt;Digital Ocean en profite pour faire de la pub sur le bouquin de leur Mr&amp;nbsp;Ceph&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="cinder"&gt;Cinder&lt;/h4&gt;
&lt;p&gt;Openstack souhaite normaliser le Cloud, ce qui est une idée sympathique, il propose une &lt;span class="caps"&gt;API&lt;/span&gt; normalisée et tous les fabricants de matériel fournisse un&amp;nbsp;pilote.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.openstack.org/cinder/latest/"&gt;Cinder&lt;/a&gt; est le service de blocs&amp;nbsp;d&amp;#8217;Openstack.&lt;/p&gt;
&lt;p&gt;Il propose &lt;a href="https://docs.openstack.org/cinder/latest/drivers.html"&gt;une quantité invraisemblable de pilotes&lt;/a&gt;, avec pas mal de SANs, et les grands classiques open&amp;nbsp;sources.&lt;/p&gt;
&lt;h4 id="amazon-ebs"&gt;Amazon &lt;span class="caps"&gt;EBS&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; aime bien rester nébuleux dans ses pages produits, mais il &lt;a href="https://aws.amazon.com/fr/blogs/aws/now-in-preview-larger-faster-io2-ebs-volumes-with-higher-throughput/"&gt;donne des indices sur son stockage bloc &lt;em&gt;io2&lt;/em&gt;&lt;/a&gt;, et d&amp;#8217;autres infos techniques dans &lt;a href="https://www.youtube.com/watch?v=kaWzAEVZ6k8"&gt;cette présentation re:Invent de 2012&lt;/a&gt; (il faut zapper les présentations produits pour se concentrer sur le&amp;nbsp;techos).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NVMeOF&lt;/li&gt;
&lt;li&gt;Le réseau (&lt;span class="caps"&gt;IP&lt;/span&gt;) de stockage est isolé du réseau&amp;nbsp;classique&lt;/li&gt;
&lt;li&gt;Pas de &lt;span class="caps"&gt;TCP&lt;/span&gt;, mais du &lt;a href="https://ieeexplore.ieee.org/document/9167399"&gt;&lt;span class="caps"&gt;SRD&lt;/span&gt;&lt;/a&gt;, qui utilise du&amp;nbsp;multipath&lt;/li&gt;
&lt;li&gt;4Gbps par&amp;nbsp;volume&lt;/li&gt;
&lt;li&gt;Accélération matérielle avec la carte &lt;a href="https://aws.amazon.com/fr/ec2/nitro/"&gt;Nitro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="google-cloud-hyperdisk"&gt;Google Cloud&amp;nbsp;Hyperdisk&lt;/h4&gt;
&lt;p&gt;De manière prévisible, Google donne peu d&amp;#8217;informations techniques sur son &lt;a href="https://cloud.google.com/compute/docs/disks/hyperdisks"&gt;Hyperdisks&lt;/a&gt;, l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; invité lui cause en &lt;span class="caps"&gt;SCSI&lt;/span&gt; (le virtio-scsi ?) ou en NVMe (du NVMeOF exposé par leur carte SmartNIC &lt;a href="https://github.com/GoogleCloudPlatform/compute-virtual-ethernet-linux"&gt;gvNIC&lt;/a&gt;&amp;nbsp;?)&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://cloud.google.com/compute/docs/disks/hyperdisks#hyperdisk-throughput"&gt;débit maximum&lt;/a&gt; de 24 Gbit/s (avec 5 disques) est assez&amp;nbsp;énorme.&lt;/p&gt;
&lt;p&gt;Hyperdisk est fier de découpler les &lt;span class="caps"&gt;IOPS&lt;/span&gt; et bande passante du nombre de &lt;span class="caps"&gt;CPU&lt;/span&gt;, le ratio fixe par &lt;span class="caps"&gt;CPU&lt;/span&gt; est classique pour la &lt;span class="caps"&gt;RAM&lt;/span&gt; ou le disque local, mais là, hyperdisk désagrège le stockage, et l&amp;#8217;on peut réserver (&lt;span class="caps"&gt;IOPS&lt;/span&gt;, bande passante) selon un planning, pour ne pas avoir à surdimensionner pour uniquement encaisser les pics&amp;nbsp;d&amp;#8217;utilisations.&lt;/p&gt;
&lt;h4 id="azure-elastic-san"&gt;Azure Elastic &lt;span class="caps"&gt;SAN&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;Azure se contente de vendre &lt;a href="https://learn.microsoft.com/en-us/azure/storage/elastic-san/elastic-san-performance"&gt;des tranches de &lt;span class="caps"&gt;SAN&lt;/span&gt;&lt;/a&gt;, avec une bande passante par To de stockage.
Le partage se fait en iSCSI sur un réseau&amp;nbsp;privé.&lt;/p&gt;
&lt;h3 id="les-blocs-de-lopen-source"&gt;Les blocs de l&amp;#8217;open&amp;nbsp;source&lt;/h3&gt;
&lt;p&gt;Les SANs sont hors de prix, les formats sont normalisés, ça fait deux raisons d&amp;#8217;avoir des implémentations libres de serveurs de disques en mode&amp;nbsp;blocs.&lt;/p&gt;
&lt;h4 id="open-iscsi"&gt;Open-&lt;span class="caps"&gt;ISCSI&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;La &lt;span class="caps"&gt;RFC3720&lt;/span&gt; est un poil aride, ça utilise de vieux protocoles (comme &lt;a href="https://en.wikipedia.org/wiki/Service_Location_Protocol"&gt;&lt;span class="caps"&gt;SLP&lt;/span&gt;&lt;/a&gt; ou &lt;a href="https://en.wikipedia.org/wiki/RADIUS"&gt;&lt;span class="caps"&gt;RADIUS&lt;/span&gt;&lt;/a&gt;), il faut du matériel exotique pour tester la partie &lt;span class="caps"&gt;RDMA&lt;/span&gt;, il n&amp;#8217;y a donc pas un enthousiasme fou pour implémenter iSCSI, c&amp;#8217;est donc
&lt;a href="https://github.com/open-iscsi/open-iscsi"&gt;open-isci&lt;/a&gt; qui s&amp;#8217;y colle, accompagné de &lt;a href="https://github.com/fujita/tgt"&gt;tgt&lt;/a&gt; pour la partie en espace utilisateur et son&amp;nbsp;daemon.&lt;/p&gt;
&lt;h4 id="spdk"&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://spdk.io/"&gt;Storage Performance Development Kit&lt;/a&gt; est, entre autres, le roi du NVMeOF.
Il cause aussi le iSCSI, le &lt;em&gt;vhost&lt;/em&gt; (pour virtio), le &lt;em&gt;ublk&lt;/em&gt; (pour Linux en espace utilisateur), le &lt;a href="https://github.com/spdk/spdk-csi"&gt;&lt;span class="caps"&gt;CSI&lt;/span&gt; de k8s&lt;/a&gt; et d&amp;#8217;autres trucs exotiques, mais surtout, il a un daemon, &lt;em&gt;spdk_tgt&lt;/em&gt; pour configurer tout vers tout, qui discute en &lt;span class="caps"&gt;JSON&lt;/span&gt;-&lt;span class="caps"&gt;RPC&lt;/span&gt;&amp;nbsp;2.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt; met à dispositions des fonctions de plus niveau, comme la &lt;a href="https://spdk.io/doc/logical_volumes.html"&gt;gestion des volumes et la création d&amp;#8217;instantanés&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt; utilisé des modules qui permettent d&amp;#8217;ajouter des fonctionnalités comme le &amp;#8220;copy on read and write&amp;#8221; du &lt;a href="https://github.com/ubicloud/bdev_ubi"&gt;bdev_ubi&lt;/a&gt; d&amp;#8217;Ubicloud, qui permet de cloner une image de base et de l&amp;#8217;utiliser tout de suite (attention à la licence non&amp;nbsp;libre)&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt; est souvent le backend caché derrière des applications de plus haut&amp;nbsp;niveau.&lt;/p&gt;
&lt;h4 id="ceph"&gt;Ceph&lt;/h4&gt;
&lt;p&gt;Ceph a comme couche basse &lt;span class="caps"&gt;RADOS&lt;/span&gt; (Reliable Autnonomous Distributed Object Store), au-dessus duquel vont être construits des services (utilisant &lt;em&gt;librados&lt;/em&gt; pour la plupart) comme &lt;span class="caps"&gt;RBD&lt;/span&gt; ou le NVMeOF de &lt;span class="caps"&gt;SPDK&lt;/span&gt;, et en dessous, il y a des&amp;nbsp;&amp;#8220;stores&amp;#8221;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.ceph.com/en/latest/rados/configuration/storage-devices/#filestore"&gt;Filestore&lt;/a&gt;, des fichiers sur du &lt;span class="caps"&gt;XFS&lt;/span&gt;, maintenant&amp;nbsp;déprécié.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ceph.com/en/latest/rados/configuration/storage-devices/#bluestore"&gt;BlueStore&lt;/a&gt;), du bloc cru (raw block), capable d&amp;#8217;utiliser du &lt;span class="caps"&gt;HDD&lt;/span&gt; et de profiter du &lt;span class="caps"&gt;SSD&lt;/span&gt; (et &lt;em&gt;pmem&lt;/em&gt;) pour les métadonnées et les&amp;nbsp;journaux.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ceph.com/en/latest/dev/seastore/"&gt;Seastore&lt;/a&gt;, prochainement, du full NVMe avec &lt;span class="caps"&gt;SPDK&lt;/span&gt; pour avoir une gestion fine du &lt;span class="caps"&gt;TRIM&lt;/span&gt; et proposer du zero copy (avec l&amp;#8217;aide de &lt;span class="caps"&gt;DPDK&lt;/span&gt;, pour peu que l&amp;#8217;on ait une carte réseau avec du &lt;span class="caps"&gt;DMA&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Des travaux sont effectués pour gérer les &lt;a href="http://zonedstorage.io/"&gt;disques zonés&lt;/a&gt; (comme les &lt;span class="caps"&gt;HDD&lt;/span&gt; &lt;span class="caps"&gt;SMR&lt;/span&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ceph propose des fonctions avancées comme la création d&amp;#8217;instantanés et le &lt;span class="caps"&gt;COW&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Ceph sait parler le Kubernetes avec &lt;a href="https://github.com/ceph/ceph-csi"&gt;ceph-sci&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Débuté lors d&amp;#8217;une thèse, le projet a intéressé des scientifiques générant beaucoup de données (Los Almos, le &lt;span class="caps"&gt;CERN&lt;/span&gt;…) avant d&amp;#8217;être rachetés par Red Hat, qui a créé une fondation avec d&amp;#8217;autres participants de prestige.
Suse a d&amp;#8217;abord collaboré avant de bouder et mettre en avant son&amp;nbsp;Longhorn.&lt;/p&gt;
&lt;h4 id="sheepdog"&gt;Sheepdog&lt;/h4&gt;
&lt;p&gt;Feu &lt;a href="https://github.com/sheepdog/sheepdog"&gt;Sheepdog&lt;/a&gt; était une solution de stockage de blocs distribués, qui s&amp;#8217;appuyait sur &lt;a href="https://en.wikipedia.org/wiki/Corosync_Cluster_Engine"&gt;corosync&lt;/a&gt; et Zookeeper.
La passerelle iSCSI était assurée par le même &lt;em&gt;tgt&lt;/em&gt; utilisé par&amp;nbsp;Open-iSCSI.&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://github.com/sheepdog/sheepdog/graphs/contributors"&gt;projet est &lt;span class="caps"&gt;SUPER&lt;/span&gt; &lt;span class="caps"&gt;STABLE&lt;/span&gt; depuis 7 ans&lt;/a&gt;.
Son fork, &lt;a href="https://github.com/sheepdog-ng/sheepdog-ng/graphs/contributors"&gt;sheepdog-ng, est dans le même état&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="drbd-linstor"&gt;&lt;span class="caps"&gt;DRBD&lt;/span&gt; /&amp;nbsp;Linstor&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://linbit.com/drbd-user-guide/drbd-guide-9_0-en/#p-intro"&gt;Distributed Replicated Block Device&lt;/a&gt; est un &lt;a href="https://docs.kernel.org/admin-guide/blockdev/drbd/index.html"&gt;module de bloc répliqué distant pour le noyau Linux&lt;/a&gt;.
On écrit sur un bloc local, le noyau réplique (en &lt;span class="caps"&gt;TCP&lt;/span&gt; ou &lt;span class="caps"&gt;RDMA&lt;/span&gt;) l&amp;#8217;écriture sur un bloc distant, en synchrone dans le scénario&amp;nbsp;recommandé.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DRDB&lt;/span&gt; est une des briques de bases de &lt;span class="caps"&gt;HA&lt;/span&gt; Linux, et permet d&amp;#8217;avoir un serveur de secours disponible en cas de défaillance du serveur&amp;nbsp;principal.&lt;/p&gt;
&lt;p&gt;Le répliqua n&amp;#8217;est pas accessible en lecture, ce qui empêche la répartition de charge en&amp;nbsp;lecture.&lt;/p&gt;
&lt;p&gt;Les gens audacieux peuvent utiliser &lt;span class="caps"&gt;DRBD&lt;/span&gt; en multiprimaire, pour peu qu&amp;#8217;ils utilisent un système de fichier adapté (comme les exotiques &lt;span class="caps"&gt;GFS&lt;/span&gt; et &lt;span class="caps"&gt;OCFS2&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Linstor expose un volume &lt;span class="caps"&gt;DRBD&lt;/span&gt; dans différents protocoles (iSCSI via &lt;em&gt;tgt&lt;/em&gt; et NVMeOF via &lt;em&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt;&lt;/em&gt;), en assumant la haute disponibilité.
Linstor fournit les drivers de haut niveau comme Cinder pour OpenStack ou &lt;span class="caps"&gt;CIS&lt;/span&gt; pour&amp;nbsp;k8s.&lt;/p&gt;
&lt;h4 id="openebs"&gt;OpenEBS&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://openebs.io/"&gt;OpenEBS&lt;/a&gt; a un logo moche, mais il a plus d&amp;#8217;étoiles Github que&amp;nbsp;Longhorn.&lt;/p&gt;
&lt;p&gt;OpenEBS est issu de la libération de&amp;nbsp;Mayastor.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/openebs/mayastor"&gt;Mayastor&lt;/a&gt; est maintenant le service utilisé par un agent &lt;span class="caps"&gt;CIS&lt;/span&gt; pour que k8s puisse utiliser des volumes persistants.
Mayastor est stateless et cause à différentes sources de blocs, dont l&amp;#8217;agent &lt;span class="caps"&gt;SPDK&lt;/span&gt; ou &lt;span class="caps"&gt;LVM&lt;/span&gt;.
Mayastor est utilisable par des microconteneurs (Firecracker, Kata), via &lt;em&gt;vhost-user&lt;/em&gt; (et donc virtio), et même hors de l&amp;#8217;écosystème k8s (pour faciliter le débogage et les&amp;nbsp;migrations).&lt;/p&gt;
&lt;h4 id="longhorn"&gt;Longhorn&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://longhorn.io/"&gt;Longhorn&lt;/a&gt; est le service &lt;span class="caps"&gt;CIS&lt;/span&gt; de &lt;a href="https://www.rancher.com/"&gt;Rancher&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rancher se positionne comme le vulgarisateur de Kubernetes et fournit des projets réputés, comme &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt; la plus petite des distributions k8s, ou &lt;a href="https://rancher.github.io/elemental-toolkit/docs/"&gt;Elemental&lt;/a&gt; qui crée des images Linux immutables à partir de fichier &lt;em&gt;Dockerfile&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Rancher est maintenant racheté par&amp;nbsp;Suse.&lt;/p&gt;
&lt;h5 id="dissection-de-longues-cornes"&gt;Dissection de longues&amp;nbsp;cornes&lt;/h5&gt;
&lt;p&gt;Longhorn, dans sa version actuelle, expose les blocs en iSCSI, via un fork de &lt;em&gt;tgt&lt;/em&gt; qui ajoute son propre backstore &lt;a href="https://github.com/fujita/tgt/compare/master...rancher:tgt:longhorn_oct2019"&gt;bs_longhorn&lt;/a&gt;, qui se contente de faire le passe-plat, en confiant toutes les commandes lire/écrire/effacer à un service en go, qui lui détient tout le code métier : la réplication, la création d&amp;#8217;instantanées, la détection d&amp;#8217;incidents et les réparations, comme le détail la &lt;a href="https://github.com/longhorn/longhorn/wiki/Architecture-Overview-For-Developers"&gt;documentation secrète&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le hack de &lt;em&gt;tgtd&lt;/em&gt; (repris de Sheepdog), est audacieux, tout comme utiliser un service en golang (avec son ramasse-miette) pour gérer de faibles latences.
Autre temps, autres mœurs, en 2016, il n&amp;#8217;y avait pas &lt;em&gt;ublk&lt;/em&gt; ou même &lt;span class="caps"&gt;SPDK&lt;/span&gt;.
Il y a eu une version utilisant &lt;span class="caps"&gt;TCMU&lt;/span&gt;, l&amp;#8217;ancêtre de &lt;em&gt;ublk&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Les images disques sont de simples fichiers troués (sparse file), et Longhorn utilise ses &lt;a href="https://github.com/longhorn/sparse-tools"&gt;sparse-tools&lt;/a&gt;, pour ne pas s&amp;#8217;embêter avec les trous lors de la création d&amp;#8217;instantané, recopie et consolidation.
Pour ça, ils s&amp;#8217;appuient sur les &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/fiemap.html"&gt;file extent mapping&lt;/a&gt; du noyau Linux (&lt;em&gt;fiemap&lt;/em&gt; étant le successeur de &lt;em&gt;fbmap&lt;/em&gt; qui lui ne voir que des blocs).
Pour bénéficier de &lt;em&gt;fiemap&lt;/em&gt;, il faut utiliser un système de fichiers gérant les extents, et pour créer des instantanés, &lt;a href="https://man7.org/linux/man-pages/man8/fsfreeze.8.html"&gt;fsfreeze&lt;/a&gt; sera utilisé, ce qui ne laisse que &lt;span class="caps"&gt;XFS&lt;/span&gt; et ext4 comme système de fichier&amp;nbsp;utilisable.&lt;/p&gt;
&lt;p&gt;La version 2 du moteur de données de Longhorn s&amp;#8217;appuie sur &lt;span class="caps"&gt;SPDK&lt;/span&gt; et NVMeOF.
Une grosse partie des fonctionnalités sont maintenant assumées par &lt;span class="caps"&gt;SPDK&lt;/span&gt; (accès distant, réplication,&amp;nbsp;instantanés…).&lt;/p&gt;
&lt;p&gt;Les gains en performance sont clairs, &lt;a href="https://longhorn.io/docs/1.6.1/v2-data-engine/performance/"&gt;pas énormément sur la bande passante, mais clairement sur les &lt;span class="caps"&gt;IOPS&lt;/span&gt; et les latences&lt;/a&gt;, et la différence est plus flagrante avec un meilleur débit&amp;nbsp;réseau.&lt;/p&gt;
&lt;p&gt;La documentation insiste sur ce point : il faut dédier un cœur&amp;nbsp;à &lt;code&gt;spdk_tgt&lt;/code&gt; pour ne pas avoir de fluctuations de performance&amp;nbsp;(jitter).&lt;/p&gt;
&lt;h5 id="au-dela-de-longhorn-harvester"&gt;Au-delà de Longhorn :&amp;nbsp;Harvester&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/harvester/harvester"&gt;Harverster&lt;/a&gt; s&amp;#8217;appuie sur LongHorn (et Rancher) pour proposer un service d&amp;#8217;infrastructure hyperconvergente (un Proxmox like,&amp;nbsp;quoi).&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est intéressant de voir une architecture du nouveau monde Kubernetes proposer de la gestion de machines virtuelles (l&amp;#8217;ancien monde, donc), auquel on pourra ajouter des&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Disque dur de 250Mo" class="image-process-article-image" src="images/huge_hard_drive.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="au-dela-des-blocs-reseau"&gt;Au-delà des blocs&amp;nbsp;réseau&lt;/h2&gt;
&lt;p&gt;Un volume réseau est une réponse universelle qui permettra de faire comme avant, sans rien changer, et vous proposera des choses bien pratiques, comme les instantanés, l&amp;#8217;agrandissement déraisonnable, ou la déconnexion depuis une machine virtuelle pour se reconnecter vers une autre&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;Un volume bloc ne sera accessible que depuis un seul client, ou alors il faut mettre les doigts dans &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/gfs2.html"&gt;gfs2&lt;/a&gt; ou &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/ocfs2.html"&gt;ocfs2&lt;/a&gt;. Globalement, un système de fichier distribué aura un comportement différent (et décevant), que ce soit en mode bloc ou&amp;nbsp;fichier.&lt;/p&gt;
&lt;h3 id="reseau"&gt;Réseau&lt;/h3&gt;
&lt;h4 id="tcp"&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;Le drame de  &lt;a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol"&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt;&lt;/a&gt; est son ossification, son incapacité à&amp;nbsp;évoluer.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt; doit faire un compromis sur la taille des paquets, soit des gros pour le débit, soit des petits pour la latence, et il y a encore des drames avec&amp;nbsp;le &lt;code&gt;TCP_NODELAY&lt;/code&gt; pour éviter d&amp;#8217;attendre 200ms pour tenter de remplir une trame et limiter le surcout des&amp;nbsp;entêtes.&lt;/p&gt;
&lt;p&gt;le reproche principal de &lt;span class="caps"&gt;TCP&lt;/span&gt; est son contrôle de congestion qui attend la perte de paquets pour prévenir la source que le tuyau est&amp;nbsp;saturé.&lt;/p&gt;
&lt;h5 id="ecn"&gt;&lt;span class="caps"&gt;ECN&lt;/span&gt;&lt;/h5&gt;
&lt;p&gt;La &lt;a href="https://tools.ietf.org/html/rfc3168"&gt;&lt;span class="caps"&gt;RFC3168&lt;/span&gt;&lt;/a&gt; propose d&amp;#8217;anticiper l&amp;#8217;arrivée d&amp;#8217;une congestion, et de ne pas arriver à l&amp;#8217;étape fatidique de la destruction de paquet.
Pour ça, un champ de l&amp;#8217;entête &lt;span class="caps"&gt;IP&lt;/span&gt;, &lt;span class="caps"&gt;ECN&lt;/span&gt; (2 bits), pour &lt;a href="https://en.wikipedia.org/wiki/Explicit_Congestion_Notification"&gt;Explicit Congestion Notification&lt;/a&gt;, est utilisé pour indiquer que la file d&amp;#8217;attente a atteint un seuil, et que si ça continue comme ça, il va&amp;nbsp;couper.&lt;/p&gt;
&lt;p&gt;Les deux bits &lt;span class="caps"&gt;ECN&lt;/span&gt; sont dans l&amp;#8217;octet &lt;span class="caps"&gt;TOS&lt;/span&gt;, qui a eu d&amp;#8217;autres usages, et quand on envoie une trame &lt;span class="caps"&gt;TCP&lt;/span&gt;, on ne sait pas si le switch gère l&amp;#8217;&lt;span class="caps"&gt;ECN&lt;/span&gt;.
S’il le gère, le switch va flipper un des bits, l&amp;#8217;autre servant d&amp;#8217;annonce de&amp;nbsp;congestion.&lt;/p&gt;
&lt;p&gt;Donc, pour bénéficier d&amp;#8217;&lt;span class="caps"&gt;ECN&lt;/span&gt;, il faut que les switchs sur le trajet le gèrent, et par exemple, chez &lt;span class="caps"&gt;AWS&lt;/span&gt;, c&amp;#8217;est&amp;nbsp;niet.&lt;/p&gt;
&lt;h5 id="dctcp"&gt;&lt;span class="caps"&gt;DCTCP&lt;/span&gt;&lt;/h5&gt;
&lt;p&gt;Le noyau Linux utilise des modules pour le contrôle de congestion, et &lt;a href="https://docs.kernel.org/networking/dctcp.html"&gt;dctcp&lt;/a&gt; (comme DataCenter &lt;span class="caps"&gt;TCP&lt;/span&gt;) utilisé &lt;span class="caps"&gt;ECN&lt;/span&gt; pour anticiper les&amp;nbsp;congestions.&lt;/p&gt;
&lt;p&gt;Il y a une &lt;span class="caps"&gt;RFC&lt;/span&gt;, donc Bortzmeyer en parle : &lt;a href="https://www.bortzmeyer.org/8257.html"&gt;8257&lt;/a&gt;&lt;/p&gt;
&lt;h5 id="mptcp"&gt;&lt;span class="caps"&gt;MPTCP&lt;/span&gt;&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Multipath_TCP"&gt;&lt;span class="caps"&gt;MPTCP&lt;/span&gt;&lt;/a&gt; propose d&amp;#8217;ajouter le multi path à &lt;span class="caps"&gt;TCP&lt;/span&gt;, sans casser Internet, sans modifier les&amp;nbsp;applications.&lt;/p&gt;
&lt;p&gt;Même &lt;a href="https://www.bortzmeyer.org/6182.html"&gt;Bortzmeyer dit du bien de la &lt;span class="caps"&gt;RFC&lt;/span&gt; 6182&lt;/a&gt;, et explique divers&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;Fort pratique pour les connexions mobiles (4G + Wifi), il peut avoir sa place dans un datacenter, pour profiter de la redondance du réseau.
Déjà déployé dans les téléphones, Linux commence à avoir l&amp;#8217;outillage nécessaire pour faire correctement du &lt;a href="https://www.mptcp.dev/"&gt;mptcp&lt;/a&gt; avec un démon en userspace, &lt;a href="https://mptcpd.mptcp.dev/README.html"&gt;mptcpd&lt;/a&gt; pour configurer finement le comportement.
Les outils se concentrent sur les usages mobiles, ciblant &lt;span class="caps"&gt;HTTP&lt;/span&gt; et &lt;span class="caps"&gt;SSH&lt;/span&gt;, et je n&amp;#8217;ai pas vu grand-chose ressemblant aux stratégies &lt;span class="caps"&gt;ECMP&lt;/span&gt; de &lt;span class="caps"&gt;SRD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est sage de partir sur une technologie compatible avec l&amp;#8217;existant, mais je suis curieux de voir comment ça va évoluer dans des réseaux policés comme ceux des&amp;nbsp;datacenters.&lt;/p&gt;
&lt;p&gt;Je miserai sur les proxys &lt;span class="caps"&gt;HTTP&lt;/span&gt;, comme premiers déploiements en prod, si votre HAproxy gère mptcp, c&amp;#8217;est gagné (il y a d&amp;#8217;ailleurs une &lt;a href="https://github.com/haproxy/haproxy/pull/2534"&gt;&lt;span class="caps"&gt;PR&lt;/span&gt; pour mptcp&lt;/a&gt;).
&lt;a href="https://www.sobyte.net/post/2023-07/mptcp-go/"&gt;Golang le gère depuis la 1.21&lt;/a&gt; ça devrait débarquer dans Traefik rapidement (aucune occurrence dans le code, pour&amp;nbsp;l&amp;#8217;instant).&lt;/p&gt;
&lt;p&gt;En interne, en garantissant l&amp;#8217;ordre des paquets, on va subir la latence du pire chemin, même si on peut profiter d&amp;#8217;un meilleur&amp;nbsp;débit.&lt;/p&gt;
&lt;p&gt;J&amp;#8217;ai du mal à voir le bénéfice par rapport à un client qui se connecte directement à une des instances d&amp;#8217;une application distribuée, sans passer par un proxy.
Pour de l&amp;#8217;&lt;span class="caps"&gt;HTTP&lt;/span&gt;, c&amp;#8217;est rigolo à coder, je l&amp;#8217;ai fait avec le projet &lt;a href="https://github.com/athoune/medusa"&gt;Medusa&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id="bbr-v3"&gt;&lt;span class="caps"&gt;BBR&lt;/span&gt;&amp;nbsp;v3&lt;/h5&gt;
&lt;p&gt;Google dit que pendre sur &lt;span class="caps"&gt;TCP&lt;/span&gt;, qui est &amp;#8220;ossifié&amp;#8221;, et si son &lt;a href="https://en.wikipedia.org/wiki/QUIC"&gt;&lt;span class="caps"&gt;QUIC&lt;/span&gt;&lt;/a&gt; est une alternative efficace, il n&amp;#8217;est pertinent que pour l&amp;#8217;Internet public, plein de bruit et de&amp;nbsp;fureur.&lt;/p&gt;
&lt;p&gt;Plus universel, Google revient à la charge et propose sa &lt;a href="https://github.com/google/bbr/blob/v3/README.md"&gt;troisième itération de &lt;span class="caps"&gt;BBR&lt;/span&gt;&lt;/a&gt;, son algorithme de contrôle de congestion. Comme tout le monde, il méprise la perte de paquet comme indicateur de congestion et se concentre sur le classique algorithme &lt;a href="https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease"&gt;&lt;span class="caps"&gt;AIMD&lt;/span&gt;&lt;/a&gt; et les informations de délais et &lt;span class="caps"&gt;ECN&lt;/span&gt;. Comme &lt;span class="caps"&gt;DCTCP&lt;/span&gt;, quoi.
Google bosse sur le sujet depuis des années, et l&amp;#8217;a utilisé chez Youtube, mais surtout pour ses connexions entre&amp;nbsp;datacenter.&lt;/p&gt;
&lt;p&gt;Il fournit un module pour le noyau, mais aussi un patch&amp;nbsp;pour &lt;code&gt;ip&lt;/code&gt; et &lt;code&gt;ss&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le statut est &amp;#8220;test élargi&amp;#8221;, du bêta,&amp;nbsp;quoi.&lt;/p&gt;
&lt;h5 id="hystart"&gt;Hystart++&lt;/h5&gt;
&lt;p&gt;Microsoft bosse aussi sur le contrôle de congestion, et il a spécifié son
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9406"&gt;Hystart++&lt;/a&gt;.
Cloudflare a fait une &lt;a href="https://blog.cloudflare.com/cubic-and-hystart-support-in-quiche/"&gt;implémentation de Hystart++ pour &lt;span class="caps"&gt;QUIC&lt;/span&gt; dans Quiche&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour Linux, le hystart sans ++, la version précédente, est intégré comme paramétrage pour le contrôleur &lt;span class="caps"&gt;CUBIC&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="roce"&gt;RoCE&lt;/h4&gt;
&lt;p&gt;La norme &lt;a href="https://en.wikipedia.org/wiki/RDMA_over_Converged_Ethernet"&gt;&lt;span class="caps"&gt;ROCE&lt;/span&gt;&lt;/a&gt; souhaite normaliser le &lt;span class="caps"&gt;RDMA&lt;/span&gt; en mettant de l&amp;#8217;Infiniband dans de l&amp;#8217;Ethernet, et donc se passer des couteux câbles et switch Infiniband, pour du classique Ethernet (classique, on parle quand même de carte 10G et la plupart du temps de fibre&amp;nbsp;optique).&lt;/p&gt;
&lt;p&gt;La version 1, assez naïve, ne fonctionne que dans un même sous réseau, non&amp;nbsp;routable.&lt;/p&gt;
&lt;p&gt;RoCE v2 est un véritable protocole Internet, utilisant &lt;span class="caps"&gt;UDP&lt;/span&gt;, routable, mais utilise  &lt;span class="caps"&gt;ECN&lt;/span&gt; pour notifier les congestions.
Voilà, de l&amp;#8217;&lt;span class="caps"&gt;UDP&lt;/span&gt; avec &lt;span class="caps"&gt;ECN&lt;/span&gt;,conçu pour &lt;span class="caps"&gt;TCP&lt;/span&gt;, mais utilisé dans &lt;span class="caps"&gt;IP&lt;/span&gt;, la couche en dessous, du bon gros recyclage.
Visiblement, pas tous les switchs le&amp;nbsp;gèrent.&lt;/p&gt;
&lt;p&gt;RoCE va envoyer des messages sur le réseau, dans son monde, on parle de &amp;#8220;verbe&amp;#8221;, mais surtout va envoyer de gros blocs de mémoire de &lt;span class="caps"&gt;RAM&lt;/span&gt; à &lt;span class="caps"&gt;RAM&lt;/span&gt;, sans passer par le &lt;span class="caps"&gt;CPU&lt;/span&gt;, comme le permet le PCIe, du &lt;span class="caps"&gt;RDMA&lt;/span&gt; (Remote Direct Memory Access) et pour ça, il faut une carte réseau qui sache le&amp;nbsp;faire.&lt;/p&gt;
&lt;h4 id="srd"&gt;&lt;span class="caps"&gt;SRD&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; a voulu proposer du réseau en 100G pour son offre de calcul scientifique (&lt;span class="caps"&gt;HPC&lt;/span&gt;). Le monde du &lt;span class="caps"&gt;HPC&lt;/span&gt; est habitué au matériel exotique, et utilise des abstractions aux dessus des couches réseaux, comme Open-&lt;span class="caps"&gt;MPI&lt;/span&gt; comme couche haute, et &lt;a href="https://ofiwg.github.io/libfabric/"&gt;libfabric&lt;/a&gt; comme couche&amp;nbsp;basse.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; dit qu&amp;#8217;&lt;span class="caps"&gt;ECN&lt;/span&gt; n&amp;#8217;est pas adapté à un réseau aussi étendu qu&amp;#8217;un datacenter &lt;span class="caps"&gt;AWS&lt;/span&gt;, et donc qu&amp;#8217;il est impossible d&amp;#8217;utiliser du RoCE v2.
Les menaces sont terribles : &amp;#8220;Risque de verrou de la mort, épanchement de congestion et blocage en tête de&amp;nbsp;ligne&amp;#8221;.&lt;/p&gt;
&lt;p&gt;De toute façon, &lt;span class="caps"&gt;AWS&lt;/span&gt; croit très fort au multipath, plutôt que de se contenter d&amp;#8217;une route pour se connecter à un serveur, autant utiliser toutes les routes&amp;nbsp;possibles.&lt;/p&gt;
&lt;p&gt;Donc, &lt;span class="caps"&gt;AWS&lt;/span&gt; crée &lt;a href="https://ieeexplore.ieee.org/document/9167399"&gt;&lt;span class="caps"&gt;SRD&lt;/span&gt;&lt;/a&gt;, Scalable Reliable Datagram,  proche de ce que propose Infiniband (suffisamment pour créer un module libfabric), qui en plus du multipath, ne s&amp;#8217;engage pas à livrer les paquets de manières&amp;nbsp;ordonnées.&lt;/p&gt;
&lt;p&gt;Le tri des paquets est confié à la couche applicative, qui a à sa disposition bien plus de mémoire qu&amp;#8217;une carte réseau, et qui saura prendre des décisions métiers.
Le multipath risque d&amp;#8217;utiliser des routes plus rapides que d&amp;#8217;autres, augmentant les chances de mélanger l&amp;#8217;ordre d&amp;#8217;arrivée des paquets.
Attendre l&amp;#8217;arrivée d&amp;#8217;un paquet lent va stopper le flot, et péter le&amp;nbsp;débit.&lt;/p&gt;
&lt;p&gt;Le classique &lt;a href="https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing"&gt;&lt;span class="caps"&gt;ECMP&lt;/span&gt;&lt;/a&gt; est utilisé sur les switchs, mais en lui donnant des indices, et un contrôle de congestion maison est&amp;nbsp;utilisé.&lt;/p&gt;
&lt;p&gt;Le réseau est partagé avec les autres, qui se contentent d&amp;#8217;une seule route, eux, et pour éviter les tant redoutés de congestions, &lt;span class="caps"&gt;SRD&lt;/span&gt; utilise les &lt;span class="caps"&gt;RTT&lt;/span&gt; pour estimer l&amp;#8217;encombrement des routes et s&amp;#8217;y adapte.
&lt;span class="caps"&gt;SRD&lt;/span&gt; utilise les SmartNICs maison d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;, les cartes Nitro, et profite de l&amp;#8217;abstraction du pilote &lt;span class="caps"&gt;EFA&lt;/span&gt; pour diverses optimisations comme les accès sans interruption (un ring je présume), du zero copy avec &lt;span class="caps"&gt;DMA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article est agréable à lire, et les chiffres du monde &lt;span class="caps"&gt;HPC&lt;/span&gt; réveillent en nous le plaisir des gros V8 qui ronronne alors qu&amp;#8217;on fait ses trajets en bus&amp;nbsp;diésel.&lt;/p&gt;
&lt;p&gt;Mais ça, c&amp;#8217;était avant, &lt;a href="https://aws.amazon.com/fr/blogs/aws/now-in-preview-larger-faster-io2-ebs-volumes-with-higher-throughput/"&gt;la dernière itération d&amp;#8217;&lt;span class="caps"&gt;EBS&lt;/span&gt;, utilise &lt;em&gt;io2&lt;/em&gt;&lt;/a&gt; qui utilise du &lt;span class="caps"&gt;SRD&lt;/span&gt;, et donc les blocs disques utilisent du multipath.
La carte Nitro est cité, je présume qu&amp;#8217;il y a du NVMe en &lt;span class="caps"&gt;RDMA&lt;/span&gt; (et un gros buffer pour ordonner les&amp;nbsp;paquets).&lt;/p&gt;
&lt;h4 id="snap-et-pony-express"&gt;Snap et Pony&amp;nbsp;express&lt;/h4&gt;
&lt;p&gt;Google a constaté que développer (et déployer) du code en espace noyau était 4 fois lent qu&amp;#8217;en espace utilisateur. Une interruption de 100ms plutôt que de redémarrer la machine, on comprend bien la différence de&amp;nbsp;confort.&lt;/p&gt;
&lt;p&gt;Google a choisi une approche maximaliste et utilise &lt;a href="https://research.google/pubs/snap-a-microkernel-approach-to-host-networking/"&gt;snap&lt;/a&gt;, un service en espace utilisateur, qui a l&amp;#8217;exclusivité sur la carte réseau (une smart &lt;span class="caps"&gt;NIC&lt;/span&gt; qui gère du &lt;span class="caps"&gt;DMA&lt;/span&gt;, Intel est cité), avec un utilisateur non privilégié, mono threadé.
Techniquement, c&amp;#8217;est un microkernel.
Le flot est multipléxé (comme &lt;span class="caps"&gt;HTTP&lt;/span&gt;/2 et &lt;span class="caps"&gt;QUIC&lt;/span&gt;), avec une seule connexion entre deux machines, il gère la congestion à sa sauce.
Le document précise juste que ce n&amp;#8217;est pas du &lt;span class="caps"&gt;TCP&lt;/span&gt;, ni de l&amp;#8217;&lt;span class="caps"&gt;UDP&lt;/span&gt;, on peut supposer que la couche &lt;span class="caps"&gt;IP&lt;/span&gt; est conservée pour avoir du routage.
La doc parle de négocier le protocole de transport d&amp;#8217;un service, via une connexion &lt;span class="caps"&gt;TCP&lt;/span&gt;, vu que Snap a des mises à jour hebdomadaires, tout le monde ne parle pas la même version.
Diverses stratégies sont possibles, mais il est possible de lui dédié un cœur (les processeurs modernes en ont plein), le service consommera donc 100% du &lt;span class="caps"&gt;CPU&lt;/span&gt;, et sera entièrement responsable de la latence, personne (et surtout pas le kernel) n&amp;#8217;étant là pour le&amp;nbsp;gêner.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;snap&lt;/code&gt; utilise plusieurs modules, avec différentes stratégies fournissant diverses performances (latences, débits) et cout.
Le débit par cœur est triplé par rapport à &lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt;, et les latences en dessous des&amp;nbsp;10µs.&lt;/p&gt;
&lt;p&gt;snap est utilisé pour le réseau des VMs, le peering Internet, et pour le trafic à faible latence au sein d&amp;#8217;un datacenter, il y a &lt;em&gt;pony express&lt;/em&gt;, qui a divers usages dont la recherche internet et le&amp;nbsp;stockage.&lt;/p&gt;
&lt;h3 id="colossus"&gt;Colossus&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://cloud.google.com/blog/products/storage-data-transfer/a-peek-behind-colossus-googles-file-system?hl=en"&gt;Colossus&lt;/a&gt; est la technologie de stockage interne de Google, mais aussi la couche basse utilisée par le stockage pour les machines virtuelles, et divers services de stockage (comme Bigtable ou&amp;nbsp;Spanner).&lt;/p&gt;
&lt;p&gt;Snap se vante de gérer le stockage, et donc&amp;nbsp;Colossus.&lt;/p&gt;
&lt;p&gt;Un énorme pool de stockage utilisant un mix de disque à plateaux et &lt;span class="caps"&gt;SSD&lt;/span&gt;, une gestion pointilleuse du cache (avec &lt;a href="https://www.usenix.org/conference/atc22/presentation/yang-tzu-wei"&gt;CacheSack&lt;/a&gt;) et la notion de données chaude (trop de répliqua) puis froide.
Chaque client exige des performances, de la redondance, qui peuvent être très variées. Pour maitriser les latences, il n&amp;#8217;y a pas de proxy, les échanges se font de point à point, par le chemin le plus court, même le cache est tiré depuis le stockage de référence (le dataplane indiquera alors d&amp;#8217;utiliser le cache, plutôt qu&amp;#8217;un disque&amp;nbsp;précis).&lt;/p&gt;
&lt;h3 id="bases-de-donnees-distribuees"&gt;Bases de données&amp;nbsp;distribuées&lt;/h3&gt;
&lt;p&gt;Un service complexe de stockage comme une base de données va solliciter fortement le stockage : le log binaire pour pas perdre de données, les données elle-même, avec des réécritures pour bien ranger, la mise à jour des index, ce genre de&amp;nbsp;choses.&lt;/p&gt;
&lt;p&gt;Si la réplication a lieu au niveau des blocs, l&amp;#8217;amplification d&amp;#8217;écriture va être entièrement répliquée, et de manière bloquante.
Les bases de données savent (toutes ?) maintenant gérer la réplication au niveau applicatif, en utilisant le flot d&amp;#8217;évènements (souvent un &lt;a href="https://en.wikipedia.org/wiki/Write-ahead_logging"&gt;&lt;span class="caps"&gt;WAL&lt;/span&gt;&lt;/a&gt;), et des quorums pour les réplications, ce qui permet de ne pas attendre les retardataires (outliers), la consolidation se faisant plus tard, en&amp;nbsp;asynchrone.&lt;/p&gt;
&lt;p&gt;Aurora a uniformisé ce principe avec &lt;a href="https://aws.amazon.com/fr/rds/aurora/"&gt;Aurora&lt;/a&gt;, sur lequel ils utilisent plusieurs bases de données open source (Mysql, Postgresql).
Leur article &lt;a href="https://www.amazon.science/publications/amazon-aurora-design-considerations-for-high-throughput-cloud-native-relational-databases"&gt;&amp;#8220;Conception et considérations pour une base de données relationnelle à haut débit née dans le Nuage&lt;/a&gt; et bien plus intéressante que leur infâme page produit.
Il ne faut pas oublier que la persistance n&amp;#8217;est qu&amp;#8217;un des soucis que va gérer une base de données relationnelles, la gestion des transactions en est un autre, et pour ce sujet l&amp;#8217;article dédié est &lt;a href="https://www.amazon.science/publications/amazon-aurora-on-avoiding-distributed-consensus-for-i-os-commits-and-membership-changes"&gt;&amp;#8220;Sur l&amp;#8217;évitement du consensus distribué sur les changements dans les entrées/sorties, les transactions et les adhésions&amp;#8221;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Google publie lui aussi des informations sur &lt;a href="https://dl.acm.org/doi/abs/10.14778/2536222.2536232"&gt;F1 et Spanner&lt;/a&gt; qui s&amp;#8217;appuie sur&amp;nbsp;Colossus.&lt;/p&gt;
&lt;p&gt;En open source, &lt;a href="https://github.com/neondatabase/neon"&gt;Neon&lt;/a&gt; propose un moteur de persistance pour Postgres, et donc une base de données serverless (?! du dbless&amp;nbsp;?!).&lt;/p&gt;
&lt;p&gt;Plus rigolo, Ceph propose &lt;a href="https://docs.ceph.com/en/latest/rados/api/libcephsqlite/"&gt;libcephsqlite&lt;/a&gt;, un sqlite avec uniquement le stockage distribué, pour un client&amp;nbsp;solitaire.&lt;/p&gt;
&lt;p&gt;Dans le même genre, il y a &lt;a href="https://github.com/superfly/litefs"&gt;litefs&lt;/a&gt;, et &lt;a href="https://github.com/superfly/litevfs"&gt;litevfs&lt;/a&gt;, du sqlite répliqué, avec un bon gros verrou pour arriver à y&amp;nbsp;écrire.&lt;/p&gt;
&lt;p&gt;Il existe des bases de données simplissimes qui se concentre surtout sur du clef/valeur (à la &lt;a href="https://rocksdb.org/"&gt;rocksdb&lt;/a&gt;) et se concentre surtout sur la distribution et la réplication&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tikv/tikv"&gt;tikv&lt;/a&gt;, du &lt;span class="caps"&gt;CNCF&lt;/span&gt;, propose un système de transactions et sert de socle à &lt;a href="https://github.com/pingcap/tidb"&gt;TiDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.foundationdb.org/"&gt;foundationDB&lt;/a&gt;, d&amp;#8217;Apple, avec des transactions &lt;span class="caps"&gt;ACID&lt;/span&gt;, des clés ordonnées, des notifications de modification, des opérations&amp;nbsp;atomiques.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scaleway.com/en/white-papers/"&gt;Hive&lt;/a&gt;, de Scaleway, crée pour coordonner leur stockage S3 et peut être un jour remplacer&amp;nbsp;etcd.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scylladb.com/"&gt;Scylladb&lt;/a&gt;, un clone de Cassandra, une base orientée colonnes, avec des données typées, un langage de requêtes avec des fonctions utilisateurs. C&amp;#8217;est le plus haut niveau de la&amp;nbsp;liste.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On notera que, perfidement, les offres de disques réseaux hautes performances des offres Cloud ciblent les bases de données propriétaires (&lt;span class="caps"&gt;SQL&lt;/span&gt; Server, &lt;span class="caps"&gt;SAP&lt;/span&gt;…), pour contourner les couts de licences (par &lt;span class="caps"&gt;CPU&lt;/span&gt;) ou les surcouts pour le&amp;nbsp;clustering.&lt;/p&gt;
&lt;h3 id="stockage-oriente-blob"&gt;Stockage orienté&amp;nbsp;blob&lt;/h3&gt;
&lt;p&gt;S3 a démontré qu&amp;#8217;il savait gérer brutalement une partie des besoins de&amp;nbsp;stockage.&lt;/p&gt;
&lt;p&gt;S3 est souvent utilisé en complément à du bloc, pour les sauvegardes par exemple, ou plus récemment comme stockage de flot, pour conserver les flots de&amp;nbsp;réplication.&lt;/p&gt;
&lt;p&gt;Ses débits ne sont pas forcément suffisants, on retrouve alors des solutions en pair à pair, soit du bittorrent pour le vaste monde, soit des variations avec des participants de confiance.
Déployer des applications et des images de conteneurs font partie des usages violents, mais aussi des données comme des &lt;span class="caps"&gt;LLM&lt;/span&gt; (Large Language Model) pour l&amp;#8217;&lt;span class="caps"&gt;IA&lt;/span&gt; ou des données scientifiques, comme des&amp;nbsp;génomes.&lt;/p&gt;
&lt;h3 id="daos"&gt;&lt;span class="caps"&gt;DAOS&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.daos.io/v2.4/overview/architecture/"&gt;&lt;span class="caps"&gt;DAOS&lt;/span&gt;&lt;/a&gt; reprends les travaux d&amp;#8217;&lt;a href="https://www.openfabrics.org/"&gt;openfabrics&lt;/a&gt; qui veut désagréger les machines de calcul avec du matériel de rêve (Cray, &lt;span class="caps"&gt;CXL&lt;/span&gt;, &lt;span class="caps"&gt;RDMA&lt;/span&gt;, &lt;span class="caps"&gt;SCM&lt;/span&gt;&amp;nbsp;…).&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DAOS&lt;/span&gt; propose d&amp;#8217;entasser des disques NVMe dans un cluster (on parle de centaines ou de milliers de nœuds), puis d&amp;#8217;utiliser tous les moyens possibles pour les saturer : zero copy, &lt;span class="caps"&gt;RDMA&lt;/span&gt;, pmem, accelerations&amp;nbsp;matérielles…&lt;/p&gt;
&lt;p&gt;Les modèles de données classiques du calcul scientifique sont exposés (&lt;span class="caps"&gt;HDF5&lt;/span&gt;, Hadoop, &lt;span class="caps"&gt;MPI&lt;/span&gt;-&lt;span class="caps"&gt;IO&lt;/span&gt;, fichiers &lt;span class="caps"&gt;POSIX&lt;/span&gt;…) pour une utilisation simple avec du code&amp;nbsp;existant.&lt;/p&gt;
&lt;p&gt;Des primitives sont fournies pour gérer les accès concurrents, avec différents modèles d&amp;#8217;organisation des données.
L&amp;#8217;idée est d&amp;#8217;avoir du code métier simple, avec &amp;#8220;des applications avec un bon comportement&amp;#8221; permettant d&amp;#8217;utiliser un &amp;#8220;mode relâché&amp;#8221;.
&lt;span class="caps"&gt;DAOS&lt;/span&gt; est prêt à tout, comme &lt;a href="https://docs.daos.io/v2.4/user/filesystem/#using-libioil"&gt;contourner les appels trop lents de &lt;span class="caps"&gt;FUSE&lt;/span&gt;&amp;nbsp;avec &lt;code&gt;LD_PRELOAD&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour le mode bloc, l&amp;#8217;incontournable NVMeOF (de &lt;span class="caps"&gt;SPDK&lt;/span&gt;) est utilisé, avec une procédure pour qu&amp;#8217;un nœud le monte en &lt;span class="caps"&gt;RW&lt;/span&gt;, écrit, démonte, puis le volume est monté en &lt;span class="caps"&gt;RO&lt;/span&gt; par plusieurs&amp;nbsp;nœuds.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DAOS&lt;/span&gt; est un monstre, mais c&amp;#8217;est toujours intéressant de voir quels sont les choix pris pour des volumes et des performances extrêmes, et surtout comment fournir des APIs simples et intuitives pour des utilisateurs&amp;nbsp;spécifiques.&lt;/p&gt;
&lt;h3 id="la-fin-de-moore"&gt;La fin de&amp;nbsp;Moore&lt;/h3&gt;
&lt;p&gt;La taille des disques durs, et les cartes réseau n&amp;#8217;ont pas atteint leur plafond, et continue de progresser, alors que la fréquence des &lt;span class="caps"&gt;CPU&lt;/span&gt; est&amp;nbsp;plafonnée.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;DMA&lt;/span&gt; doit permettre de décharger une partie du travail des &lt;span class="caps"&gt;CPU&lt;/span&gt;, mais peut poser des problèmes de sécurité dans des environnements multitenants (comme les environnements&amp;nbsp;virtualisés).&lt;/p&gt;
&lt;h3 id="evolution-des-abstractions"&gt;Évolution des&amp;nbsp;abstractions&lt;/h3&gt;
&lt;p&gt;Le noyau et ses interruptions sont souvent blâmés pour ajouter des latences et empêcher d&amp;#8217;utiliser à plein le&amp;nbsp;matériel.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;espace noyau n&amp;#8217;est plus une chasse gardée, le noyau a besoin de déléguer une partie de ses tâches en espace&amp;nbsp;utilisateurs.&lt;/p&gt;
&lt;p&gt;Beaucoup de travail est fait dans ce sens pour le kernel Linux, mais il faut du temps pour stabiliser et surtout avoir une sécurité décente (coucou io_uring), puis attendre que ça apparaisse upstream, dans les&amp;nbsp;distributions.&lt;/p&gt;
&lt;p&gt;La virtualisation est généralisée, et le domaine est bouillonnant : rust dépoussière kvm, les conteneurs rendent tentant les µconteneurs, et même les unikernels reviennent avec &lt;a href="https://github.com/unikraft/unikraft"&gt;Unikraft&lt;/a&gt;.
Virtio permet de concentrer les efforts pour que kvm puissent profiter au mieux, de manière la plus transparente, des nouveautés du&amp;nbsp;noyau.&lt;/p&gt;
&lt;p&gt;Le bloc de stockage est une abstraction naïve, les stockages ont leur spécificité&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les seeks des &lt;span class="caps"&gt;HDD&lt;/span&gt; (de pis en pis avec l&amp;#8217;augmentation des&amp;nbsp;tailles)&lt;/li&gt;
&lt;li&gt;l&amp;#8217;asymétrie des modifications des &lt;span class="caps"&gt;SSD&lt;/span&gt; et son &lt;a href="https://en.wikipedia.org/wiki/Trim_(computing)"&gt;trim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;le stockage zoné pour prolonger la densification des&amp;nbsp;stockages.&lt;/li&gt;
&lt;li&gt;Le &lt;span class="caps"&gt;RAID&lt;/span&gt; a un palier, bon courage pour restaurer votre &lt;span class="caps"&gt;HDD&lt;/span&gt; de&amp;nbsp;16To.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-zoned.html"&gt;dm-zoned&lt;/a&gt;, &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/zonefs.html"&gt;ZoneFS&lt;/a&gt;
&lt;a href="https://en.wikipedia.org/wiki/F2FS"&gt;&lt;span class="caps"&gt;F2FS&lt;/span&gt;&lt;/a&gt; propose des réponses pour le zonage et le trim.
Pour le seek, la réponse sera applicative (on écrit pour avoir des réponses séquentielles), et les caches &lt;span class="caps"&gt;SSD&lt;/span&gt; devant un stockage plus lent, &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/cache.html"&gt;dm-cache&lt;/a&gt;, &lt;a href="https://bcache.evilpiepirate.org/"&gt;bcache&lt;/a&gt;, sont imparfaits, le &lt;span class="caps"&gt;LRU&lt;/span&gt; n&amp;#8217;aime pas les &lt;span class="caps"&gt;SSD&lt;/span&gt;, et sans indices il est possible de remplacer l&amp;#8217;intégralité  d&amp;#8217;un cache pour des données qui ne seront finalement pas&amp;nbsp;utilisées.&lt;/p&gt;
&lt;h3 id="stateless-is-the-new-diskless"&gt;Stateless is the new&amp;nbsp;diskless&lt;/h3&gt;
&lt;p&gt;Le confort de déployer des services sans états est immense.
Ils peuvent facilement être multipliés, déplacés, mis à jour sans crainte de perdre des&amp;nbsp;données.&lt;/p&gt;
&lt;p&gt;Pour profiter de cette pureté (pureté promise par le développement fonctionnel), il faut arriver à ranger proprement la gestion de la persistance, en utilisant l&amp;#8217;abstraction&amp;nbsp;adéquate.&lt;/p&gt;</content><category term="Dev, Ops"></category><category term="virtio"></category><category term="ceph"></category><category term="iscsi"></category><category term="nvmeof"></category><category term="tcp"></category></entry><entry><title>Flot d’images disque</title><link href="http://blog.garambrogne.net/stream-my-image.html" rel="alternate"></link><published>2024-04-23T09:04:00+02:00</published><updated>2024-04-23T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-04-23:/stream-my-image.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; dévoile un des secrets de Lambda pour avoir des démarrages rapides, la lecture en flot des images disques. Décorticage de l&amp;#8217;article et un &lt;span class="caps"&gt;POC&lt;/span&gt; pour comprendre cette&amp;nbsp;approche.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; aime beaucoup les armes secrètes (et son monopole) : c&amp;#8217;est bon pour son image, et ça la différencie des autres offres Cloud.
Mais de temps en temps, &lt;span class="caps"&gt;AWS&lt;/span&gt; libère du code (pour mutualiser les efforts, pas pour faire plaisir à Stallman) et explique des points précis de son&amp;nbsp;architecture.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article &lt;a href="https://arxiv.org/abs/2305.13162"&gt;On-demand Container Loading in &lt;span class="caps"&gt;AWS&lt;/span&gt; Lambda&lt;/a&gt;, détail comment ils ont optimisé le temps de téléchargement des images disques utilisées dans leur offre &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;serverless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/22140536@N06/2133547589"&gt;&lt;img alt="Le lac de Pierre-Chatêl" class="image-process-article-image" src="/images/lac-pierre-chatel.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="contexte-le-monde-du-function-as-a-service"&gt;Contexte : le monde du Function As A&amp;nbsp;Service&lt;/h1&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;FAAS&lt;/span&gt; est proche du serverless, et je suis un fan de jargon technicocommercial préfixé en &lt;em&gt;less&lt;/em&gt;, le flou et la polémique vont forcément accompagner ce nouveau&amp;nbsp;barbarisme.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;FAAS&lt;/span&gt; permet d&amp;#8217;exposer une fonction comme service dans un cloud.
Aucun détail d&amp;#8217;implémentation ou même d&amp;#8217;exécution ne doit perturber l&amp;#8217;utilisateur de &lt;span class="caps"&gt;FAAS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Le serverless est un poil au-dessous du &lt;span class="caps"&gt;FAAS&lt;/span&gt;, il abstrait totalement la partie exécution du service, en gérant le nombre d&amp;#8217;instances, de zéros à suffisamment, avec une facturation à&amp;nbsp;l&amp;#8217;usage.&lt;/p&gt;
&lt;h2 id="aws-lambda"&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt;&amp;nbsp;Lambda&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/AWS_Lambda"&gt;La page Wikipédia d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt; Lambda&lt;/a&gt; donne une explication synthétique (avec l&amp;#8217;historique des évolutions), qui évite de lire la &lt;a href="https://aws.amazon.com/lambda/"&gt;page officielle&lt;/a&gt; et sa tempête d&amp;#8217;acronymes en 3&amp;nbsp;lettres.&lt;/p&gt;
&lt;p&gt;Techniquement, une fonction serverless est un bout de code qui sera démarré, puis qui ira dépiler une file d&amp;#8217;attente en &lt;span class="caps"&gt;HTTP&lt;/span&gt; en causant &lt;span class="caps"&gt;JSON&lt;/span&gt;.
Le protocole est masqué par les bibliothèques fournies, mais pour les curieux (et les langages compilés), il y a de la documentation et &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html"&gt;un exemple en shell&lt;/a&gt;.
On est plus proche de Celery que de&amp;nbsp;Django.&lt;/p&gt;
&lt;p&gt;La plupart des utilisateurs se contenteront d&amp;#8217;écrire leur bout de code métier : une fonction qui prend un &lt;span class="caps"&gt;JSON&lt;/span&gt; et un contexte en entrée, et qui en sortie produira une réponse serialisable en &lt;span class="caps"&gt;JSON&lt;/span&gt;. On range le nécessaire dans une archive zip, et hop, on&amp;nbsp;téléverse.&lt;/p&gt;
&lt;p&gt;Les lambdas ont été imaginés comme le liant entre différents services d&amp;#8217;une offre Cloud, pour réagir à des évènements.
Permettant ainsi à l&amp;#8217;utilisateur de brancher son code &lt;span class="caps"&gt;DANS&lt;/span&gt; la machine, pas juste de consommer du service, avec le très classique protocole&amp;nbsp;client-serveur.&lt;/p&gt;
&lt;p&gt;Exposer des fonctions lambdas comme &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;HTTP&lt;/span&gt; n&amp;#8217;est qu&amp;#8217;un des différents usages des&amp;nbsp;lambdas.&lt;/p&gt;
&lt;p&gt;Les lambdas sont volatils et démarrés en quantité fluctuante, la persistance devra être assurée par un autre service, capable d&amp;#8217;encaisser les pics d&amp;#8217;utilisation (comme un pool de connexion pour une base de données relationnelles, ou des services conçus pour la violence comme S3, DynamoDB ou &lt;a href="https://www.scylladb.com/alternator/"&gt;Scylladb&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Les lambdas sont facturés à la milliseconde, alors que les machines virtuelles sont facturées à la seconde). Oui, c&amp;#8217;est un coup de comm, cette granularité est bien trop fine, mais ça insiste sur l&amp;#8217;importance de la latence pour un&amp;nbsp;lambda.&lt;/p&gt;
&lt;h3 id="de-lautre-cote-du-miroir"&gt;De l&amp;#8217;autre côté du&amp;nbsp;miroir&lt;/h3&gt;
&lt;p&gt;Vu du côté hébergement, en intégrant la notion de file d&amp;#8217;attente, &lt;span class="caps"&gt;AWS&lt;/span&gt; se laisse la possibilité de lancer plein d&amp;#8217;instances si le cluster n&amp;#8217;est pas chargé (et se vanter de la faible latence), ou laisser s&amp;#8217;allonger les files d&amp;#8217;attente en attendant que ça se calme.
Si le temps de démarrage du lambda est raisonnable, le cout devrait être&amp;nbsp;comparable.&lt;/p&gt;
&lt;p&gt;Lambda masque la notion d&amp;#8217;image conteneur (par ce qu&amp;#8217;au début, il n&amp;#8217;en utilisait pas), mais expose quand même la notion de couches des images conteneurs, en permettant de composer une fonction avec une liste ordonnée d&amp;#8217;archives&amp;nbsp;Zip.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;environnement d&amp;#8217;exécution des lambdas est massivement multitenant, l&amp;#8217;isolation des conteneurs (namespace, cgroup, seccomp, apparmor/SELinux) n&amp;#8217;est pas suffisante pour un milieu aussi agressif, il faut donc des micromachines virtuelles.
&lt;span class="caps"&gt;AWS&lt;/span&gt; s&amp;#8217;est tourné vers les travaux de ChromeOS pour utiliser &lt;em&gt;kvm&lt;/em&gt; (le module kernel), mais sans &lt;em&gt;&lt;span class="caps"&gt;QEMU&lt;/span&gt;&lt;/em&gt;.
Ils se sont mis d&amp;#8217;accord pour partager &lt;a href="https://github.com/rust-vmm"&gt;rust-vmm&lt;/a&gt;, qui sert de base pour &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt;, et pour les VMs spécifiques de&amp;nbsp;ChromeOS.&lt;/p&gt;
&lt;p&gt;Firecracker peut mettre en pause une &lt;span class="caps"&gt;VM&lt;/span&gt; (comparable au &lt;a href="https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt"&gt;cgroup freezer&lt;/a&gt;), ce qui libère le processeur, mais pas la mémoire, permettant d&amp;#8217;entasser des fonctions prédémarrées (Provisioned Concurrency en jargon &lt;span class="caps"&gt;AWS&lt;/span&gt;), fort pratique pour gérer des évènements synchrones peu fréquents, mais&amp;nbsp;réactifs.&lt;/p&gt;
&lt;p&gt;Firecracker pourra congeler (developer preview pour l&amp;#8217;instant) une &lt;span class="caps"&gt;VM&lt;/span&gt; (en pause) et &lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/snapshot-support.md"&gt;écrire son état&lt;/a&gt; (l&amp;#8217;état de la &lt;span class="caps"&gt;VM&lt;/span&gt; et le contenu de la &lt;span class="caps"&gt;RAM&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Il est possible de rebrancher le réseau et l&amp;#8217;entropie d&amp;#8217;une fonction congelés, avant de la relancer (&lt;em&gt;unpause&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;).
Il est donc possible de décongeler une fonction autre part, plusieurs fois, et ainsi obtenir des clones.
&lt;a href="https://criu.org/Main_Page"&gt;Criu&lt;/a&gt; promet la même chose depuis longtemps pour les conteneurs (ou les simples &lt;em&gt;process&lt;/em&gt;).
Le pool de fonctions &amp;#8220;pret-à-porter&amp;#8221; ne consommera pas de &lt;span class="caps"&gt;RAM&lt;/span&gt;, et pourra être centralisé.
Curieux de connaitre la différence entre le temps de démarrage à froid et un téléchargement de l&amp;#8217;état suivi d&amp;#8217;une décongélation, j&amp;#8217;imagine qu&amp;#8217;un python qui tire de grosses bibliothèques sera plus apte à la décongélation qu&amp;#8217;un langage&amp;nbsp;compilé.&lt;/p&gt;
&lt;h2 id="serverless-libre"&gt;Serverless&amp;nbsp;libre&lt;/h2&gt;
&lt;p&gt;Il n&amp;#8217;y a pas encore de réponse évidente, mais tout tourne autour de Kurbernetes avec des évènements spécifiés par le &lt;span class="caps"&gt;CNCF&lt;/span&gt;, avec &lt;a href="https://cloudevents.io/"&gt;cloudevents&lt;/a&gt;.
Tous considèrent que les fonctions sont exposées en &lt;span class="caps"&gt;HTTP&lt;/span&gt;, et qu&amp;#8217;une passerelle gèrera les évènements issus d&amp;#8217;une file d&amp;#8217;attente, le contraire de&amp;nbsp;Lambda.&lt;/p&gt;
&lt;p&gt;Tous ces &lt;span class="caps"&gt;FAAS&lt;/span&gt; sont déployables sur un K8s maison ou infogéré, garantissant ainsi une portabilité (&lt;em&gt;multi-cloud&lt;/em&gt; en&amp;nbsp;jargon).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://knative.dev/"&gt;Knative&lt;/a&gt; se positionne comme standard de fait, poussé par Google, &lt;span class="caps"&gt;IBM&lt;/span&gt; et&amp;nbsp;Redhat.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://openfunction.dev/"&gt;OpenFunction&lt;/a&gt; propose une approche de plus haut niveau, proposant d&amp;#8217;utiliser Knative ou des alternatives (toujours basé sur k8s) comme &lt;a href="https://keda.sh/"&gt;Keda&lt;/a&gt; (l&amp;#8217;autoscaler poussé par Azure), &lt;a href="https://dapr.io/"&gt;Dapr&lt;/a&gt; (comme Distributed Application Runtime, un sidecar comparable à &lt;a href="https://istio.io/"&gt;Istio&lt;/a&gt; en plus&amp;nbsp;ambitieux).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fission.io/"&gt;Fission&lt;/a&gt; propose une approche simple et de bon gout, avec juste &lt;em&gt;Istio&lt;/em&gt; comme fantaisie.
Leurs efforts se portent sur le démarrage à froid, avec un pool de conteneurs déjà prêt.
À vérifier si le &lt;em&gt;pod&lt;/em&gt; est en pause pour libérer du &lt;span class="caps"&gt;CPU&lt;/span&gt;.
Le principal contributeur de Fission est &lt;a href="https://www.infracloud.io"&gt;InfraCloud&lt;/a&gt;, une grosse boite indienne, avec des bureaux en Allemagne, Pays-Bas et États-Unis, et un gros fan du &lt;span class="caps"&gt;CNCF&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/openfaas"&gt;OpenFAAS&lt;/a&gt; a exploré les alternatives à K8s, pour finalement ne faire plus que du k8s, le tout sous une licence bancale.
Il est compliqué pour une simple société de financer ce genre de projet libre, Knative est financé par des gens qui ont d&amp;#8217;autres (énormes) sources de revenus, et leur but est de péter (chatouiller) le monopole d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt; (et de vendre de la &lt;span class="caps"&gt;VM&lt;/span&gt; ou du&amp;nbsp;consulting).&lt;/p&gt;
&lt;p&gt;K8s va tirer les runtimes alternatifs, dont les micro VMs comme &lt;a href="https://katacontainers.io/"&gt;Kata&lt;/a&gt; qui lui va pouvoir tirer les fils de &lt;span class="caps"&gt;KVM&lt;/span&gt; : &lt;span class="caps"&gt;QEMU&lt;/span&gt;, &lt;a href="https://www.cloudhypervisor.org/"&gt;Cloud-Hypervisor&lt;/a&gt; et &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt; (qui lui sait directement causer le&amp;nbsp;containerd).&lt;/p&gt;
&lt;h3 id="de-lautre-cote-du-miroir_1"&gt;De l&amp;#8217;autre côté du&amp;nbsp;miroir&lt;/h3&gt;
&lt;p&gt;Les alternatives libres n&amp;#8217;ont pas la volonté (ou même la possibilité) d&amp;#8217;imposer un framework qui va emballer la définition de la fonction, d&amp;#8217;où le choix standard de n&amp;#8217;exposer que des services &lt;span class="caps"&gt;HTTP&lt;/span&gt;.
Depuis l&amp;#8217;époque de la création de Lambda, l&amp;#8217;utilisation des &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; s&amp;#8217;est généralisé, le build des images est moins bourrins, &lt;a href="https://github.com/docker/buildx"&gt;BuildX&lt;/a&gt; et ses amis dispensent le développeur d&amp;#8217;une partie du tuning de build.
Le distroless et les builds en composant les couches des images reprennent les empilements de Zip de Lambda.
Une partie de la simplicité de déploiement de Lambda fait maintenant partie des workflows standards de&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;approche tout &lt;span class="caps"&gt;HTTP&lt;/span&gt; permet d&amp;#8217;exposer une &lt;span class="caps"&gt;API&lt;/span&gt; simple, qui sera utilisée de manière synchrone (directement appelé par l&amp;#8217;utilisateur en &lt;span class="caps"&gt;HTTP&lt;/span&gt;) ou asynchrone (toujours en &lt;span class="caps"&gt;HTTP&lt;/span&gt;, mais via un dépileur interne connecté à un fil&amp;nbsp;d&amp;#8217;attente).&lt;/p&gt;
&lt;p&gt;En asynchrone, le &lt;em&gt;scaler&lt;/em&gt; (contrôle de débit?) se contente d&amp;#8217;effectuer des requêtes &lt;span class="caps"&gt;HTTP&lt;/span&gt; et de regarder ce que raconte Prometheus ou le cgroup du conteneur (la consommation de la fonction) pour estimer le bon débit d&amp;#8217;évènement pour qu&amp;#8217;une instance soit utilisée entièrement, avant de créer de nouvelles&amp;nbsp;instances.&lt;/p&gt;
&lt;p&gt;Les offres libres évoquent différentes files d&amp;#8217;attente, comme Redis &lt;span class="caps"&gt;STREAM&lt;/span&gt;, Kafa ou &lt;a href="https://nats.io/"&gt;&lt;span class="caps"&gt;NATS&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="decorticage-de-larticle-sur-le-demarrage-au-fil-de-leau"&gt;Décorticage de l&amp;#8217;article sur le démarrage au fil de&amp;nbsp;l&amp;#8217;eau&lt;/h1&gt;
&lt;p&gt;L&amp;#8217;article est écrit par des gens de chez &lt;span class="caps"&gt;AWS&lt;/span&gt;, lié au projet Lambda.
Il faut donc s&amp;#8217;attendre à de l&amp;#8217;enthousiasme, et à une validation par leur équipe communication/secret industriel.
Venant d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;, il faut aussi s&amp;#8217;attendre à des échelles titanesques (démarrage en pic de 15k conteneurs par seconde, par exemple), que peu d&amp;#8217;entreprises&amp;nbsp;approcheront.&lt;/p&gt;
&lt;p&gt;Je suis bien content pour eux de leurs chouettes gains en performance, mais c&amp;#8217;est plutôt leur choix technique (et les choix écartés), ainsi que leurs erreurs qui&amp;nbsp;m&amp;#8217;intéressent.&lt;/p&gt;
&lt;h2 id="images"&gt;Images&lt;/h2&gt;
&lt;p&gt;L&amp;#8217;&lt;span class="caps"&gt;OCI&lt;/span&gt; a normalisé le format des images de conteneur : une collection d&amp;#8217;archives &lt;em&gt;tar&lt;/em&gt; que l&amp;#8217;on désarchive les unes sur les autres, avec une convention de nom pour effacer (et non pas écraser) un&amp;nbsp;fichier.&lt;/p&gt;
&lt;p&gt;Même si les images &lt;span class="caps"&gt;OCI&lt;/span&gt; ont été imaginées pour des conteneurs classiques&amp;nbsp;(&lt;code&gt;namespace&lt;/code&gt; et ses amis), elles sont utilisables par des machines virtuelles, pour peu que l&amp;#8217;on ajoute divers fichiers (comme un &lt;em&gt;kernel&lt;/em&gt; et un &lt;em&gt;init&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Le conteneur (classique ou micro &lt;span class="caps"&gt;VM&lt;/span&gt;) n&amp;#8217;a pas besoin de lire l&amp;#8217;intégralité de son disque pour démarrer le service.
D&amp;#8217;après les mesures de Lambda, en moyenne, seuls 6% sont nécessaire pour&amp;nbsp;démarrer.&lt;/p&gt;
&lt;p&gt;Les conteneurs travaillent au niveau des fichiers, avec un assemblage virtuel, via &lt;em&gt;overlayfs&lt;/em&gt;, même si la piste des blocs a été explorée (avec &lt;a href="https://docs.docker.com/storage/storagedriver/device-mapper-driver/"&gt;devicemapper&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Des essais ont été faits avec un chargement paresseux (lazy loading en &lt;span class="caps"&gt;VO&lt;/span&gt;), fichier par fichier, par les projets &lt;a href="https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter"&gt;Slacker&lt;/a&gt; et &lt;a href="https://www.usenix.org/conference/nsdi22/presentation/chen-jun-lin"&gt;Startlight&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lambda, pour des raisons de sécurité, souhaite utiliser une &lt;span class="caps"&gt;VM&lt;/span&gt; minimaliste, sans drivers autre que virtio, sans actions complexes en &lt;em&gt;kernel space&lt;/em&gt;, ou même en &lt;em&gt;root&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Le tricotage du disque doit donc se faire coté hôte, qui sera exposé à l&amp;#8217;invité (la machine virtuelle) via&amp;nbsp;un &lt;code&gt;virtio&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le débit de création de fonctions est raisonnable, même poussé, testé systématiquement depuis une &lt;span class="caps"&gt;CI&lt;/span&gt; (à&amp;nbsp;chaque &lt;code&gt;git push&lt;/code&gt;, donc), l&amp;#8217;utilisateur s&amp;#8217;attend à un temps raisonnable de cuisson&amp;nbsp;(&lt;code&gt;baking&lt;/code&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Il est donc pertinent de préparer les fonctions (et leur image) lors de leur création, pour optimiser au maximum leur temps de démarrage à&amp;nbsp;froid.&lt;/p&gt;
&lt;h2 id="exposer-une-image-dans-un-conteneur-en-vm"&gt;Exposer une image dans un conteneur en&amp;nbsp;µVM&lt;/h2&gt;
&lt;p&gt;Dans l&amp;#8217;article, les indices sont tenus : une image en ext4 découpé en tranche, du &lt;span class="caps"&gt;FUSE&lt;/span&gt;, et enfin virtio-blk pour communiquer avec la machine&amp;nbsp;virtuelle.&lt;/p&gt;
&lt;p&gt;À la fin de l&amp;#8217;article, les auteurs s&amp;#8217;excusent du bricolage qui fait deux sauts périlleux dans le kernel, pour finalement (après l&amp;#8217;article) se passer de &lt;span class="caps"&gt;FUSE&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Pour leur version 1, vraisemblablement, leur agent expose avec &lt;span class="caps"&gt;FUSE&lt;/span&gt; un volume avec un seul fichier &lt;span class="caps"&gt;RAW&lt;/span&gt;, qui est confié à la &lt;span class="caps"&gt;VM&lt;/span&gt; via virtio-blk, et depuis la &lt;span class="caps"&gt;VM&lt;/span&gt;, on voit un bloc, qui est montée en &lt;em&gt;ext4&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://libguestfs.org/nbdfuse.1.html"&gt;nbdfuse&lt;/a&gt; fait ce genre de chose, en utilisant &lt;span class="caps"&gt;FUSE&lt;/span&gt; pour exposer une image disque dynamique (connecté à un serveur &lt;span class="caps"&gt;NBD&lt;/span&gt; dans ce cas), tout en disant que c&amp;#8217;est une mauvaise idée de monter cette&amp;nbsp;image.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is tempting (and possible) to loop mount the file. However this will be very slow and may sometimes&amp;nbsp;deadlock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nbdfuse fait partie de &lt;a href="https://libguestfs.org"&gt;libguestfs&lt;/a&gt;, et dit qu&amp;#8217;il est possible de lancer un &lt;span class="caps"&gt;QEMU&lt;/span&gt; avec l&amp;#8217;image dans &lt;span class="caps"&gt;FUSE&lt;/span&gt;, même si c&amp;#8217;est mieux d&amp;#8217;utiliser &lt;a href="https://www.qemu.org/docs/master/system/qemu-manpage.html"&gt;&lt;span class="caps"&gt;QEMU&lt;/span&gt; avec nbd:// &lt;/a&gt;.
Bref, &lt;em&gt;nbdfuse&lt;/em&gt; a un petit souci de confiance en&amp;nbsp;lui.&lt;/p&gt;
&lt;p&gt;Lambda est plus enthousiaste sur les performances d&amp;#8217;un passage par &lt;span class="caps"&gt;FUSE&lt;/span&gt;, même si ils ont abandonné cette&amp;nbsp;approche.&lt;/p&gt;
&lt;p&gt;Les buzzwords utilisés dans l&amp;#8217;article indiquent que la version 2 utilise &lt;a href="https://qemu-project.gitlab.io/qemu/interop/vhost-user.html"&gt;vhost-user&lt;/a&gt;, la nouvelle approche de virtio pour communiquer, sans interruption systèmes, sans copie (et si tout se passe bien, sans passer par le processeur si le matériel le&amp;nbsp;permet).&lt;/p&gt;
&lt;p&gt;La communication est établie avec&amp;nbsp;une &lt;code&gt;Vsock&lt;/code&gt;, ensuite les messages sont partagés dans&amp;nbsp;un &lt;code&gt;mmap&lt;/code&gt; synchronisé par&amp;nbsp;un &lt;code&gt;ring&lt;/code&gt;.
Attention de ne pas confondre&amp;nbsp;avec &lt;code&gt;io_uring&lt;/code&gt; qui fait ce genre de chose entre &lt;em&gt;user-space&lt;/em&gt; et &lt;em&gt;kernel-space&lt;/em&gt;, avec de bonnes performances, &lt;a href="https://github.com/moby/moby/commit/891241e7e74d4aae6de5f6125574eb994f25e169"&gt;mais pour l&amp;#8217;instant des soucis de sécurité&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La communication &lt;em&gt;zero copy&lt;/em&gt; sera utilisée dans plusieurs virtio, et il y a du code rust bas niveau pour &lt;a href="https://github.com/rust-vmm/vhost/tree/main/vhost-user-backend"&gt;vhost user backend&lt;/a&gt; (le frontend étant dans l&amp;#8217;invité) et une partie des &lt;a href="https://github.com/rust-vmm/vhost-device"&gt;virtio à la sauce vhost-user&lt;/a&gt; est&amp;nbsp;disponible.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;vhost-user&lt;/em&gt; est conçu pour repousser très loin les performances des &lt;span class="caps"&gt;IO&lt;/span&gt;, en se branchant sur les ambitieux projets &lt;a href="https://spdk.io/doc/vhost_processing.html"&gt;&lt;span class="caps"&gt;SPDK&lt;/span&gt;&lt;/a&gt; (Storage Performance Development Kit) et &lt;a href="https://www.dpdk.org/"&gt;&lt;span class="caps"&gt;DPDK&lt;/span&gt;&lt;/a&gt;) (Data Plane Development Kit), conçues pour profiter des accélérations matérielles et l&amp;#8217;univers &lt;a href="https://en.wikipedia.org/wiki/Remote_direct_memory_access"&gt;&lt;span class="caps"&gt;RDMA&lt;/span&gt;&lt;/a&gt; : SmartNIC,&amp;nbsp;NVMeOF…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;vhost-user&lt;/em&gt; permet d&amp;#8217;avoir une &lt;span class="caps"&gt;API&lt;/span&gt; universelle, rappelons que le but de virtio est de limiter drastiquement le nombre de drivers dans l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; invité, même si la quête de performances, avec des blocs rangés dans un S3, sera bien loin des latences d&amp;#8217;un disque&amp;nbsp;NVMe.&lt;/p&gt;
&lt;h2 id="poc"&gt;&lt;span class="caps"&gt;POC&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Pour mettre les mains dans le moteur, et comprendre l&amp;#8217;article, j&amp;#8217;ai décidé d&amp;#8217;écrire un &lt;span class="caps"&gt;POC&lt;/span&gt; en golang, oui, l&amp;#8217;article vante Rust, et tous les outils modernes liés à &lt;em&gt;kvm&lt;/em&gt; et &lt;em&gt;virtio&lt;/em&gt; ne jurent que par Rust et le &lt;em&gt;zero-copy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Mais c&amp;#8217;est un &lt;span class="caps"&gt;POC&lt;/span&gt;, réalisé par une équipe de 1, pour comprendre comment s&amp;#8217;architecture cette abstraction de disque avec dans la boucle des téléchargements depuis un S3 et du cache&amp;nbsp;distant.&lt;/p&gt;
&lt;p&gt;Mon interrogation porte surtout sur l&amp;#8217;utilisation d&amp;#8217;images disques dédoublonnées, en mode bloc, alors que Containerd (Docker et K8s) ne jure que par les &lt;em&gt;tar&lt;/em&gt; et &lt;em&gt;overlayfs&lt;/em&gt;.
Il est même possible d&amp;#8217;indexer des tar pour faire des seek dans l&amp;#8217;archive, pour y lire un fichier précis (comme le propose le vénérable &lt;a href="https://github.com/devsnd/tarindexer"&gt;tarindexer&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Golang permet d&amp;#8217;avoir rapidement du code lisible, des performances décentes, et de l&amp;#8217;asynchrone qui fonctionne, ce que ne permet pas (plus?) du python ou du&amp;nbsp;nodejs.&lt;/p&gt;
&lt;h3 id="nbd"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Pour pouvoir exposer ces blocs dynamiques dans un Linux, l&amp;#8217;abstraction sera donc &lt;span class="caps"&gt;NBD&lt;/span&gt;, comme le fait &lt;a href="https://www.qemu.org/docs/master/tools/qemu-nbd.html"&gt;qemu-nbd&lt;/a&gt; pour exposer une image &lt;a href="https://en.wikipedia.org/wiki/Qcow"&gt;qcow2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pojntfx/go-nbd"&gt;go-nbd&lt;/a&gt; utilise&amp;nbsp;une &lt;code&gt;interface&lt;/code&gt; simplissime : on lit et écrit des listes de bytes à une position précise,&amp;nbsp;le &lt;code&gt;ReadAt&lt;/code&gt;/&lt;code&gt;WriteAt&lt;/code&gt; de la&amp;nbsp;bibliothèque &lt;code&gt;io&lt;/code&gt; de Golang.
Le code métier va donc se contenter d&amp;#8217;implémenter&amp;nbsp;une &lt;code&gt;interface&lt;/code&gt; sans dépendre de la bibliothèque, il est ainsi théoriquement possible de brancher un autre protocole pour exposer des&amp;nbsp;blocs.&lt;/p&gt;
&lt;p&gt;Le code de la maquette est dans le projet &lt;a href="https://github.com/athoune/stream-my-root"&gt;stream-my-root&lt;/a&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/athoune/stream-my-root.git
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stream-my-root
make&lt;span class="w"&gt; &lt;/span&gt;submodule
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="blocs"&gt;Blocs&lt;/h3&gt;
&lt;p&gt;Peu convaincu par les tactiques de chargement paresseux de fichiers, Lambda décide de partir sur du&amp;nbsp;bloc.&lt;/p&gt;
&lt;p&gt;Pour chaque image &lt;span class="caps"&gt;OCI&lt;/span&gt;, on crée une image disque, en ext4, et on y désarchive les &lt;em&gt;tar&lt;/em&gt; réclamés dans le &lt;em&gt;manifest&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;image est découpée en blocs de 512Ko appelés &lt;em&gt;chunks&lt;/em&gt;.
Les &lt;em&gt;chunks&lt;/em&gt; vides ne sont pas&amp;nbsp;conservés.&lt;/p&gt;
&lt;p&gt;Chaque &lt;em&gt;chunk&lt;/em&gt; est nommé à partir de son hachage, un &lt;span class="caps"&gt;SHA256&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Un nouveau &lt;em&gt;manifest&lt;/em&gt; est créé, listant les &lt;em&gt;chunks&lt;/em&gt; et leur&amp;nbsp;position.&lt;/p&gt;
&lt;p&gt;Pour avoir de la mutualisation de &lt;em&gt;chunks&lt;/em&gt; équivalent à la mutualisation de &lt;em&gt;layers&lt;/em&gt; des images &lt;span class="caps"&gt;OCI&lt;/span&gt;, il faut que la création d&amp;#8217;images disque et les écritures soient déterministes, ce que ne font pas les outils ext4 classiques.
L&amp;#8217;écosystème ext4 n&amp;#8217;est pas énorme (et sa &lt;em&gt;homepage&lt;/em&gt; est restée dans son jus : &lt;a href="https://e2fsprogs.sourceforge.net/"&gt;e2fsprog&lt;/a&gt;), mais il existe quelques&amp;nbsp;pépites.&lt;/p&gt;
&lt;p&gt;Android, il y a longtemps, utilisait des images disques en &lt;em&gt;ext4&lt;/em&gt;, avant de passer à &lt;a href="https://www.kernel.org/doc/html/latest/filesystems/f2fs.html"&gt;&lt;span class="caps"&gt;F2FS&lt;/span&gt;&lt;/a&gt;, le code était disponible dans le &lt;span class="caps"&gt;SDK&lt;/span&gt; d&amp;#8217;Android 7, puis a disparu dans les versions suivantes.
Il y a eu un paquet debian, qui tentait de bien ranger un &lt;span class="caps"&gt;SDK&lt;/span&gt; bien&amp;nbsp;invasif.&lt;/p&gt;
&lt;p&gt;Heureusement, il y a eu des forks, dont &lt;a href="https://git.openwrt.org/?p=project/make_ext4fs.git;a=tree"&gt;le fork make_ext4fs d&amp;#8217;OpenWRT&lt;/a&gt; qui l&amp;#8217;a nettoyé de toutes dépendances exotiques, et qui fonctionne même avec autre chose que la glibc.
Du beau travail de dev&amp;nbsp;embarqué.&lt;/p&gt;
&lt;p&gt;Lambda évoque l&amp;#8217;utilisation d&amp;#8217;un code spécifique qui linéarise les écritures, sans trop préciser le logiciel en question.
&lt;a href="https://man.archlinux.org/man/core/fuse2fs/fuse2fs.1.en"&gt;fuse2fs&lt;/a&gt; (qui fait partie de &lt;em&gt;e2fsprog&lt;/em&gt;) semble faire le&amp;nbsp;job.&lt;/p&gt;
&lt;p&gt;Pour rapatrier les images &lt;span class="caps"&gt;OCI&lt;/span&gt; et ses couches, j&amp;#8217;ai déjà écrit un &lt;a href="/distroless.html"&gt;billet sur le distroless&lt;/a&gt; qui explique comment le faire&amp;nbsp;avec &lt;code&gt;curl&lt;/code&gt; et &lt;code&gt;jq&lt;/code&gt;, cette&amp;nbsp;fois-ci, &lt;code&gt;crane&lt;/code&gt; fera très bien le job :
&lt;a href="https://github.com/athoune/stream-my-root/blob/main/manifest2layers.sh"&gt;manifest2layers.sh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pour créer une image disque, empiler le contenu des archives tar dans l&amp;#8217;image, ce sera&amp;nbsp;avec &lt;code&gt;make_ext4fs&lt;/code&gt; et &lt;code&gt;fuse2fs&lt;/code&gt; : &lt;a href="https://github.com/athoune/stream-my-root/blob/main/tar2img.sh"&gt;tar2img.sh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ces deux scripts utilisent des logiciels exotiques ou dans des versions trop récentes, et sont donc emballés dans un conteneur Docker, accessibles via une commande &lt;em&gt;make&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;docker-tool
make&lt;span class="w"&gt; &lt;/span&gt;img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gcr.io/distroless/base-debian12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les outils pour créer une image ext4 et la découper ne requièrent pas de privilèges &lt;em&gt;root&lt;/em&gt;, juste un accès&amp;nbsp;à &lt;code&gt;/dev/fuse&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;image a une taille fixe, et sera pleine de vide, mais bon, les systèmes de fichiers modernes le gèrent bien (tout comme &lt;a href="https://www.gnu.org/software/tar/manual/html_section/Compression.html#sparse"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; tar avec -S&lt;/a&gt;).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-slh&lt;span class="w"&gt; &lt;/span&gt;out/*.img
42M&lt;span class="w"&gt; &lt;/span&gt;-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0G&lt;span class="w"&gt; &lt;/span&gt;Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;:31&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_base-debian12.img
55M&lt;span class="w"&gt; &lt;/span&gt;-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0G&lt;span class="w"&gt; &lt;/span&gt;Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;:50&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_java-base-debian12.img
57M&lt;span class="w"&gt; &lt;/span&gt;-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0G&lt;span class="w"&gt; &lt;/span&gt;Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:42&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_python3-debian12.img
24M&lt;span class="w"&gt; &lt;/span&gt;-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0G&lt;span class="w"&gt; &lt;/span&gt;Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;:22&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_static-debian12.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les outils suivants seront en&amp;nbsp;golang:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour trimmer les zéros, puis découper en &lt;em&gt;chunks&lt;/em&gt;, ce sera le cli en go &lt;a href="https://github.com/athoune/stream-my-root/blob/main/cmd/chunk/chunk.go"&gt;chunk&lt;/a&gt; qui utilise la bibliothèque &lt;a href="https://github.com/athoune/stream-my-root/blob/main/pkg/chunk/chunk.go"&gt;chunk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour grappiller de la place, les chunks étant de taille connue (car fixe), ils sont tronqués, et n&amp;#8217;ont pas leurs zéros en fin de&amp;nbsp;fichier.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;article dit bien que la compression avant chiffrage n&amp;#8217;est pas recommandée, mais les chunks sont pleins de trous (des longues suites de zéro), et la compression m&amp;#8217;évite de coder&amp;nbsp;un &lt;code&gt;sparse bytes&lt;/code&gt; dans un premier&amp;nbsp;temps.&lt;/p&gt;
&lt;p&gt;Une &amp;#8220;recette&amp;#8221;, préfixé&amp;nbsp;en &lt;code&gt;.recipe&lt;/code&gt; est écrite pour chaque image &lt;span class="caps"&gt;OCI&lt;/span&gt;, en format texte pour&amp;nbsp;l&amp;#8217;instant.&lt;/p&gt;
&lt;p&gt;Les chunks sont entassés dans un même dossier, une partie d&amp;#8217;entre eux seront communs entre différentes&amp;nbsp;images.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./bin/chunk&lt;span class="w"&gt; &lt;/span&gt;out/*.img
&lt;span class="c1"&gt;# les recettes sont dans le même dossier que l&amp;#39;image&lt;/span&gt;
ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;out/*.recipe
&lt;span class="c1"&gt;# les chunks sont dans le dossier smr&lt;/span&gt;
ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;smr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L&amp;#8217;outil &lt;a href="https://github.com/athoune/stream-my-root/blob/main/cmd/diff/diff.go"&gt;diff&lt;/a&gt; permet de comparer deux recettes, de compter le nombre de chunks de chacun, et surtout le nombre de chunks partagés.
Les scores sont crédibles, une image dérivant d&amp;#8217;une autre aura à peu près le bon nombre de chunks en commun.
Il y a vraisemblablement des informations uniques dans le premier chunk, je n&amp;#8217;ai pas été assez agressif sur les options&amp;nbsp;de &lt;code&gt;make_ext4fs&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./bin/diff&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_python3-debian12.img.recipe&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_base-debian12.img.recipe
A:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chunks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;121&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;unique&lt;span class="w"&gt; &lt;/span&gt;chunks
B:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;58&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chunks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;56&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;unique&lt;span class="w"&gt; &lt;/span&gt;chunks
B&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chunks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;common&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;A,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;MB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le &lt;a href="https://github.com/athoune/stream-my-root/blob/main/cmd/server/server.go"&gt;serveur&lt;/a&gt; &lt;span class="caps"&gt;NBD&lt;/span&gt; va utiliser le &lt;a href="https://github.com/athoune/stream-my-root/blob/main/pkg/backend/ro/ro.go"&gt;backend en lecture seule&lt;/a&gt;, seule partie du code lié à &lt;em&gt;go-nbd&lt;/em&gt;, qui va utiliser le module &lt;a href="https://github.com/athoune/stream-my-root/tree/main/pkg/blocks"&gt;blocks&lt;/a&gt;, la seule partie de code un peu complexe, pour gérer les zéros&amp;nbsp;implicites.&lt;/p&gt;
&lt;p&gt;Le debug de serveur &lt;span class="caps"&gt;NBD&lt;/span&gt; est ingérable, les outils Linux ne sont simplement pas faits pour ça, même s’il logue gentiment&amp;nbsp;dans &lt;code&gt;/var/log/kernel.log&lt;/code&gt;, sur une lecture incohérente il se bloquera.
Avec un Linux Alpine, ce sera encore pire, le client nbd est géré&amp;nbsp;par &lt;code&gt;busybox&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La nouvelle bibliothèque&amp;nbsp;standard &lt;code&gt;slog&lt;/code&gt; permet d&amp;#8217;avoir des informations en mode debug, pour avoir le contexte et la partie de code qui crash.
Il est étonnement simple de lancer le&amp;nbsp;debugger &lt;code&gt;delve&lt;/code&gt; depuis VSCode avec le client &lt;span class="caps"&gt;NBD&lt;/span&gt; dans un Linux&amp;nbsp;virtualisé.&lt;/p&gt;
&lt;p&gt;Mais ce n&amp;#8217;est pas la bonne approche, les tests fonctionnels ne doivent être faits qu&amp;#8217;à la toute fin, une fois que l&amp;#8217;on a un serveur dans un état&amp;nbsp;présentable.&lt;/p&gt;
&lt;p&gt;Il est étonnement facile de s&amp;#8217;autopipoter avec des tests unitaires qui affichent un excellent ratio de couverture.
Les tests unitaires doivent être complétés par du fuzzing avec des fixtures crédibles, en l&amp;#8217;occurrence de vraies images.
Une fois que le fuzzing trouve une erreur, on l&amp;#8217;ajoute aux tests unitaires (pour la lisibilité du prochain qui lira le code), on corrige, puis, &amp;#8220;rinse and repeat&amp;#8221; comme on dit en&amp;nbsp;Amérique.&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://go.dev/doc/security/fuzz/"&gt;fuzzing de golang&lt;/a&gt; est agréable à utiliser, mais il utilise beaucoup de magie, avec des arguments non nommés, de l&amp;#8217;introspection, et cette magie apparait dans les très confuses &lt;em&gt;stacks trace&lt;/em&gt; de&amp;nbsp;vautrage.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;fuzz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour compléter le fuzzing, le cli &lt;a href="https://github.com/athoune/stream-my-root/blob/main/cmd/debug/debug.go"&gt;debug&lt;/a&gt; utilise le &lt;em&gt;backend&lt;/em&gt;, la plus haute couche du code métier, juste avant go-nbd, et va faire des itérations &lt;em&gt;seek+read&lt;/em&gt; aléatoires sur l&amp;#8217;image brute, et la même chose sur le backend, pour comparer les&amp;nbsp;deux.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./bin/debug&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_python3-debian12.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le serveur utilise une&amp;nbsp;recette&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./bin/server&lt;span class="w"&gt; &lt;/span&gt;out/gcr.io_distroless_python3-debian12.img.recipe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Qemu fournit un outil de debug pour décrire un serveur&amp;nbsp;nbd&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;qemu-img&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt; &lt;/span&gt;nbd://localhost:10809/smr
image:&lt;span class="w"&gt; &lt;/span&gt;nbd://localhost:10809/smr
file&lt;span class="w"&gt; &lt;/span&gt;format:&lt;span class="w"&gt; &lt;/span&gt;raw
virtual&lt;span class="w"&gt; &lt;/span&gt;size:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1073741824&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;
disk&lt;span class="w"&gt; &lt;/span&gt;size:&lt;span class="w"&gt; &lt;/span&gt;unavailable
Child&lt;span class="w"&gt; &lt;/span&gt;node&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/file&amp;#39;&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;filename:&lt;span class="w"&gt; &lt;/span&gt;nbd://localhost:10809/smr
&lt;span class="w"&gt;    &lt;/span&gt;protocol&lt;span class="w"&gt; &lt;/span&gt;type:&lt;span class="w"&gt; &lt;/span&gt;nbd
&lt;span class="w"&gt;    &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;length:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1073741824&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;size:&lt;span class="w"&gt; &lt;/span&gt;unavailable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On peut maintenant monter l&amp;#8217;image depuis un Linux (physique ou&amp;nbsp;virtuel).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# nbd ne se plaindra que dans kernel.log, découper votre tmux avec un ctrl-%&lt;/span&gt;
tail&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/var/log/kern.log
&lt;span class="c1"&gt;# le module nbd doit être chargé&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;modprobe&lt;span class="w"&gt; &lt;/span&gt;nbd
&lt;span class="c1"&gt;# nbd-client va connecter le serveur avec un /dev/nbdx&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;nbd-client&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;smr&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10809&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/nbd1
sudo&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/mnt/smr
&lt;span class="c1"&gt;# le serveur est en lecture seule&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;ro&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ext4&lt;span class="w"&gt; &lt;/span&gt;/dev/nbd1&lt;span class="w"&gt; &lt;/span&gt;/mnt/smr
&lt;span class="c1"&gt;# l&amp;#39;image est disponible, ou peut l&amp;#39;utiliser&lt;/span&gt;
ls&lt;span class="w"&gt; &lt;/span&gt;/mnt/smr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Grâce à la persévérance d&amp;#8217;OpenWRT, il est possible de créer des images disques déterministes, avec des &lt;em&gt;chunks&lt;/em&gt; partagés entre différentes images.
&lt;span class="caps"&gt;NBD&lt;/span&gt; permet de prototyper un serveur de bloc simplement, et un Linux vanilla saura&amp;nbsp;l&amp;#8217;utiliser.&lt;/p&gt;
&lt;p&gt;Le prototype ne dépendant pas de go-nbd, il ne devrait pas être compliqué de tester avec l&amp;#8217;audacieuse stratégie de &lt;em&gt;blocks over &lt;span class="caps"&gt;FUSE&lt;/span&gt;&lt;/em&gt;, car &lt;a href="https://github.com/search?q=org%3Arust-vmm+nbd&amp;amp;type=code"&gt;rust-vmm n&amp;#8217;en a que faire de nbd&lt;/a&gt;, &lt;a href="https://github.com/search?q=org%3Afirecracker-microvm+nbd&amp;amp;type=code"&gt;pas plus que Firecracker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le cache en &lt;a href="https://dl.acm.org/doi/10.1145/170035.170081"&gt;&lt;span class="caps"&gt;LRU&lt;/span&gt;/K&lt;/a&gt;, un serveur de cache à la Memcache/Redis, les &lt;em&gt;chunks&lt;/em&gt; dans S3, ainsi que le chiffrage ne devrait pas poser de&amp;nbsp;problèmes.&lt;/p&gt;
&lt;p&gt;Le &lt;code&gt;copy on write&lt;/code&gt; pour avoir un disque en lecture/écriture,&amp;nbsp;le &lt;code&gt;sparse bytes&lt;/code&gt; pour se passer de compression,&amp;nbsp;le &lt;code&gt;taint read&lt;/code&gt; pour connaitre les &lt;em&gt;chunks&lt;/em&gt; utilisés lors du démarrage nécessiteront un peu plus&amp;nbsp;d&amp;#8217;attention.&lt;/p&gt;
&lt;p&gt;Exposer les &lt;em&gt;blocks&lt;/em&gt; via un &lt;em&gt;vhost user backend&lt;/em&gt; devrait être un poil plus sportif, mais bon, il y a des specs et une implémentation de&amp;nbsp;référence.&lt;/p&gt;
&lt;p&gt;Je ne m&amp;#8217;attendais pas à ce que l&amp;#8217;article permette de faire une maquette, et qu&amp;#8217;il utiliserait des outils secrets insuffisamment définis pour être reproduit. Mais non, avec pas mal de &lt;span class="caps"&gt;RTFM&lt;/span&gt; et d&amp;#8217;assiduité dans la chasse aux bugs, ça&amp;nbsp;passe.&lt;/p&gt;</content><category term="Dev, Ops"></category><category term="firecracker"></category><category term="lambda"></category><category term="oci"></category><category term="ext4"></category><category term="faas"></category><category term="nbd"></category></entry><entry><title>Campagne internationale pour la sécurité informatique : audit automatique des projets</title><link href="http://blog.garambrogne.net/la-maison-blanche-veut-securiser-Internet-2.html" rel="alternate"></link><published>2024-04-08T09:04:00+02:00</published><updated>2024-04-08T09:04:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-04-08:/la-maison-blanche-veut-securiser-Internet-2.html</id><summary type="html">&lt;p&gt;La Maison-Blanche demande un audit sur la sécurité des logiciels open source. Partie 2/3 : audit automatique des&amp;nbsp;projets.&lt;/p&gt;</summary><content type="html">&lt;h1 id="campagne-internationale-pour-la-securite-informatique-partie-2"&gt;Campagne internationale pour la sécurité informatique, partie&amp;nbsp;2&lt;/h1&gt;
&lt;p&gt;La &lt;a href="la-maison-blanche-veut-securiser-Internet-1.html"&gt;partie 1&lt;/a&gt; traitait des dépendances logicielles et des failles&amp;nbsp;officielles.&lt;/p&gt;
&lt;p&gt;Plutôt que d&amp;#8217;attendre la découverte d&amp;#8217;une faille (et sa divulgation), il est quand même plus sage d&amp;#8217;auditer le code que l&amp;#8217;on utilise.
Pouvoir lire le code est quand même une des bases de l&amp;#8217;open&amp;nbsp;source.&lt;/p&gt;
&lt;p&gt;Un humain aura du mal à lire tout le code qu&amp;#8217;il utilise (puis les modifications), et il aura donc besoin de l&amp;#8217;aide de&amp;nbsp;robots.&lt;/p&gt;
&lt;p&gt;Imaginer une lecture exhaustive est utopique, mais ça ne veut pas dire qu&amp;#8217;il ne faut pas&amp;nbsp;essayer.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.camptocamp.org/outings/541082/fr/dent-parrachee-voie-normale-et-descente-par-la-breche-de-la-loza"&gt;&lt;img alt="La Dent parachée" class="image-process-article-image" src="/images/dent-parachee.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="quantifier-la-qualite-du-code"&gt;Quantifier la qualité du&amp;nbsp;code&lt;/h2&gt;
&lt;p&gt;Il est difficile d&amp;#8217;auditer en continu le code que l&amp;#8217;on écrit, le moindre mal étant la revue de code dans les demandes de fusion, mais il n&amp;#8217;est pas humainement possible d&amp;#8217;auditer tout le code open source utilisé, et encore moins de recommencer à chaque mise à&amp;nbsp;jour.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée est de confier cette tâche à des robots, comme assistant, et de manière&amp;nbsp;autonome.&lt;/p&gt;
&lt;h3 id="typage"&gt;Typage&lt;/h3&gt;
&lt;p&gt;Le typage fort (tellement pratique pour la complétion dans les IDEs) s&amp;#8217;avère être aussi très utile pour faire de l&amp;#8217;analyse statique (ou même dynamique) de code.
Le typage fort, longtemps méprisé par les langages de scripting, est maintenant partout : Javascript était tellement mou qu&amp;#8217;il a fallu crée &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt; (mais il y a maintenant la &lt;a href="https://tc39.es/proposal-type-annotations/"&gt;proposition d&amp;#8217;intégrer le typage dans Ecmascript&lt;/a&gt;),
&lt;a href="https://docs.python.org/3/library/typing.html#module-typing"&gt;Python a son typing&lt;/a&gt; et
même Ruby, pourtant fan de monkey patching, a son &lt;a href="https://sorbet.org/"&gt;sorbet&lt;/a&gt; pour gérer le&amp;nbsp;typage.&lt;/p&gt;
&lt;p&gt;Le typage permet d&amp;#8217;avoir des informations sur l&amp;#8217;intention du développeur, indépendamment de l&amp;#8217;implémentation qu&amp;#8217;il propose.
Ce serait dommage de s&amp;#8217;en&amp;nbsp;passer.&lt;/p&gt;
&lt;h3 id="analyse-statique"&gt;Analyse&amp;nbsp;statique&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;analyse statique de code existe depuis longtemps, et ça a même été une des fiertés de Java : les vraies professionnelles ont plein de tableaux de bord avec des graphiques qui vont en s&amp;#8217;améliorant (sauf la consommation de mémoire, mais ce n&amp;#8217;est qu&amp;#8217;un&amp;nbsp;détail).&lt;/p&gt;
&lt;p&gt;Quand l&amp;#8217;analyse touche la sécurité, on parle de &lt;a href="https://en.wikipedia.org/wiki/Static_application_security_testing"&gt;&lt;span class="caps"&gt;SAST&lt;/span&gt;&lt;/a&gt;, Static Application Security&amp;nbsp;Testing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/SonarSource/sonarqube"&gt;SonarQube&lt;/a&gt; regroupe divers outils d&amp;#8217;analyse de codes pour créer de chouettes tableaux de bord dont est friand le management&amp;nbsp;moderne.&lt;/p&gt;
&lt;h3 id="regles"&gt;Règles&lt;/h3&gt;
&lt;p&gt;La recherche d&amp;#8217;erreur récurrente fait maintenant partie des outils de développement des différents langages.
Ces outils mélangent souvent les règles de formatage, les remarques blessantes sur l&amp;#8217;absence de commentaires, de vraies obligations de bonnes pratiques dans le code.
Les outils sont souvent dispersés, et utilisés via un métaoutil &amp;#8220;pour les gouverner tous&amp;#8221;&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://golangci-lint.run/"&gt;Golangci-lint&lt;/a&gt; pour&amp;nbsp;Golang&lt;/li&gt;
&lt;li&gt;&lt;a href="https://astral.sh/ruff"&gt;Ruff&lt;/a&gt; pour&amp;nbsp;Python&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rubocop.org/"&gt;Rubocop&lt;/a&gt; pour&amp;nbsp;Ruby&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-lang/rust-clippy"&gt;Clippy&lt;/a&gt; pour Rust, oui, le nom est un hommage à l&amp;#8217;infâme trombone de &lt;span class="caps"&gt;MS&lt;/span&gt;-Office&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ajinabraham/nodejsscan"&gt;Nodejsscan&lt;/a&gt; pour&amp;nbsp;Nodejs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ansible.readthedocs.io/projects/lint/"&gt;Ansible-lint&lt;/a&gt; pour&amp;nbsp;Ansible&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hadolint/hadolint"&gt;Hadolint&lt;/a&gt; pour les&amp;nbsp;Dockerfile&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tenable/terrascan"&gt;Terrasscan&lt;/a&gt; pour l&amp;#8217;infra as&amp;nbsp;code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/koalaman/shellcheck"&gt;Shellcheck&lt;/a&gt; pour les scripts&amp;nbsp;shell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour aller plus loin que cette sélection, allez jeter un œil sur &lt;a href="https://github.com/analysis-tools-dev/static-analysis"&gt;analysis-tools.dev&lt;/a&gt; que l&amp;#8217;on pourrait renommer &amp;#8220;awesome static&amp;nbsp;analysis&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Ces outils sont souvent utilisés via les &lt;span class="caps"&gt;IDE&lt;/span&gt;, mais ils peuvent se brancher dans l&amp;#8217;intégration continue pour avoir un résultat systématique et homogène.
L&amp;#8217;intégration la plus brutale est de refuser le commit, puis de vautrer la &lt;span class="caps"&gt;CI&lt;/span&gt;, le plus diplomate étant de commenter la demande de fusion (et de bloquer cette&amp;nbsp;demande).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning"&gt;Github intègre les résultats d&amp;#8217;analyse de code&lt;/a&gt; via le format &lt;a href="https://github.com/microsoft/sarif-tutorials"&gt;&lt;span class="caps"&gt;SARIF&lt;/span&gt;&lt;/a&gt;, crée par Microsoft.
Le format, basé sur &lt;span class="caps"&gt;JSON&lt;/span&gt;, est normalisé par &lt;span class="caps"&gt;OASIS&lt;/span&gt;, et utilisé pas différents&amp;nbsp;outils.&lt;/p&gt;
&lt;p&gt;Gitlab a &lt;a href="https://gitlab.com/gitlab-org/gitlab/-/issues/118496"&gt;réfléchi pendant 4 ans à son intégration&lt;/a&gt;, pour finalement proposer &lt;a href="https://gitlab.com/gitlab-org/gitlab/-/issues/345082"&gt;une moulinette qui transforme un rapport &lt;span class="caps"&gt;SARIF&lt;/span&gt; vers leur format maison&lt;/a&gt;, implémenté dans leur projet &lt;a href="https://gitlab.com/gitlab-org/security-products/analyzers/report"&gt;report&lt;/a&gt; sous licence &lt;span class="caps"&gt;MIT&lt;/span&gt; (dans &lt;a href="https://gitlab.com/gitlab-org/security-products/analyzers/report/-/blob/main/sarif.go"&gt;sarif.go&lt;/a&gt; pour être&amp;nbsp;précis).&lt;/p&gt;
&lt;p&gt;Les outils de lint fournissent des listes de règles qui se pensent universelles et on peut les désactiver une à une (attention, c&amp;#8217;est une lourde&amp;nbsp;responsabilité).&lt;/p&gt;
&lt;p&gt;Les règles génériques constituent une base, elles sont écrites et recommandées par des entités de renoms, ce qui permet d&amp;#8217;éviter de longs ergotages sur la pertinence de tel ou tel&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;La découverte d&amp;#8217;une nouvelle faille ou soucis de performance peut être généralisé sous la forme d&amp;#8217;un motif, que l&amp;#8217;on doit pouvoir rechercher autre&amp;nbsp;part.&lt;/p&gt;
&lt;p&gt;Chaque application/groupe/entreprise est unique, et il y aura des motifs récurrents en son sein : des bonnes pratiques, et des erreurs.
Ici aussi, des outils doivent permettre de travailler avec des motifs&amp;nbsp;maison.&lt;/p&gt;
&lt;p&gt;Idéalement, il faudrait pouvoir corriger les problèmes remontés par les outils d&amp;#8217;analyses, à défaut, il est possible de se contenter d&amp;#8217;une note, et attendre les effets de la pression sociale pour que ça s&amp;#8217;améliore (d&amp;#8217;ailleurs, &lt;a href="https://www.ssllabs.com/ssltest/analyze.html?d=blog.garambrogne.net"&gt;ce blog a fièrement un A+ pour son &lt;span class="caps"&gt;SSL&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="recherche"&gt;Recherche&lt;/h3&gt;
&lt;p&gt;La recherche &amp;#8220;full text&amp;#8221; classique, naïve en fait, est &amp;#8220;courte des pattes&amp;#8221; pour faire des recherches dans de grandes quantités de codes.
La normalisation des mots (comme la lemmatisation) n&amp;#8217;est pas pertinente pour du code, et c&amp;#8217;est surtout le débit de modification sature les index&amp;nbsp;inversés.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.blog/2021-12-15-a-brief-history-of-code-search-at-github/"&gt;Github a utilisé Solr puis Elasticsearch&lt;/a&gt;, qui n&amp;#8217;a pas pu suivre la croissance du nombre de projets et de modifications.
Au sommet, Github a utilisé un cluster Elasticsearch de 162 nœuds, 5184 vCPUs, 40To de &lt;span class="caps"&gt;RAM&lt;/span&gt;, 1.25Po de disques, une moyenne de 200 requêtes par seconde, sur 53 milliards de fichiers.
Des dimensions&amp;nbsp;mythologiques.&lt;/p&gt;
&lt;h4 id="expression-reguliere"&gt;Expression&amp;nbsp;régulière&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; est l&amp;#8217;ami du développeur, enfin, &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; de nos jours.
Ils lisent quantité de fichiers pour y rechercher des motifs, ce qui ne va pas bien fonctionner sur de grandes quantités de textes et beaucoup&amp;nbsp;d&amp;#8217;utilisateurs.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cacm.acm.org/research/why-google-stores-billions-of-lines-of-code-in-a-single-repository/"&gt;Google explique comment combiner indexation et expressions régulières&lt;/a&gt; : c&amp;#8217;est ainsi que fonctionnait (fonctionne encore?) leur CodeSearch interne.
C&amp;#8217;est à priori ce même CodeSearch qui est mis à disposition pour rechercher dans
&lt;a href="https://cs.opensource.google/"&gt;ses projets open source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La recette secrète de CodeSearch est de pouvoir appliquer des expressions régulières (enfin, un sous-ensemble qui garantit un temps de réponse raisonnable, &lt;a href="https://github.com/google/re2"&gt;Re2&lt;/a&gt;) sur des quantités gargantuesques de code, indexé par trigrammes.
On commence par extraire les trigrammes de l&amp;#8217;expression de recherche (tout ce qui n&amp;#8217;est pas signe cabalistique), ce qui permet de faire une première sélection de documents contenant ces trigrammes, et de ne lancer la couteuse expression régulière que dans ce&amp;nbsp;sous-ensemble.&lt;/p&gt;
&lt;p&gt;Russ Cox (cofondateur de Go et &amp;#8220;ingénieur distingué&amp;#8221; chez Google) &lt;a href="https://swtch.com/~rsc/regexp/regexp4.html"&gt;explique précisément le principe de la recherche par expression régulière sur des index&lt;/a&gt; avec du &lt;a href="https://github.com/google/codesearch/tree/master"&gt;vieux code archivé comme démonstration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sourcegraph maintient &lt;a href="https://github.com/sourcegraph/zoekt"&gt;Zoekt&lt;/a&gt;, projet initié par Google, qui implémente (et cite) l&amp;#8217;article regexp/trigram, pour des quantités raisonnables de code (2Go), en utilisant un simple &lt;em&gt;btree&lt;/em&gt; pour le stockage.
Attention à la taille des index, qui peut atteindre 3 fois la taille du&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://mush-zhang.github.io/homepage/"&gt;Ling Zang&lt;/a&gt;, doctorante au sein du &lt;a href="https://www.microsoft.com/en-us/research/group/gray-systems-lab/"&gt;Gray Systems Lab&lt;/a&gt; propose une implémentation contemporaine en C++ : &lt;a href="https://github.com/mush-zhang/Blare"&gt;Blare&lt;/a&gt; (BLAzing fast REgex) avec le &lt;a href="https://dl.acm.org/doi/10.1145/3589297"&gt;white paper qui va bien&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La recherche de Github est basée sur l&amp;#8217;article de Cox, mais avec un &lt;a href="https://github.blog/2023-02-06-the-technology-behind-githubs-new-code-search/#fn-69904-bignote"&gt;pinaillage sur la taille des ngrams (les sparse-ngrams)&lt;/a&gt;, car le code contient trop de mots trop courts&amp;nbsp;(&lt;code&gt;for&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;…).
Oui, leur explication technique sur les ngrams clairsemés est&amp;nbsp;nébuleuse.&lt;/p&gt;
&lt;h4 id="arbre-symbolique-abstrait"&gt;Arbre symbolique&amp;nbsp;abstrait&lt;/h4&gt;
&lt;p&gt;Le code n&amp;#8217;est pas de la&amp;nbsp;prose.&lt;/p&gt;
&lt;p&gt;Quand on cherche un élément, on ne veut pas mélanger les commentaires, fonction, argument ou autres informations typées.
De manière similaire, les commentaires (annotations, types…) sont attachés à un&amp;nbsp;élément.&lt;/p&gt;
&lt;p&gt;Pour indexer du code, il est pertinent de commencer par le parser, pour créer un &lt;span class="caps"&gt;AST&lt;/span&gt; (Abstract Symbolic Tree), qui lui, sera&amp;nbsp;indexé.&lt;/p&gt;
&lt;p&gt;Chaque langage civilisé a dans sa bibliothèque standard ce qu&amp;#8217;il faut pour lire sa propre syntaxe.
Ils existent de multiples approches pour parser des formats structurés, et le domaine évolue au fil du temps, et de nouveaux outils apparaissent régulièrement, l&amp;#8217;hégémonie de lex/yacc n&amp;#8217;est&amp;nbsp;plus.&lt;/p&gt;
&lt;h5 id="tree-sitter"&gt;Tree-sitter&lt;/h5&gt;
&lt;p&gt;L&amp;#8217;ambitieux &lt;a href="https://github.com/tree-sitter/tree-sitter"&gt;Tree-Sitter&lt;/a&gt; se positionne comme le générateur universelle de parsers&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gestion d&amp;#8217;un nombre farfelu de formats avec ses &lt;a href="https://tree-sitter.github.io/tree-sitter/#parsers"&gt;parsers&lt;/a&gt; (137 cités dans la&amp;nbsp;doc)&lt;/li&gt;
&lt;li&gt;des &lt;a href="https://tree-sitter.github.io/tree-sitter/#language-bindings"&gt;bindings&lt;/a&gt; dans 19&amp;nbsp;langages&lt;/li&gt;
&lt;li&gt;tree-sitter sait créer une bibliothèque partagée (un .so, quoi) à partir d&amp;#8217;une grammaire, et la plupart des bindings utiliseront ce format, il y aura donc du C dans votre langage préféré. Le parser est suffisamment rapide pour reparser le buffer d&amp;#8217;un éditeur de texte à chaque clic d&amp;#8217;une&amp;nbsp;touche.&lt;/li&gt;
&lt;li&gt;les &lt;a href="https://en.wikipedia.org/wiki/S-expression"&gt;s-expressions&lt;/a&gt; (les listes imbriquées de Lisp) sont utilisées à différents endroits : comme format de sortie, comme prédicat pour filtrer le parcours d&amp;#8217;un&amp;nbsp;arbre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La navigation de code de Github utilise tree-sitter, ainsi que son moteur de&amp;nbsp;recherche.&lt;/p&gt;
&lt;p&gt;Sourcegraph aussi aime bien&amp;nbsp;tree-sitter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;doctree&lt;/strong&gt; crée un site web de documentation de code, mais &lt;a href="https://github.com/sourcegraph/doctree/graphs/contributors"&gt;le code n&amp;#8217;a pas été touché depuis 2 ans&lt;/a&gt;, et le nom de domaine est&amp;nbsp;mort.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sourcegraph/cody"&gt;cody&lt;/a&gt; est une &lt;span class="caps"&gt;IA&lt;/span&gt; qui connait votre&amp;nbsp;code.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sourcegraph/sourcegraph"&gt;sourcegraph&lt;/a&gt; permet de naviguer et de chercher dans votre code (et une intégration avec&amp;nbsp;Cody)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bien d&amp;#8217;autres applications utilisent tree-sitter, dont certaines seront évoquées plus bas dans&amp;nbsp;l&amp;#8217;article.&lt;/p&gt;
&lt;h4 id="datalog"&gt;Datalog&lt;/h4&gt;
&lt;p&gt;Ni les moteurs de recherche &lt;em&gt;full text&lt;/em&gt;, ni les bases de données relationnelles ne sont adaptés pour indexer des projets avec leur&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Il faut donc étendre la recherche dans les modèles exotiques de bases de données.
Avant l&amp;#8217;invention de la base de données relationnelle, il y a eu les &lt;a href="https://en.wikipedia.org/wiki/Deductive_database"&gt;bases de données déductives&lt;/a&gt;, inspirées par &lt;a href="https://en.wikipedia.org/wiki/Prolog"&gt;Prolog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le concept de ces bases est proche de celui des bases orientées graphes, les données sont modélisées par des triplets : 2 nœuds liés par une&amp;nbsp;relation.&lt;/p&gt;
&lt;p&gt;Le modèle déductif a &lt;a href="https://en.wikipedia.org/wiki/Datalog"&gt;Datalog&lt;/a&gt; comme langage de requête tout comme le modèle relationnel a &lt;strong&gt;&lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="embeded-code"&gt;Embeded&amp;nbsp;code&lt;/h4&gt;
&lt;p&gt;Rien à voir avec les ordinateurs trop petits de&amp;nbsp;l&amp;#8217;embarqué, &lt;code&gt;embeded&lt;/code&gt; désigne la technique de représenter les mots (enfin, des &lt;em&gt;tokens&lt;/em&gt;) sous forme de vecteurs, ainsi que les documents, permettant ainsi de faire des recherches de proximité (en calculant la distance entre deux vecteurs).
C&amp;#8217;est la base des &lt;span class="caps"&gt;LLM&lt;/span&gt;, les &lt;em&gt;Larges Languages Model&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Vous reprendrez bien un louche d&amp;#8217;&lt;span class="caps"&gt;IA&lt;/span&gt;?&lt;/p&gt;
&lt;p&gt;Github a documenté ses travaux pour proposer de la &lt;a href="https://github.blog/2018-09-18-towards-natural-language-semantic-code-search/"&gt;recherche sémantique de code&lt;/a&gt; (avec les liens vers les papiers de recherche), pour proposer l&amp;#8217;année suivante (2019) un &lt;a href="https://github.blog/2019-09-26-introducing-the-codesearchnet-challenge/"&gt;concours sur le même thème&lt;/a&gt; avec encore des citations de publications sympathiques comme &lt;a href="https://arxiv.org/abs/2201.10005"&gt;embarquement de texte et de code par préentrainement contrasté&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tout ça a fini par devenir Copilot, leur magicien de l&amp;#8217;autocomplétion de&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;LLM&lt;/span&gt; peuvent rendre de grands services, mais pas (encore?) de chasser le&amp;nbsp;bogue.&lt;/p&gt;
&lt;h3 id="sourcegraph"&gt;Sourcegraph&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://sourcegraph.com/search"&gt;Sourcegraph&lt;/a&gt; essaye de se positionner comme le leader de la recherche de code, bien que seule une petite portion de son code soit libre.
On peut installer le service sur son poste, pour avoir la première dose&amp;nbsp;gratuite.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.gitlab.com/ee/integration/sourcegraph.html#set-up-a-self-managed-sourcegraph-instance"&gt;Gitlab peut utiliser Sourcegraph&lt;/a&gt; pour naviguer dans le code, depuis l&amp;#8217;interface web, et effectuer des recherches, ça en fait presque un standard de&amp;nbsp;fait.&lt;/p&gt;
&lt;p&gt;La recherche de Sourcegraph s&amp;#8217;appuie sur un salmigondis de bases de données (du trigramme/regexp avec Zoepkt, du vecteur avec &lt;a href="https://github.com/qdrant/qdrant"&gt;Qdrant&lt;/a&gt;, du relationnel avec Postgres et Sqlite).
On peut ajouter à ça un peu de &lt;em&gt;full text&lt;/em&gt; (j&amp;#8217;ai vu passer des bibliothèques de lemmatisation), et de l&amp;#8217;&lt;span class="caps"&gt;IA&lt;/span&gt; avec son&amp;nbsp;Cody.&lt;/p&gt;
&lt;p&gt;Sourcegraph a essayé d&amp;#8217;étendre &lt;a href="https://langserver.org/"&gt;Langserver&lt;/a&gt; (le &lt;span class="caps"&gt;LSP&lt;/span&gt; des &lt;span class="caps"&gt;IDE&lt;/span&gt;) avec &lt;a href="https://lsif.dev/"&gt;&lt;span class="caps"&gt;LSIF&lt;/span&gt;&lt;/a&gt; pour normaliser l&amp;#8217;indexation de code, pour finalement abandonner et déprécier ses contributions, pour se rediriger vers son &lt;a href="https://github.com/sourcegraph/scip"&gt;&lt;span class="caps"&gt;SCIP&lt;/span&gt;&lt;/a&gt;, qui crée des index que son serveur peut manger.
Un outil permet de dumper les index en &lt;span class="caps"&gt;JSON&lt;/span&gt;, pour l&amp;#8217;utiliser en dehors de&amp;nbsp;Sourcegraph.&lt;/p&gt;
&lt;p&gt;Sourcegraph propose des outils pour &lt;a href="https://sourcegraph.com/docs/batch_changes"&gt;rechercher puis patcher du code&lt;/a&gt;, mais l&amp;#8217;implémentation actuelle est brutale : il faut écrire un bout de bash qui sera lancé dans un conteneur sur toutes les occurrences de la&amp;nbsp;recherche.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;écosystème Sourcegraph fournit des briques intéressantes, mais rien de bouleversant pour aller à la chasse au mauvais&amp;nbsp;code.&lt;/p&gt;
&lt;h3 id="codeql"&gt;CodeQL&lt;/h3&gt;
&lt;p&gt;Microsoft, en rachetant Github s&amp;#8217;est retrouvé, de fait, comme responsable d&amp;#8217;une bonne partie des sources du logiciel libre.
C&amp;#8217;est assez cocasse quand on connait &lt;a href="https://en.wikipedia.org/wiki/Microsoft_and_open_source"&gt;la position de Steve Ballmer quelques décennies plus tôt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gardien des sources, des bugs mais aussi de la flemme de tous les&amp;nbsp;jours.&lt;/p&gt;
&lt;p&gt;Microsoft est bien au courant de la valeur d&amp;#8217;une &lt;span class="caps"&gt;CVE&lt;/span&gt;, il a donc ajouté des outils pour boucher le code&amp;nbsp;troué.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dependabot"&gt;Dependabot&lt;/a&gt; (dont certaines briques sont libres) est un robot qui crée des demandes de fusion quand la correction est triviale (mettre à jour une dépendance officiellement trouée) ou envoie des alertes s’il faut prendre une décision pour&amp;nbsp;corriger.&lt;/p&gt;
&lt;p&gt;Personnellement, je pense que Dependabot est aussi un complot pour faire regretter d&amp;#8217;avoir choisi Nodejs, qui avec sa palanquée de microbibliothèques, va statistiquement trouver au moins une faille par semaine, quelque part au fond d&amp;#8217;une branche du graphe de vos&amp;nbsp;dépendances.&lt;/p&gt;
&lt;p&gt;La vraie puissance de Dependabot n&amp;#8217;est pas juste de lire et patcher les listes de dépendances figées, mais la recherche de motif de code avec &lt;a href="https://codeql.github.com/docs/ql-language-reference/about-the-ql-language/"&gt;CodeQL&lt;/a&gt; en fournissant un &lt;a href="https://github.com/github/codeql/"&gt;lot de règles CodeQL&lt;/a&gt;, qui ont la gentillesse d&amp;#8217;être sous licence &lt;span class="caps"&gt;MIT&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;CodeQL a fait le pari de Datalog pour écrire des requêtes.
Choisir un langage ésotérique, inventé avant le Minitel, est audacieux.
Charge à Github d&amp;#8217;utiliser correctement ses index, le cache et la parallélisation pour exécuter efficacement ces recherches déclenchées par la &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="regles-metiers"&gt;Règles&amp;nbsp;métiers&lt;/h3&gt;
&lt;p&gt;Il existe divers outils pour décrire des patchs génériques à appliquer sur quantités de projets (ou dans un bon gros&amp;nbsp;monorepo).&lt;/p&gt;
&lt;p&gt;Ils sont souvent conçus pour créer des patchs jetables, et se contentent pour la plupart d&amp;#8217;une utilisation en ligne de commande, quelques-uns proposent une intégration plus fine, avec des consoles interactives ou de l&amp;#8217;intégration&amp;nbsp;continue.&lt;/p&gt;
&lt;h4 id="agnostique"&gt;Agnostique&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/semgrep/semgrep"&gt;Semgrep&lt;/a&gt;, écrit en OCaml qui aime tant le parsing, propose d&amp;#8217;appliquer des règles décrites en &lt;span class="caps"&gt;YAML&lt;/span&gt;.
Il y a une version libre en &lt;span class="caps"&gt;LGPL&lt;/span&gt;, et pour avoir le parsing multifichier et d&amp;#8217;autres bonus, il faut passer à la caisse.
On notera la possibilité d&amp;#8217;obtenir une réponse en &lt;span class="caps"&gt;SARIF&lt;/span&gt;, gage d&amp;#8217;intégration avec d&amp;#8217;autres&amp;nbsp;produits.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/comby-tools/comby"&gt;Comby&lt;/a&gt;, encore en OCaml, et sous licence Apache 2, se positionne comme roi du &lt;em&gt;one liner&lt;/em&gt;, pour remplacer le traditionnel grep+sed. Il positionne comme outil de patch universel, plutôt que comme&amp;nbsp;linter.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/facebook/infer"&gt;Infer&lt;/a&gt;, de chez Facebook, et toujours en OCaml, se contente de chasser le bogue en C/C++/Objective-C et Java, mais il est utilisé par de grands&amp;nbsp;noms.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ast-grep/ast-grep"&gt;ast-grep&lt;/a&gt;, écrit en Rust avec du tree-sitter, se positionne&amp;nbsp;comme &lt;code&gt;grep&lt;/code&gt; du code, alors qu&amp;#8217;il permet aussi de &lt;strong&gt;remplacer&lt;/strong&gt;, en plus de &lt;strong&gt;rechercher&lt;/strong&gt; des&amp;nbsp;motifs.&lt;/p&gt;
&lt;h4 id="python"&gt;Python&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/pyparsing/pyparsing"&gt;pyparsing&lt;/a&gt; a longtemps été la brique de base pour lire du code (pour ensuite râler ou proposer une correction).
Yelp a utilisé &lt;a href="https://github.com/Yelp/undebt"&gt;Undebt&lt;/a&gt; pour faire de la chasse au code déprécié. La chasse a été bonne, mais le projet a été&amp;nbsp;archivé.&lt;/p&gt;
&lt;p&gt;Plus classique, issu de l&amp;#8217;équipe sécurité d&amp;#8217;OpenStack, puis maintenu par la &lt;a href="https://meta.pycqa.org/"&gt;Python Code Quality Authority&lt;/a&gt;, &lt;a href="https://github.com/PyCQA/bandit"&gt;bandit&lt;/a&gt; parcours l&amp;#8217;&lt;span class="caps"&gt;AST&lt;/span&gt; d&amp;#8217;un fichier Python pour y trouver des mauvaises pratiques.
Bandit propose une liste de règles, mais il est aisé d&amp;#8217;en créer de nouvelles.
Une règle s&amp;#8217;abonne à un évènement (souvent un appel de fonction), qui déclenchera une vérification, pouvant renvoyer un Incident.
Les règles fournies sont simples (voir naïves), &lt;em&gt;bandit&lt;/em&gt; ne permet pas une grande&amp;nbsp;expressivité.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://redbaron.readthedocs.io/en/latest/"&gt;RedBaron&lt;/a&gt; propose de naviguer dans l&amp;#8217;arbre abstrait de code python, de manière interactive, avec la possibilité de modifier des éléments, et de revenir à un code source avec juste ce qu&amp;#8217;il faut de modifications.
RedBaron a tout ce qu&amp;#8217;il faut pour créer des patchs génériques, mais pour l&amp;#8217;instant, il n&amp;#8217;a pas exploré la recherche de &amp;#8220;mauvais motif&amp;#8221; avec une correction; il est plus conçu pour créer du patch du sur mesure, pas du&amp;nbsp;prêt-à-porter.&lt;/p&gt;
&lt;p&gt;Facebook a sa productive équipe OCaml et propose &lt;a href="https://pyre-check.org/"&gt;Pyre&lt;/a&gt;, parfois appelé Pysa (pour PYre Security Audit).
Pyre file la métaphore des hydrologues, avec des &lt;em&gt;source&lt;/em&gt; qui sont &lt;em&gt;tainted&lt;/em&gt; (des inputs confiés à un utilisateur) et des &lt;em&gt;sink&lt;/em&gt; (des zones à surveiller, comme l&amp;#8217;exécution d&amp;#8217;une commande) qui seront teintés ou non.
Pyre s&amp;#8217;appuie sur le typage de&amp;nbsp;Python.&lt;/p&gt;
&lt;h4 id="golang"&gt;Golang&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/uber-go/gopatch"&gt;Gopatch&lt;/a&gt; reprend l&amp;#8217;astuce de &lt;a href="https://coccinelle.gitlabpages.inria.fr/website/"&gt;Coccinelle&lt;/a&gt;, et utilise l&amp;#8217;antique syntaxe de patch&amp;nbsp;de &lt;code&gt;diff&lt;/code&gt; pour décrire des&amp;nbsp;correctifs.&lt;/p&gt;
&lt;h5 id="java"&gt;Java&lt;/h5&gt;
&lt;p&gt;Java propose des outils étranges et grandiloquents, comme
&lt;a href="https://docs.openrewrite.org/"&gt;OpenRewrite&lt;/a&gt; et
&lt;a href="https://www.rascal-mpl.org/"&gt;Rascal&lt;/a&gt;.
Les patchs, c&amp;#8217;est pour les petits bras, Java mérite de la méta&amp;nbsp;programmation.&lt;/p&gt;
&lt;h3 id="fuzzing"&gt;Fuzzing&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;analyse statique, c&amp;#8217;est bien, mais l&amp;#8217;analyse dynamique peut apporter des informations&amp;nbsp;complémentaires.&lt;/p&gt;
&lt;p&gt;Le fuzzing est une des stratégies possibles d&amp;#8217;analyse dynamique, il en existe&amp;nbsp;d&amp;#8217;autres.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée du fuzzing est de jeter du pâté dans le ventilateur (&amp;#8220;spam hits the fan&amp;#8221; en &lt;span class="caps"&gt;VO&lt;/span&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le code est instrumenté, depuis l&amp;#8217;intérieur en le recompilant (ou une technique équivalente pour du script), ou depuis l&amp;#8217;extérieur via le kernel (souvent via &lt;span class="caps"&gt;QEMU&lt;/span&gt;). Des désinfecteurs (&lt;em&gt;sanitizer&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;) vont surveiller des comportements anormaux, tant sur le plan logiciel que&amp;nbsp;matériel.&lt;/li&gt;
&lt;li&gt;On désigne un point d&amp;#8217;entrée (une&amp;nbsp;fonction)&lt;/li&gt;
&lt;li&gt;Il est possible de modifier les appels système (avec &lt;a href="https://github.com/zardus/preeny"&gt;preeny&lt;/a&gt; par exemple) pour que le code ait un fonctionnement plus déterministe, voire même de remplacer un échange réseau par une discussion sur &lt;span class="caps"&gt;STDIN&lt;/span&gt;/&lt;span class="caps"&gt;STDOUT&lt;/span&gt;, les fuzzer préfèrent appeler une fonction, plutôt que de démarrer un serveur&amp;nbsp;complet.&lt;/li&gt;
&lt;li&gt;À partir d&amp;#8217;exemples, ou de rien, un corpus va être créé, en essayant de maximiser la couverture de code. Ce corpus devient un artéfact et sera&amp;nbsp;réutilisé.&lt;/li&gt;
&lt;li&gt;Le point d&amp;#8217;entrée est testé avec des données issues du corpus, auquel on applique des mutations. En cas d&amp;#8217;erreurs (ce que l&amp;#8217;on cherche), le test sera réduit (&lt;em&gt;shrinking&lt;/em&gt; en &lt;span class="caps"&gt;VO&lt;/span&gt;) pour n&amp;#8217;avoir qu&amp;#8217;un changement mineur entre une valeur qui passe, et celle qui ne passe&amp;nbsp;pas.&lt;/li&gt;
&lt;li&gt;Historiquement, les données en entrée sont un gros binaire, ce qui correspond aux cas classiques de la lecture d&amp;#8217;un fichier ou d&amp;#8217;un échange réseau. Il est aussi possible de définir des générateurs (ou des grammaires), pour fournir du contenu&amp;nbsp;typé.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Générer du contenu complètement aléatoire sera peu efficace, il existe différentes stratégies faisant référence à la génétique, avec des notions de mutations, qui ne sont pas retenues si elles ne génèrent pas d&amp;#8217;erreur ou n&amp;#8217;améliorent pas la couverture de&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Bien sûr, il est possible d&amp;#8217;&lt;a href="https://security.googleblog.com/2023/08/ai-powered-fuzzing-breaking-bug-hunting.html"&gt;utiliser de l&amp;#8217;&lt;span class="caps"&gt;IA&lt;/span&gt; pour fuzzer encore plus fort&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les fuzzers subissent une forte sélection darwinienne, les bonnes idées sont conservées pour la génération suivante, les autres idées finissent en&amp;nbsp;fossile.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lcamtuf.coredump.cx/afl/"&gt;&lt;span class="caps"&gt;AFL&lt;/span&gt;&lt;/a&gt;, considéré comme standard de fait, mute en &lt;a href="https://github.com/AFLplusplus/AFLplusplus"&gt;&lt;span class="caps"&gt;AFL&lt;/span&gt;++&lt;/a&gt; pour finalement donner &lt;a href="https://github.com/google/honggfuzz"&gt;HonggFuzz&lt;/a&gt;, et je vous fais grâce de l&amp;#8217;explosion cambrienne listée dans &lt;a href="https://github.com/Microsvuln/Awesome-AFL"&gt;Awesome-&lt;span class="caps"&gt;AFL&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.gitlab.com/ee/user/application_security/api_fuzzing/"&gt;Gitlab propose de s&amp;#8217;interfacer avec du fuzzing&lt;/a&gt;, mais dans sa version ultimate (la plus chère), en vous laissant &lt;a href="https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing"&gt;l&amp;#8217;approche &lt;span class="caps"&gt;DIY&lt;/span&gt; dans votre très classique &lt;span class="caps"&gt;CI&lt;/span&gt;, avec des exemples dans différents&amp;nbsp;langages&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="oss-fuzz"&gt;Oss-fuzz&lt;/h4&gt;
&lt;p&gt;Google a testé ses outils de fuzzing créés pour Chrome sur des logiciels opens sources, et a trouvé beaucoup d&amp;#8217;erreurs, certaines avec des implications au niveau sécurité.
Comme les erreurs découvertes se chiffrent en milliers, Google s&amp;#8217;est associé à l&amp;#8217;&lt;a href="https://openssf.org/"&gt;&lt;span class="caps"&gt;OSSF&lt;/span&gt;&lt;/a&gt; pour créer &lt;a href="https://github.com/google/oss-fuzz"&gt;oss-fuzz&lt;/a&gt;, un service de fuzzing continue dédié à l&amp;#8217;open&amp;nbsp;source.&lt;/p&gt;
&lt;p&gt;Attention, on entre dans le monde merveilleux du code Google libéré : il faut s&amp;#8217;attendre à des acronymes aléatoires de 4 lettres, des projets remplaçant le précédent (parfois 3 ou 4 remplacements) et des &lt;span class="caps"&gt;README&lt;/span&gt; partiellement à&amp;nbsp;jour.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;outil, &lt;a href="https://github.com/google/clusterfuzz"&gt;ClusterFuzz&lt;/a&gt; (avec sa variante allégée, &lt;a href="https://google.github.io/clusterfuzzlite/"&gt;ClusterFuzzLite&lt;/a&gt;), est libre (licence Apache 2). Il est conseillé pour fuzzer du code non libre (sur une instance&amp;nbsp;interne).&lt;/p&gt;
&lt;p&gt;ClusterFuzz gère différent fuzzers historiques, et globalement, les fuzzers spécifiques se rapprochent de &lt;a href="https://llvm.org/docs/LibFuzzer.html"&gt;LibFuzzer&lt;/a&gt; dans leurs&amp;nbsp;comportements.&lt;/p&gt;
&lt;p&gt;On notera que l&amp;#8217;&lt;a href="https://jfrog.com/blog/xz-backdoor-attack-cve-2024-3094-all-you-need-to-know/"&gt;attaque sur OpenSSH via xz&lt;/a&gt; a commencé par désactiver le fuzzing d&amp;#8217;Oss-fuzz sur la partie qu&amp;#8217;il a ensuite contaminée : témoignage de crainte face à Oss-fuzz.
&lt;span class="caps"&gt;OK&lt;/span&gt;, Oss-fuzz n&amp;#8217;a pas découvert cette faille, car le cambrioleur a coupé l&amp;#8217;alarme de la salle du coffre, mais il a pris la peine de le&amp;nbsp;faire.&lt;/p&gt;
&lt;h5 id="sanitizers"&gt;Sanitizers&lt;/h5&gt;
&lt;p&gt;Les &lt;a href="https://github.com/google/sanitizers"&gt;désinfecteurs&lt;/a&gt; vont surveiller ce qu&amp;#8217;il se passe au niveau de l&amp;#8217;allocation de la mémoire et des&amp;nbsp;threads.&lt;/p&gt;
&lt;p&gt;Les désinfecteurs peuvent être utilisés en instrumentant le code compilé (avec &lt;span class="caps"&gt;LLVM&lt;/span&gt; et même &lt;em&gt;gcc&lt;/em&gt;), puis le code est exécuté avec un &lt;em&gt;malloc&lt;/em&gt;&amp;nbsp;spécifique.&lt;/p&gt;
&lt;p&gt;Le kernel Linux est maintenant outillé pour surveiller ce qu&amp;#8217;il se passe au niveau de la mémoire et des threads (qui font quand même partie de sa responsabilité).
Ces modules ont été intégrés au fur et à mesure dans le kernel : ce sont les &lt;a href="https://github.com/google/kernel-sanitizers"&gt;K*&lt;span class="caps"&gt;SAN&lt;/span&gt;&lt;/a&gt;.
Ils ne gèreront pas forcément toutes les architectures, et pour certains, il faudra utiliser un noyau compilé avec &lt;em&gt;&lt;span class="caps"&gt;LLVM&lt;/span&gt;&lt;/em&gt; plutôt que &lt;em&gt;gcc&lt;/em&gt; (ne le dites pas à Stallman).
Les kernels instrumentés ne visent pas la production, mais une &lt;span class="caps"&gt;VM&lt;/span&gt; temporaire pour secouer du code dans une cuve pleine de&amp;nbsp;capteurs.&lt;/p&gt;
&lt;h5 id="fuzzers"&gt;Fuzzers&lt;/h5&gt;
&lt;p&gt;D&amp;#8217;autres &lt;a href="https://google.github.io/oss-fuzz/getting-started/new-project-guide/"&gt;langages disposent d&amp;#8217;une instrumentation compatible avec oss-fuzz&lt;/a&gt; : C/C++, Rust, Go, Python, Java/&lt;span class="caps"&gt;JVM&lt;/span&gt;, Swift et JavaScript (dont typescript), pour les autres, ce sera uniquement via &lt;span class="caps"&gt;LLVM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Comme grand absent, on notera Ruby, tellement fière de la liberté de sa syntaxe et assument le &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch"&gt;monkey patching&lt;/a&gt; ne doit pas aider les fuzzers à se concentrer.
Mais trêve de mauvaise langue, le projet &lt;a href="https://blog.trailofbits.com/2024/03/29/introducing-ruzzy-a-coverage-guided-ruby-fuzzer/"&gt;Ruzzy&lt;/a&gt; reprend pour Ruby l&amp;#8217;approche d&amp;#8217;&lt;a href="https://github.com/google/atheris"&gt;Atheris&lt;/a&gt; (le binding de libFuzzer pour&amp;nbsp;Python).&lt;/p&gt;
&lt;h4 id="mutation-de-test"&gt;Mutation de&amp;nbsp;test&lt;/h4&gt;
&lt;p&gt;Le principe de mutation de données peut être utilisé sur de très classiques tests unitaires pour répondre à la question &amp;#8220;Qui test les test?&amp;#8221; (ou
&lt;a href="https://fr.wikipedia.org/wiki/Quis_custodiet_ipsos_custodes%3F"&gt;Quis custodiet ipsos custodes?&lt;/a&gt; pour les fans de péplums en &lt;span class="caps"&gt;VO&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;La &lt;a href="https://en.wikipedia.org/wiki/Mutation_testing"&gt;mutation de test&lt;/a&gt; est une idée de Richard Lipton, alors étudiant, en 1971, ce qui ne nous rajeunit pas (c&amp;#8217;est plus vieux&amp;nbsp;que &lt;code&gt;vi&lt;/code&gt;).
Il a attendu 1980 pour donner le sujet à un thésard en &lt;strong&gt;philosophie&lt;/strong&gt;, Thimoty Alan Budd : &lt;a href="https://gwern.net/doc/cs/algorithm/1980-budd.pdf"&gt;Mutation Analysys of Program Test Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée est de cochonner votre base de code avec des erreurs, et de voir si les tests passent encore.
Les mutations correspondent à des fautes que ferait un développeur distrait (remplacer&amp;nbsp;un &lt;code&gt;&amp;gt;&lt;/code&gt; par&amp;nbsp;une &lt;code&gt;&amp;gt;=&lt;/code&gt;,&amp;nbsp;un &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; par&amp;nbsp;un &lt;code&gt;||&lt;/code&gt;, mettre&amp;nbsp;un &lt;code&gt;not&lt;/code&gt; devant un booléen, ce genre&amp;nbsp;d&amp;#8217;étourderies).&lt;/p&gt;
&lt;p&gt;Le logiciel va créer un rapport et coller une note : le ratio entre le nombre de mutants attrapés et le nombre de mutants&amp;nbsp;crée.&lt;/p&gt;
&lt;p&gt;Si votre outil est un poil finaud, il va commencer par lancer les tests un à un, et noter la couverture de code à chaque fois.
Ainsi, il ne va muter que le code couvert par les tests, et ne lancera que les tests&amp;nbsp;concernés.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="https://github.com/topics/mutation-testing"&gt;mutations de tests ne déclenchent pas un enthousiasme fou sur Github&lt;/a&gt;, mais il y a largement de quoi&amp;nbsp;farfouiller.&lt;/p&gt;
&lt;p&gt;Le leader de ce domaine est Java, avec &lt;a href="https://pitest.org/"&gt;Pitest&lt;/a&gt;, mais il existe aussi &lt;a href="https://github.com/boxed/mutmut"&gt;mutmut&lt;/a&gt; pour Python, &lt;a href="https://mutants.rs/"&gt;Mutant.rs&lt;/a&gt; ou &lt;a href="https://github.com/go-gremlins/gremlins"&gt;go-Gremlins&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="quantifier-la-qualite-dun-projet"&gt;Quantifier la qualité d&amp;#8217;un&amp;nbsp;projet&lt;/h2&gt;
&lt;p&gt;Nous venons de voir qu&amp;#8217;il était possible de lancer des hordes de robots pour auditer le code de logiciels open source que vous souhaiteriez utiliser.
Mais la qualité du code n&amp;#8217;est qu&amp;#8217;une partie de la qualité globale du&amp;nbsp;projet.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;exemple de &lt;a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor"&gt;l&amp;#8217;attaque sur xz&lt;/a&gt; est frappant, sur un des miroirs (le projet officiel est banni), on voit que la qualité du code n&amp;#8217;est qu&amp;#8217;un détail :le payload de l&amp;#8217;attaque, présenté comme une fixture, est chiffré, et le déclencheur n&amp;#8217;est pas versionné dans le git, mais juste dans la tarball de la &lt;em&gt;release&lt;/em&gt;.
La vraie information, ce sont les &lt;a href="https://github.com/xz-mirror/xz/graphs/contributors"&gt;contributions de xz&lt;/a&gt;, il y a &lt;span class="caps"&gt;UN&lt;/span&gt; mainteneur, Larzhu, une poignée de gens faisant quelques propositions de modifications, et un sauveur, l&amp;#8217;infâme JiaT75, qui vient filer un coup de&amp;nbsp;main.&lt;/p&gt;
&lt;h3 id="scorecarddev"&gt;ScoreCard.dev&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;OpenSource Security Foundation, a créé une moulinette, [ScoreCard] (https://scorecard.dev/) qui donne une note aux projets open source, dans une approche très scolaire, pour pousser les projets à suivre de bonnes&amp;nbsp;pratiques.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="https://scorecard.dev/#the-checks"&gt;différentes vérifications&lt;/a&gt; sont plus que légitimes, mais aussi des pousse-au-crime pour utiliser l&amp;#8217;écosystème Github de bout en&amp;nbsp;bout.&lt;/p&gt;
&lt;p&gt;J&amp;#8217;espère que ça va piailler, et qu&amp;#8217;il va y avoir du lobbying pour que des services alternatifs (des gros services en ligne, ou des petits autohébergés) soient pris en compte pour le calcul du&amp;nbsp;ScoreCard.&lt;/p&gt;
&lt;h3 id="depsdev"&gt;Deps.dev&lt;/h3&gt;
&lt;p&gt;Google, avec &lt;a href="https://deps.dev/"&gt;deps.dev&lt;/a&gt; pousse l&amp;#8217;idée de ScoreCard.dev un cran plus loin en recyclant leur &lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt;.
la note finale d&amp;#8217;un paquet est calculée en fonction du graphe de dépendance tirée par ce paquet.
Un paquet avec une mauvaise note va faire baisser la note de ceux qui l&amp;#8217;importent, directement ou&amp;nbsp;indirectement.&lt;/p&gt;
&lt;p&gt;Autre avantage de deps.dev est qu&amp;#8217;il propose un chouette site web avec un moteur de recherche alors que scorecard.dev vous propose de télécharger des &lt;span class="caps"&gt;CSV&lt;/span&gt; de dimensions&amp;nbsp;cyclopéennes.&lt;/p&gt;
&lt;h3 id="criticite-dun-projet"&gt;Criticité d&amp;#8217;un&amp;nbsp;projet&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;&lt;span class="caps"&gt;OSSF&lt;/span&gt; a une autre moulinette (en bêta) pour donner une note à l&amp;#8217;importance d&amp;#8217;un paquet au sein de l&amp;#8217;écosystème open source : le &lt;a href="https://github.com/ossf/criticality_score"&gt;Criticality Score&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il utilise le ScoreCard, l&amp;#8217;âge, le nombre de projets l&amp;#8217;utilisant, le nombre de contributeurs (individuels et organisations), le débit de contributions et de tickets, ce genre de&amp;nbsp;choses.&lt;/p&gt;
&lt;p&gt;Ce genre de classement devrait permettre de trouver les maillons faibles de la chaine d&amp;#8217;approvisionnement, et justifier des financements pour des développeurs quelconques du Nebraska, comme le recommande &lt;span class="caps"&gt;XKCD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://xkcd.com/2347/"&gt;&lt;img alt="Dependency by XKCD" class="image-process-article-image" src="images/dependency_2x.png" /&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Dev"></category><category term="sourcegraph"></category><category term="tree-sitter"></category><category term="comby"></category><category term="security"></category><category term="ossf"></category></entry><entry><title>Distroless, les images sans distributions</title><link href="http://blog.garambrogne.net/distroless.html" rel="alternate"></link><published>2024-03-24T09:04:00+01:00</published><updated>2024-03-24T09:04:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-03-24:/distroless.html</id><summary type="html">&lt;p&gt;Repenser la construction des images de conteneur pour un gain en simplicité, en poids et en vélocité, sans oublier de gérer les multiples architectures&amp;nbsp;processeurs.&lt;/p&gt;</summary><content type="html">&lt;h2 id="less-is-more"&gt;Less is&amp;nbsp;more&lt;/h2&gt;
&lt;p&gt;Le journalisme a un suffixe magique, &amp;#8220;gate&amp;#8221;, qui permet de créer des buzzwords qui claquent, depuis le &lt;a href="https://fr.wikipedia.org/wiki/Les_Hommes_du_pr%C3%A9sident_(film)"&gt;watergate&lt;/a&gt;.
En informatique, le suffixe magique est &amp;#8220;less&amp;#8221;, comme&amp;nbsp;dans&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Diskless_node"&gt;diskless&lt;/a&gt;, un ordinateur qui démarre via le réseau (sans utiliser son&amp;nbsp;disque).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Headless_CMS"&gt;headless&lt;/a&gt;, un système de gestion de contenu, qui permettra de générer un site statique et/ou une &lt;span class="caps"&gt;API&lt;/span&gt; distante depuis le javascript de la page&amp;nbsp;web.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackaday.com/tag/cpu-less/"&gt;cpu-less&lt;/a&gt;, blagounette d&amp;#8217;ordinateur rudimentaire à base de portes&amp;nbsp;logiques.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.com/blog/dbless-architecture-and-why-its-the-future/"&gt;db-less&lt;/a&gt;, architecture sans base de données&amp;nbsp;relationnelles.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;serverless&lt;/a&gt;, du cloud sans le cloud, juste votre code poussé dans le&amp;nbsp;nuage.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/No-code_development_platform"&gt;code-less&lt;/a&gt;, le terme no-code est plus courant. Une interface permet de décrire une logique métier, sans écrire de&amp;nbsp;code.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://browserless.io/"&gt;browserless&lt;/a&gt;, des test fonctionnels (ou du scraping de fourbe), depuis un navigateur web sur un ordinateur sans&amp;nbsp;écran.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fr.wikipedia.org/wiki/Less_(Unix)"&gt;less&lt;/a&gt;, comme &lt;a href="https://en.wikipedia.org/wiki/More_(command)"&gt;more&lt;/a&gt; mais mieux (humour &lt;span class="caps"&gt;UNIX&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;distroless&lt;/strong&gt;, une image conteneur construite sans distribution&amp;nbsp;Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="http://www.nioc.eu/albums/Entre-lac-et-montagnes-Annecy/photos/636"&gt;&lt;img alt="Lac d'Annecy" class="image-process-article-image" src="/images/Entre-lac-et-montagnes-Annecy-636-1920.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="conteneur"&gt;Conteneur&lt;/h2&gt;
&lt;h3 id="nouvelles-abstractions"&gt;Nouvelles&amp;nbsp;abstractions&lt;/h3&gt;
&lt;p&gt;Quand la virtualisation est apparue, la promesse était &amp;#8220;ne touchez à rien, c&amp;#8217;est comme avant&amp;#8221;, on prend le contenu d&amp;#8217;un serveur physique, on le met dans un serveur virtuel, et hop.
Sauf que la virtualisation (fort pratique pour simuler une autre architecture) est rapidement devenue de la para-virtualisation, bien plus performante, l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; invité collabore avec l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; hôte.
Cette collaboration est allé plus loin avec la création de matériel virtuel &lt;a href="https://blogs.oracle.com/linux/post/introduction-to-virtio"&gt;VirtIO&lt;/a&gt;, pour finalement aboutir aux machines virtuelles légères qui n&amp;#8217;utilisent quasi que du Virtio (
&lt;a href="https://github.com/kata-containers/kata-containers/tree/main/src/dragonball"&gt;DragonBall&lt;/a&gt;,
&lt;a href="https://github.com/firecracker-microvm/firecracker/blob/main/docs/design.md"&gt;FireCracker&lt;/a&gt;&amp;nbsp;…).&lt;/p&gt;
&lt;p&gt;Les conteneurs ont été conçus pour isoler une poignée de process (idéalement un).
À l&amp;#8217;arrivée des conteneurs, la même promesse a été faite : &lt;span class="caps"&gt;LXC&lt;/span&gt;, c&amp;#8217;est presque comme une machine virtuelle, avec un &lt;em&gt;init&lt;/em&gt; qui lance une grappe de services.
Docker a fait le ménage pédagogique en expliquant bien qu&amp;#8217;un conteneur va héberger un seul service.
Mais, pour pallier aux cas de process zombie, Docker a intégré &lt;a href="https://github.com/krallin/tini"&gt;tini&lt;/a&gt;, avec l&amp;#8217;option&amp;nbsp;(&lt;code&gt;--init&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Cette option est très peu utilisée, les applications métiers, même celles qui forkent des workers, savent gérer leur sous-process.
De plus, le dogme demande de laisser à Kubernetes le soin de gérer la mise à l&amp;#8217;échelle en multipliant les&amp;nbsp;instances.&lt;/p&gt;
&lt;h3 id="image-de-base"&gt;Image de&amp;nbsp;base&lt;/h3&gt;
&lt;p&gt;Docker a fait le choix d&amp;#8217;utiliser comme dossier racine un assemblage de pelures de systèmes de fichiers.
Seule la pelure du haut est modifiable.
Pour créer une image, on part d&amp;#8217;une image, et on ajoute des pelures.
Par convention, la première couche, vide, s&amp;#8217;appelle &lt;em&gt;&lt;span class="caps"&gt;SCRATCH&lt;/span&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Le meilleur moyen d&amp;#8217;appréhender une nouvelle abstraction est de commencer par faire comme avant, et donc d&amp;#8217;utiliser une &lt;a href="https://w3techs.com/technologies/details/os-linux"&gt;distribution Linux&lt;/a&gt;, en partant d&amp;#8217;un bootstrap (par ce que l&amp;#8217;installation complète depuis un &lt;span class="caps"&gt;CD&lt;/span&gt;, à la &lt;a href="https://www.packer.io/"&gt;Packer&lt;/a&gt;, ça va 5 minutes), puis d&amp;#8217;installer des&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Une image Docker est allégée, elle n&amp;#8217;est pas bootable et n&amp;#8217;a ni kernel, ni la ribambelle de services&amp;nbsp;systèmes.&lt;/p&gt;
&lt;p&gt;Docker a porté son choix d&amp;#8217;image de référence sur Debian (libre à vous d&amp;#8217;en faire d&amp;#8217;autres à partir de votre distribution favorite).
Ubuntu a deux fois plus de fan, propose de nouvelles versions à date fixe, mais fait régulièrement des choix polémiques.
Ubuntu étant basé sur Debian, et ses ajouts ne s&amp;#8217;aventurant que rarement dans les couches basses et les runtimes, ça ne change pas grand-chose.
Donc,&amp;nbsp;Debian.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;image Debian officielle (maintenue par Debian) est construite à partir de son système de paquets, via l&amp;#8217;outil &lt;em&gt;debootstrap&lt;/em&gt;, utilisé aussi pour créer des images disques pour des machines virtuelles.
Pour avoir des images reproductibles, &lt;em&gt;deboostrap&lt;/em&gt; est emballé par
&lt;a href="https://github.com/debuerreotype/debuerreotype"&gt;debuerreotype&lt;/a&gt;, qui en plus de la gestion de &lt;em&gt;snapshot&lt;/em&gt; va aussi appliquer différents paramétrages pour minimiser la taille de&amp;nbsp;l&amp;#8217;image.&lt;/p&gt;
&lt;p&gt;Avec une optimisation agressive (en virant la doc essentiellement), on gagne 30% de place, et on a une image dite &lt;strong&gt;slim&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Les images de langages (python, ruby, golang, node, rust…) sont basées sur les images Debian officielles.
Comme les images partagent leurs ancêtres, avec tout basé sur Debian, on mutualise pas mal de place en disque et réseau (lors du&amp;nbsp;déploiement).&lt;/p&gt;
&lt;p&gt;Les utilisateurs Docker se sont rendu compte qu&amp;#8217;en partant sur une distribution Alpine, distribution minimaliste prévue pour l&amp;#8217;embarqué, on obtient une image 10 fois plus petite, mais avec un système de paquet moins cohérent que Debian, une libc rikiki (&lt;a href="https://musl.libc.org/"&gt;musl&lt;/a&gt;) et busybox plutôt que les outils gnu.
Les images de langages proposent souvent une variante basée sur Alpine, en plus de&amp;nbsp;Debian.&lt;/p&gt;
&lt;p&gt;Pour construire l&amp;#8217;image de son application, on utilise souvent deux images : une image de construction, éphémère, avec les outils de développement, et une image d&amp;#8217;exécution qui finira en&amp;nbsp;production.&lt;/p&gt;
&lt;h2 id="images-minimalistes"&gt;Images&amp;nbsp;minimalistes&lt;/h2&gt;
&lt;p&gt;Pour limiter la surface d&amp;#8217;attaque, les faux positifs dans les analyses de versions de paquets, et la taille des images, la nouvelle tendance est d&amp;#8217;utiliser des images minimalistes.
Minimaliste pour ne pas dire &amp;#8220;à l&amp;#8217;os&amp;#8221;, ces images n&amp;#8217;ont pas de shell, et aucun&amp;nbsp;utilitaire.&lt;/p&gt;
&lt;p&gt;Debugs et tests ne se feront pas depuis l&amp;#8217;image, mais autour de&amp;nbsp;l&amp;#8217;image.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;application aura des tests unitaires, l&amp;#8217;image des tests fonctionnels et des tests de charges.
Les erreurs remonteront via les journaux et Sentry.
Les soucis de performances seront analysé par les traces, les sondes déployées en&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;Dans le pire du pire, un debug en preprod sera fait avec un conteneur sidecar, par ce que le conteneur de l&amp;#8217;application sans shell, ne permet pas de s&amp;#8217;y connecter avec&amp;nbsp;un &lt;code&gt;docker exec&lt;/code&gt;.
&lt;a href="https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/"&gt;Kubernetes permet de se connecter à une instance (un pod)&lt;/a&gt; mais je ne suis pas sûr que ce soit une bonne idée de le faire en&amp;nbsp;production.&lt;/p&gt;
&lt;h3 id="distroless"&gt;Distroless&lt;/h3&gt;
&lt;p&gt;Google a exploré une piste minimaliste pour construire des images dites &amp;#8220;runtime&amp;#8221; avec différents critères&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;basé sur une distribution connue, pour la confiance et la correction des&amp;nbsp;CVEs.&lt;/li&gt;
&lt;li&gt;les applications apprécient d&amp;#8217;utiliser les dernières mises à jour (stables) de leur langage, ce que ne peut fournir une distribution qui a besoin de la valider avec une brouette de&amp;nbsp;paquets.&lt;/li&gt;
&lt;li&gt;en se basant sur Debian, on peut construire l&amp;#8217;application avec l&amp;#8217;image de langage officielle, elle aussi basée sur Debian, pour ensuite déposer le tout dans une image&amp;nbsp;&amp;#8220;runtime&amp;#8221;.&lt;/li&gt;
&lt;li&gt;Les images doivent pouvoir être construites rapidement, avec du cache efficace, sans réclamer un utilisateur&amp;nbsp;privilégié.&lt;/li&gt;
&lt;li&gt;Différentes architectures de processeurs doivent pouvoir être gérées, sans émulation ni &lt;span class="caps"&gt;CI&lt;/span&gt; sur différentes architectures de&amp;nbsp;serveurs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="explorer-une-image-sans-shell"&gt;Explorer une image sans&amp;nbsp;shell&lt;/h4&gt;
&lt;p&gt;Voici une petite recette, qui sent le vestiaire, pour aller visiter une image sans shell.
Commencez par voler&amp;nbsp;un &lt;code&gt;busybox&lt;/code&gt;, un bon petit binaire statique qui sert à bricoler dans des Linux trop&amp;nbsp;petits.&lt;/p&gt;
&lt;p&gt;Comme distroless est basé sur Debian, le vol&amp;nbsp;de &lt;code&gt;busybox&lt;/code&gt; se fera à partir d&amp;#8217;une Debian de la même&amp;nbsp;version.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Préparez un volume pour accueillir le larcin&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;/tmp/tools
&lt;span class="c1"&gt;# Et branchez le dans votre bonne vieille Debian&lt;/span&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-ti&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/tmp/tools:/tools&lt;span class="w"&gt; &lt;/span&gt;debian:12-slim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Vous êtes dans le conteneur.&lt;/span&gt;
&lt;span class="c1"&gt;# Un conteneur n&amp;#39;a pas d&amp;#39;index apt, ça prend de la place et c&amp;#39;est tout le temps périmé&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;update
&lt;span class="c1"&gt;# On va éventrer le paquet, autant faire ça dans un endroit tranquille&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp
&lt;span class="c1"&gt;# En suivant la voie de Debian, téléchargez le paquet dans sa version courante&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;download&lt;span class="w"&gt; &lt;/span&gt;busybox-static
&lt;span class="c1"&gt;# dpkg va ouvrir le paquet, pas moyen de le faire à la main, une debian-slim n&amp;#39;a pas l&amp;#39;application ar&lt;/span&gt;
dpkg-deb&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;busybox-static_*.deb&lt;span class="w"&gt; &lt;/span&gt;.
&lt;span class="c1"&gt;# Recopiez le binaire dans le volume monté&lt;/span&gt;
mv&lt;span class="w"&gt; &lt;/span&gt;bin/busybox&lt;span class="w"&gt; &lt;/span&gt;/tools/
&lt;span class="c1"&gt;# Au revoir&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous pouvez maintenant lancer une image ascétique en&amp;nbsp;montant &lt;code&gt;busybox&lt;/code&gt; dans le &lt;span class="caps"&gt;PATH&lt;/span&gt;, comme le&amp;nbsp;dossier &lt;code&gt;/usr/local/bin&lt;/code&gt; prévu pour accueillir du bazar, n&amp;#8217;existe pas dans l&amp;#8217;image, ce sera&amp;nbsp;donc &lt;code&gt;usr/bin&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-ti&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/tmp/tools/busybox:/usr/bin/busybox&lt;span class="w"&gt; &lt;/span&gt;gcr.io/distroless/static-debian12&lt;span class="w"&gt; &lt;/span&gt;busybox&lt;span class="w"&gt; &lt;/span&gt;sh


BusyBox&lt;span class="w"&gt; &lt;/span&gt;v1.35.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Debian&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;:1.35.0-4+b3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;built-in&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;ash&lt;span class="o"&gt;)&lt;/span&gt;
Enter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;help&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;built-in&lt;span class="w"&gt; &lt;/span&gt;commands.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="bazel"&gt;Bazel&lt;/h4&gt;
&lt;p&gt;Distroless utilise &lt;a href="https://bazel.build/"&gt;Bazel&lt;/a&gt;, un outil de build très agressif sur la parallélisation et le cache (en utilisant un graphe de dépendance).
Bazel est la version open source de Blaze, outil interne de Google, libéré en&amp;nbsp;2015.&lt;/p&gt;
&lt;p&gt;Bazel utilise un dialecte spécifique, &lt;a href="https://github.com/bazelbuild/starlark"&gt;Starlack&lt;/a&gt; (basé sur la syntaxe de Python), mais n&amp;#8217;hésite pas à utiliser des outils maison (évidemment compilé par&amp;nbsp;Bazel).&lt;/p&gt;
&lt;h4 id="gestion-des-paquets-debian"&gt;Gestion des paquets&amp;nbsp;Debian&lt;/h4&gt;
&lt;p&gt;Distroless fait le choix de faire les images sans utiliser de conteneur, ni les outils de gestion de paquets Debian.
En travaillant directement avec les paquets (on pourrait ricaner en parlant de containerless), plus de soucis de multi architecture, ni même d&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; hôte, le build sera le même depuis un Mac ou un Windows (oui, le build sera fait depuis une &lt;span class="caps"&gt;CI&lt;/span&gt;, dans un conteneur, comme tout le&amp;nbsp;monde).&lt;/p&gt;
&lt;p&gt;Distroless utilise &lt;a href="https://github.com/GoogleContainerTools/distroless/tree/main/debian_package_manager"&gt;son propre module golang pour gérer des paquets Debian&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il y a un sous-module (peu palpitant), &lt;em&gt;build&lt;/em&gt; qui va permettre à &lt;a href="https://github.com/bazelbuild/bazel-gazelle"&gt;gazelle&lt;/a&gt; d&amp;#8217;écrire des fichiers &lt;em&gt;bzl&lt;/em&gt; avec les détails sur les paquets à&amp;nbsp;utiliser.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;autre sous-module, &lt;a href="https://github.com/GoogleContainerTools/distroless/tree/main/debian_package_manager/internal/deb"&gt;deb&lt;/a&gt;, est le plus intéressant, il gère les debianeries pour récupérer, entre autres, la liste de&amp;nbsp;paquets.&lt;/p&gt;
&lt;h5 id="instantanes-debian"&gt;Instantanés&amp;nbsp;Debian&lt;/h5&gt;
&lt;p&gt;Debian fournit un système d&amp;#8217;instantanés pour retrouver les versions des paquets à une date donnée.
Le site &lt;a href="https://snapshot.debian.org/"&gt;snapshot.debian.org&lt;/a&gt; explique son fonctionnement (on notera l&amp;#8217;architecture moderniste : 2 sponsors fournissent un serveur apache+varnish avec une application Flask en python 3 derrière un &lt;em&gt;load balancing&lt;/em&gt; &lt;span class="caps"&gt;DNS&lt;/span&gt;, et du certificat &lt;span class="caps"&gt;LE&lt;/span&gt;. Par contre, c&amp;#8217;est 135 To de stockage, pour un historique depuis&amp;nbsp;2005).&lt;/p&gt;
&lt;h6 id="chercher-un-instantane-pas-trop-vieux-avec-la-liste-des-paquets"&gt;Chercher un instantané pas trop vieux avec la liste des&amp;nbsp;paquets&lt;/h6&gt;
&lt;p&gt;&lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/debian_package_manager/internal/deb/snapshot.go"&gt;deb/snapshot.go&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Il suffit d&amp;#8217;aller&amp;nbsp;sur &lt;code&gt;https://snapshot.debian.org/archive/debian/?year=%d&amp;amp;month=%d&lt;/code&gt; puis de fouiller dans le &lt;span class="caps"&gt;HTML&lt;/span&gt; l&amp;#8217;expression&amp;nbsp;régulière &lt;code&gt;[0-9]+T[0-9]+Z&lt;/code&gt;.
On notera qu&amp;#8217;il peut y avoir des jours sans snapshots, et plusieurs snapshots sur une journée, avec des horaires&amp;nbsp;aléatoires.&lt;/p&gt;
&lt;p&gt;Le commentaire dans le code précise que parfois, le dernier instantané est incomplet, et qu&amp;#8217;il suffit de prendre le précédent.
De toutes façon, le code cherche un snapshot qui a plus de deux jours (ce délai est codé en&amp;nbsp;dur).&lt;/p&gt;
&lt;p&gt;On peut ensuite aller&amp;nbsp;sur &lt;code&gt;https://snapshot.debian.org/archive/debian/%s/dists/%s/main/binary-%s/Packages.xz&lt;/code&gt; avec le &lt;em&gt;snapshot&lt;/em&gt;, la &lt;em&gt;version&lt;/em&gt; (bookworm pour la version courante) puis l&amp;#8217;&lt;em&gt;architecture&lt;/em&gt;, pour connaitre l&amp;#8217;&lt;span class="caps"&gt;URL&lt;/span&gt; de la liste des&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Ça, c&amp;#8217;est pour &lt;em&gt;main&lt;/em&gt;, Debian utilise plusieurs canaux, il faut recommencer pour &lt;em&gt;updates&lt;/em&gt; et &lt;em&gt;security&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;On n&amp;#8217;est pas tout à fait à l&amp;#8217;état de l&amp;#8217;art comme &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt;, mais bon, avoir accès à tous les instantanés des différentes Debian garanti une reproductibilité et un retour en arrière&amp;nbsp;possible.&lt;/p&gt;
&lt;h6 id="lister-les-paquets-dun-instantane"&gt;Lister les paquets d&amp;#8217;un&amp;nbsp;instantané&lt;/h6&gt;
&lt;p&gt;&lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/debian_package_manager/internal/deb/parser.go"&gt;deb/parser.go&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Le format de liste de paquets Debian est plutôt carré, sauf que le parser prévoit un buffer de 1Mo pour lire des lignes trop&amp;nbsp;longues.&lt;/p&gt;
&lt;h6 id="nomenclature-des-versions-de-paquet-debian"&gt;Nomenclature des versions de paquet&amp;nbsp;Debian&lt;/h6&gt;
&lt;p&gt;&lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/debian_package_manager/internal/deb/versions.go"&gt;deb/versions.go&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Debian utilise une nomenclature personnelle pour les &lt;a href="https://www.debian.org/doc/debian-policy/ch-controlfields.html#version"&gt;versions de paquets&lt;/a&gt;, prenant en compte la version du paquet source et un versionnage des correctifs appliqués par le mainteneur.
Comme on travaille avec 3 listes de paquets, il faut être capable de trier les versions pour trouver la plus&amp;nbsp;récente.&lt;/p&gt;
&lt;h5 id="format-de-paquet"&gt;Format de&amp;nbsp;paquet&lt;/h5&gt;
&lt;p&gt;Les paquets Debian utilisent un format antédiluvien, &lt;a href="https://en.wikipedia.org/wiki/Ar_(Unix)"&gt;ar&lt;/a&gt;.
La&amp;nbsp;commande &lt;code&gt;ar&lt;/code&gt; fait parti de &lt;span class="caps"&gt;GNU&lt;/span&gt; binutils, mais la &lt;a href="https://en.wikipedia.org/wiki/Linux_Standard_Base"&gt;Linux Standard Base&lt;/a&gt; l&amp;#8217;a déprécié et il sera bientôt&amp;nbsp;retiré.&lt;/p&gt;
&lt;p&gt;Chose intéressante, le tar de &lt;span class="caps"&gt;BSD&lt;/span&gt; (celui disponible sur MacOS) sait lire des archives &lt;em&gt;ar&lt;/em&gt;, et c&amp;#8217;est avec &lt;a href="https://github.com/GoogleContainerTools/rules_distroless/blob/main/distroless/private/locale.bzl"&gt;cet outil que Bazel dépiaute les paquets Debian&lt;/a&gt; dans la règle étrangement nommée &lt;a href="https://github.com/GoogleContainerTools/rules_distroless/blob/main/docs/rules.md#locale"&gt;locale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour installer (comme une brute) un paquet Debian dans une image de conteneur, il suffit d&amp;#8217;ouvrir le paquet (au format &lt;em&gt;ar&lt;/em&gt;), de récupérer les données (l&amp;#8217;archive &lt;em&gt;tar&lt;/em&gt;, &lt;em&gt;data.tar.xz&lt;/em&gt;), de &lt;a href="https://github.com/GoogleContainerTools/distroless/blob/f55b2b343481f52ef3bde34c8a2f3631a40a36c1/private/pkg/dpkg_status.go"&gt;créer le fichier status qui se retrouvera&amp;nbsp;dans &lt;code&gt;/var/lib/dpkg/status.d&lt;/code&gt;&lt;/a&gt; et d&amp;#8217;en faire un &lt;em&gt;tar&lt;/em&gt;, qui sera ajouté à votre image de base.
On notera qu&amp;#8217;à la différence des images debian-slim, la documentation que l&amp;#8217;on trouve&amp;nbsp;dans &lt;code&gt;usr/share/doc&lt;/code&gt; est&amp;nbsp;conservé.&lt;/p&gt;
&lt;p&gt;En maintenant la base &lt;em&gt;dpkg/status&lt;/em&gt;, les outils listant les versions, comme &lt;a href="https://github.com/anchore/syft"&gt;Syft&lt;/a&gt; fonctionnent sans&amp;nbsp;surprises.&lt;/p&gt;
&lt;p&gt;Ce genre d&amp;#8217;installation ne prendra pas en compte &lt;a href="https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#"&gt;les déclencheurs du paquet&lt;/a&gt; (&lt;em&gt;preinst&lt;/em&gt; et &lt;em&gt;postinst&lt;/em&gt;), mais bon, l&amp;#8217;image Debian Docker officielle &lt;a href="https://github.com/debuerreotype/debuerreotype/blob/master/scripts/debuerreotype-minimizing-config"&gt;brime aussi le paquet en les empêchant de lancer/relancer des services&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Distroless fournit des images de base spécialisées (node, java, python…) pour faire tourner du code métier, il ne se charge même pas de la compilation du code, et donc à part quelques bibliothèques, vous n&amp;#8217;allez pas installer grand-chose (car tout est dans votre dossier de &lt;em&gt;vendoring&lt;/em&gt;).&lt;/p&gt;
&lt;h5 id="les-couches-dune-image-conteneur"&gt;Les couches d&amp;#8217;une image&amp;nbsp;conteneur&lt;/h5&gt;
&lt;p&gt;Bazel a décidé de bouder Docker en archivant &lt;a href="https://github.com/bazelbuild/rules_docker"&gt;rules_docker&lt;/a&gt;, pour mettre en avant le format d&amp;#8217;image &lt;span class="caps"&gt;OCI&lt;/span&gt; avec &lt;a href="https://github.com/bazel-contrib/rules_oci"&gt;rules_oci&lt;/a&gt;, et en disant quelques méchancetés sur son défunt concurrent.
Docker utilise les images &lt;span class="caps"&gt;OCI&lt;/span&gt; de manière transparente, ce n&amp;#8217;est donc pas très grave, et Distroless fournit des exemples de &lt;em&gt;Dockerfile&lt;/em&gt; utilisant ses&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Distroless assemble ses images &lt;span class="caps"&gt;OCI&lt;/span&gt; à partir d&amp;#8217;&lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L81-L95"&gt;une liste de tar&lt;/a&gt;, certains crée à partir de paquets Debian, d&amp;#8217;autre sont générés (et tous sont&amp;nbsp;cachés).&lt;/p&gt;
&lt;p&gt;Des tests sont effectués avec &lt;a href="https://github.com/GoogleContainerTools/container-structure-test"&gt;container-structure-test&lt;/a&gt;, un simple outil en ligne de commande, qui va savoir travailler avec une liste de tar pour vérifier la présence/absence d&amp;#8217;un fichier, ou avec docker, pour lancer des commandes.
Les paranoïaques ont la possibilité d&amp;#8217;utiliser &lt;a href="https://gvisor.dev/"&gt;gVisor&lt;/a&gt; pour lancer des commandes douteuses dans des&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;p&gt;Les images sont créées pour chacune des architectures, puis &lt;a href="https://github.com/bazel-contrib/rules_oci/blob/31521dfd143e52342ac917303661abe2d2bb424f/oci/private/image_index.bzl"&gt;rassemblées par &lt;em&gt;rules_oci&lt;/em&gt; pour en faire une image multiarchitectures&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id="inventaire"&gt;Inventaire&lt;/h5&gt;
&lt;p&gt;L&amp;#8217;administration américaine demande d&amp;#8217;utiliser des &lt;span class="caps"&gt;SBOM&lt;/span&gt; (Software Bill Of Material), et il existe différents formats dont &lt;a href="https://spdx.dev/"&gt;&lt;span class="caps"&gt;SPDX&lt;/span&gt; (Software Package Data Exchange)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://wiki.debian.org/SPDX"&gt;Debian s&amp;#8217;intéresse à &lt;span class="caps"&gt;SPDX&lt;/span&gt;&lt;/a&gt;, surtout pour &lt;a href="https://wiki.debian.org/CopyrightReviewTools"&gt;gérer les histoires de licence&lt;/a&gt; (tout comme le projet &lt;a href="https://reuse.software/spec/"&gt;reuse&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Distroless a un outil pour créer une entrée &lt;span class="caps"&gt;SPDX&lt;/span&gt; à partir d&amp;#8217;un paquet Debian : &lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/private/pkg/debian_spdx.go"&gt;debian_spdx&lt;/a&gt;.
À partir de la liste de paquets demandés pour créer une image, il est facile de créer un &lt;span class="caps"&gt;SBOM&lt;/span&gt; spdx : &lt;a href="https://github.com/GoogleContainerTools/distroless/blob/main/private/pkg/oci_image_spdx.go"&gt;oci_image_spdx&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dans une première itération, il était possible de pousser les &lt;a href="https://github.com/sigstore/cosign/blob/main/specs/SBOM_SPEC.md"&gt;&lt;span class="caps"&gt;SBOM&lt;/span&gt; specifications&lt;/a&gt; dans un registre &lt;span class="caps"&gt;OCI&lt;/span&gt;, mais rien ne garantit la cohérence entre cette spécification et l&amp;#8217;image.
Il est maintenant recommandé d&amp;#8217;utiliser une &lt;a href="https://github.com/in-toto/attestation"&gt;Attestation spec&lt;/a&gt; qui utilise le &lt;a href="https://github.com/in-toto/attestation"&gt;format d&amp;#8217;attestation spécifié par in-toto&lt;/a&gt;, et une&amp;nbsp;signature.&lt;/p&gt;
&lt;p&gt;Par convention, Docker puis &lt;span class="caps"&gt;OCI&lt;/span&gt;, stockent la description de l&amp;#8217;image à un endroit, le &lt;em&gt;manifest&lt;/em&gt;, qui va désigner des &lt;em&gt;blobs&lt;/em&gt;.
Les &lt;em&gt;blobs&lt;/em&gt; sont immuables et nommés à partir de leur hachage, ce qui permet d&amp;#8217;avoir un cache efficace, et de ne télécharger que les &lt;em&gt;blobs&lt;/em&gt; manquants.
Un &lt;em&gt;blob&lt;/em&gt; est souvent la couche d&amp;#8217;une image, mais le système reste ouvert sur le contenu d&amp;#8217;un &lt;em&gt;blob&lt;/em&gt;.&lt;/p&gt;
&lt;h6 id="demo-time"&gt;Demo&amp;nbsp;time&lt;/h6&gt;
&lt;p&gt;Prenons comme&amp;nbsp;exemple &lt;code&gt;gcr.io/distroless/static-debian12:latest&lt;/code&gt;, la fameuse image de moins de&amp;nbsp;2Mo.&lt;/p&gt;
&lt;p&gt;La convention de nommage permet de construire l&amp;#8217;url de son &lt;em&gt;manifest&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Si dans le nom d&amp;#8217;une image il y a un ou deux éléments séparés par des slash, elle sera sur le hub Docker, si il y a 3 éléments, le premier désigne le hub,&amp;nbsp;ici &lt;code&gt;gcr.io&lt;/code&gt; (pour Google Container Registry je&amp;nbsp;présume).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;v2&lt;/code&gt; est la version de l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;distroless&lt;/code&gt; est le&amp;nbsp;groupe.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;static-debian12&lt;/code&gt; est&amp;nbsp;l&amp;#8217;image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;manifests&lt;/code&gt; par ce que je souhaite connaître la description de&amp;nbsp;l&amp;#8217;image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;latest&lt;/code&gt; est le tag par&amp;nbsp;défaut.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/manifests/latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;schemaVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.index.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manifests&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:285af5683714393b2702463eb92bf1290bb12401a265c2a8942ebd79ac1ac673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;architecture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;amd64&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;architecture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;arm64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;variant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v8&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:60f84cc8a1e4afa08c901b307b2aba8ae34ee088b92659114038660840eeac4a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;architecture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;arm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;variant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v7&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:3791cb6581d68b72be439062377c67e16a21110cf68d9114e5384a510cacc159&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;architecture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s390x&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:8edfd8cf3c82045c05763cb2d25c0f421ccce218b69440190870a5d821fb8f95&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;platform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;architecture&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ppc64le&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;application/vnd.oci.image.index.v1+json&lt;/code&gt; précise que le format &lt;span class="caps"&gt;OCI&lt;/span&gt; pour décrire les index d&amp;#8217;image est utilisé (et non le format&amp;nbsp;Docker).&lt;/p&gt;
&lt;p&gt;Il y a plusieurs &lt;em&gt;manifests&lt;/em&gt; car l&amp;#8217;image est multi&amp;nbsp;architectures.&lt;/p&gt;
&lt;p&gt;Prenons l&amp;#8217;image arm64, par ce que le arm64, c&amp;#8217;est chic, et allons chercher son &lt;em&gt;manifest&lt;/em&gt;.
Pour ne pas risquer une confusion avec un tag, on préfixe avec le type de hachage. On notera que cette fois ci, le json n&amp;#8217;est pas indenté, tache dont s&amp;#8217;acquitte très&amp;nbsp;bien &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/manifests/sha256:7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.config.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1870&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:1dcec56bafe5a886201f3011e859f9e95086aee5e19faf08fcbbd3ae46de18f4&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;layers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:8398841c83114be91a2fb43e384042528a6d82580f0ff857997dfa7090bda899&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104183&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:2b776ada03417eaa87102a617f964324df1de8967698fc4209dc1a1fbdfae8cd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13384&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:2a977872b36c1a2309fac8bd22b0fa0b3ee6efd1a25af5016ee9409beca1b3cf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;537713&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:b6824ed73363f94b3b2b44084c51c31bc32af77a96861d49e16f91e3ab6bed71&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:7c12895b777bcaa8ccae0605b4de635b68fc32d60fa08f421dc3818bf55ee212&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;188&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:33e068de264953dfdc9f9ada207e76b61159721fd64a4820b320d05133a55fb8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:5664b15f108bf9436ce3312090a767300800edbbfd4511aa1a6d64357024d5dd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;168&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:27be814a09ebd97fac6fb7b82d19f117185e90601009df3fbab6f442f85cd6b3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;93&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;385&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:da7816fa955ea24533c388143c78804c28682eef99b4ee3723b548c70148bba6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;321&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:9aee425378d2c16cd44177dc54a274b312897f5860a8e78fdfda555a0d79dd71&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.layer.v1.tar+gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;130495&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;schemaVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cette fois ci, on a des couches d&amp;#8217;image &lt;span class="caps"&gt;OCI&lt;/span&gt; sous forme&amp;nbsp;de &lt;code&gt;tar&lt;/code&gt; compressé&amp;nbsp;avec &lt;code&gt;gzip&lt;/code&gt; : &lt;code&gt;application/vnd.oci.image.layer.v1.tar+gzip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La description des couches se trouve dans &lt;a href="https://github.com/GoogleContainerTools/distroless/blob/a019fc244dc1fe857c7623f444413fc68ee351fb/base/base.bzl#L81-L95"&gt;base/base.bzl&lt;/a&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;tars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;deb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;base-files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;deb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;netbase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;deb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tzdata&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:rootfs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:passwd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:home&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:group&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;too&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;things&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assume&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;correct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;01777&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:tmp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:nsswitch.tar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:os_release_&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;//common:cacerts_&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On notera que les couches toutes petites contiennent de la conf, et sont les mêmes, indépendamment de l&amp;#8217;architecture.
Bricolez la commande curl pour le vérifier, c&amp;#8217;est&amp;nbsp;instructif.&lt;/p&gt;
&lt;p&gt;Une convention de nommage, un peu crado, permet de créer l&amp;#8217;url de l&amp;#8217;attestation d&amp;#8217;une image : il faut&amp;nbsp;ajouter &lt;code&gt;.att&lt;/code&gt; à la fin, et surtout remplacer&amp;nbsp;le &lt;code&gt;:&lt;/code&gt; qui sépare le type de hachage de sa valeur, par&amp;nbsp;un &lt;code&gt;-&lt;/code&gt;.
Une convention similaire permet de trouver la signature, avec le&amp;nbsp;suffixe &lt;code&gt;.sig&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/manifests/sha256-7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c.att&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;schemaVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.manifest.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.oci.image.config.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;243&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:1f3ca242c4197b2abe2db3f9d162b4719fccd6261e2b45819c9d51361f340b8a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;layers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mediaType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.dsse.envelope.v1+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12944&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;annotations&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dev.cosignproject.cosign/signature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dev.sigstore.cosign/bundle&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{\&amp;quot;SignedEntryTimestamp\&amp;quot;:\&amp;quot;MEUCIAz/DSGCZlu0Zm/sN4BJf4MmEzLnZFANmlHy50k8I/bhAiEAj8keEKbxX/VYZAfjZIsgbMkGDRlo+yO+j/3Skmgh/qY=\&amp;quot;,\&amp;quot;Payload\&amp;quot;:{\&amp;quot;body\&amp;quot;:\&amp;quot;eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjNDJlZjJiNmZhOWUzZDE1YWFkMTk4YzIzYTE0N2JjMmZiYzQ3NGFmM2M2MDdmZmUwNWVmNWU5MzRjYjdkOTc3In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNDU1OTc4MDgyNDU0OGRlNDM5NzdlZDJhN2Q2NGE2YWQ1MDA4OTgwZTcxY2Q5ZDBmN2Y2NzBmMTU2YWM3MjIyYyJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTTBla05EUVcxcFowRjNTVUpCWjBsVlUyNWxaVFYyWTBSUVIzSnpTa2h4SzIxcmFVUTBjbTVRWkdjd2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDE2UlhoTlZHTXhUMVJGTUZkb1kwNU5hbEYzVFhwRmVFMVVaM2RQVkVVd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVU0ZFM5SmVrMHZkVlp5YjB4cGFGTmpiVzQzV1dKaGJuRkZVVWxyVWpKeU9HNDRXbFVLY1ZsbVJrTmFaaTlaV1ZoT1RtdDBMMDh6VEVvcmJURmpTRlpJWjBoR2IwVlNZamxVZVdrd1pUaGpXRGRTZGxGblZqWlBRMEZaWTNkblowZEVUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZPU3k4d0NrcE9SR1V4TUc5SU5VUjZhMGRPVW5sT1oyMU1abUpOZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDA5QldVUldVakJTUVZGSUwwSkROSGRNU1VWeFlUSldOV0pIVm5wak1FSnJZVmhPTUdOdE9YTmFXRTU2VEcxc2FHSlROVzVqTWxaNVpHMXNhZ3BhVjBacVdUSTVNV0p1VVhWWk1qbDBUVU5yUjBOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyQ2xveWVHeE1iVTUyWWxSQmNrSm5iM0pDWjBWRlFWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd3S1RHMU9kbUpVUTBKcFVWbExTM2RaUWtKQlNGZGxVVWxGUVdkU04wSklhMEZrZDBJeFFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5Bb3phbmwwTHpSbFMyTnZRWFpMWlRaUFFVRkJRbXBwTm5KTlExRkJRVUZSUkVGRldYZFNRVWxuU1ZSWFkwOU5VbWhJVUdScmJtcEdZV2wwWVZaUlJ5OTVDbFZuVFVSR05XRTNlVWxWT1hSaEsya3pRbEZEU1VoMUwxcHRZemRLY0hSSmJ6bDFNRGxKWjBKd2VEUXdhMFYyT1ZjMk9XOU1NRU5sU2xKcmNucEhkSGdLVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5YTBGTlIxbERUVkZEVDJob2NUZGhMM1F2TXpKS05EWnZMMmx0WmtjcmVIWkhlbEp0UWxWQllubFFjbVJFYXdwT1dVRnVVa3gwYVZoWWRHaFRhRkJHTTAxc1NtdGtaRmR4YjJ0RFRWRkRUR05uUVRNdlIycFRTakJvUkVSeE9GQkNNMnBvWkcwM1pFbHBhMlF6TUdGMUNrSTVNMVpFUzJGSWJXNWpjSEIwTW05a2NucElWRkU1U21sNE4zUnBUMmM5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19\&amp;quot;,\&amp;quot;integratedTime\&amp;quot;:1710179954,\&amp;quot;logIndex\&amp;quot;:77294834,\&amp;quot;logID\&amp;quot;:\&amp;quot;c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\&amp;quot;}}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dev.sigstore.cosign/certificate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-----BEGIN CERTIFICATE-----\nMIIC4zCCAmigAwIBAgIUSnee5vcDPGrsJHq+mkiD4rnPdg0wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwMzExMTc1OTE0WhcNMjQwMzExMTgwOTE0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE8u/IzM/uVroLihScmn7YbanqEQIkR2r8n8ZU\nqYfFCZf/YYXNNkt/O3LJ+m1cHVHgHFoERb9Tyi0e8cX7RvQgV6OCAYcwggGDMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUNK/0\nJNDe10oH5DzkGNRyNgmLfbMwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj\nZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29v\nZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xl\nLmNvbTCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl64\n3jyt/4eKcoAvKe6OAAABji6rMCQAAAQDAEYwRAIgITWcOMRhHPdknjFaitaVQG/y\nUgMDF5a7yIU9ta+i3BQCIHu/Zmc7JptIo9u09IgBpx40kEv9W69oL0CeJRkrzGtx\nMAoGCCqGSM49BAMDA2kAMGYCMQCOhhq7a/t/32J46o/imfG+xvGzRmBUAbyPrdDk\nNYAnRLtiXXthShPF3MlJkddWqokCMQCLcgA3/GjSJ0hDDq8PB3jhdm7dIikd30au\nB93VDKaHmncppt2odrzHTQ9Jix7tiOg=\n-----END CERTIFICATE-----\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dev.sigstore.cosign/chain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-----BEGIN CERTIFICATE-----\nMIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C\nAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7\n7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS\n0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB\nBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp\nKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI\nzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR\nnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP\nmygUY7Ii2zbdCdliiow=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7\nXeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex\nX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j\nYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY\nwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ\nKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM\nWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9\nTNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\n-----END CERTIFICATE-----&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;predicateType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://spdx.dev/Document&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L&amp;#8217;attestation contient&amp;nbsp;des &lt;code&gt;layers&lt;/code&gt;, comme une image, il ne faut pas se laisser distraire par la quantité d&amp;#8217;annotations utilisée par la signature de &lt;em&gt;cosign&lt;/em&gt;.
Si il y a&amp;nbsp;des &lt;code&gt;layers&lt;/code&gt;, c&amp;#8217;est qu&amp;#8217;il y a des blobs (un layer et un blob, en&amp;nbsp;l&amp;#8217;occurrence).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;payloadType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/vnd.in-toto+json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;payload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5c&lt;/span&gt;
&lt;span class="s2"&gt;  […]&lt;/span&gt;
&lt;span class="s2"&gt;  uc2hpcFR5cGVcIjpcIkRFUEVORFNfT05cIn1dfSJ9&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;signatures&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;keyid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sig&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MEUCIEdF83LnL01ToM4pkDvgnMkqvzRHwT3viDb/bOhqYA1JAiEAlgZR0Lei6lng9itoHr/Iw1xMllI67DngGtyg0o+hP4A=&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On a un document &lt;em&gt;in-toto&lt;/em&gt; avec un gros paté en base64 (que je n&amp;#8217;affiche que partiellement), que &lt;em&gt;jq&lt;/em&gt; va traiter pour&amp;nbsp;nous.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.payload|@base64d|fromjson&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On obtient un prédicat de&amp;nbsp;type &lt;code&gt;https://spdx.dev/Document&lt;/code&gt;, le sujet qui désigne l&amp;#8217;image, avec un hash pour préciser la version.
Le corps du prédicat, qui contient le &lt;span class="caps"&gt;SPDX&lt;/span&gt; attendu est sous forme de json dans du json (sans base64 cette fois&amp;nbsp;ci).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.payload|@base64d|fromjson|.predicate|fromjson&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDX-2.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dataLicense&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CC0-1.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-DOCUMENT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;//base:static_root_arm64_debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;documentNamespace&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://spdx.org/spdxdocs/distroless/-slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;creationInfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;licenseListVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;creators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Organization: distroless&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1970-01-01T00:00:00Z&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;packages&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;//base:static_root_arm64_debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;base-files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-base-files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;versionInfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;12.4+deb12u5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;supplier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person: Santiago Vila \\u003csanvila@debian.org\\u003e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/b/base-files/base-files_12.4+deb12u5_arm64.deb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;algorithm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SHA256&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksumValue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ca5e69b38214de267d7d59bf4d0c1abd10987abacb5c9bfaf72b178bee883d1b&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This is the Debian prepackaged version of the Debian Base System\nMiscellaneous files. These files were written by Ian Murdock\n&amp;lt;imurdock@debian.org&amp;gt; and Bruce Perens &amp;lt;bruce@pixar.com&amp;gt;.\n\nThis package was first put together by Bruce Perens &amp;lt;Bruce@Pixar.com&amp;gt;,\nfrom his own sources.\n\nThe GNU Public Licenses in /usr/share/common-licenses were taken from\nftp.gnu.org and are copyrighted by the Free Software Foundation, Inc.\n\nThe Artistic License in /usr/share/common-licenses is the one coming\nfrom Perl and its SPDX name is \&amp;quot;Artistic License 1.0 (Perl)\&amp;quot;.\n\n\nCopyright (C) 1995-2011 Software in the Public Interest.\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nOn Debian systems, the complete text of the GNU General\nPublic License can be found in `/usr/share/common-licenses/GPL&amp;#39;.\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Debian base system miscellaneous files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Debian base system miscellaneous files\nThis package contains the basic filesystem hierarchy of a Debian system, and\nseveral important miscellaneous files, such as /etc/debian_version,\n/etc/host.conf, /etc/issue, /etc/motd, /etc/profile, and others,\nand the text of several common licenses in use on Debian systems.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;externalRefs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceCategory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PACKAGE-MANAGER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;purl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceLocator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pkg:deb/debian/base-files@12.4+deb12u5?arch=arm64&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@arm64_debian12_base-files//:spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generated from base-files@12.4+deb12u5&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;netbase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-netbase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;versionInfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;6.4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;supplier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person: Marco d&amp;#39;Itri \\u003cmd@linux.it\\u003e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/n/netbase/netbase_6.4_all.deb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;algorithm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SHA256&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksumValue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;29b23c48c0fe6f878e56c5ddc9f65d1c05d729360f3690a593a8c795031cd867&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nComment:\n This package was created by Peter Tobias tobias@et-inf.fho-emden.de on\n Wed, 24 Aug 1994 21:33:28 +0200 and maintained by Anthony Towns\n &amp;lt;ajt@debian.org&amp;gt; until 2001.\n It is currently maintained by Marco d&amp;#39;Itri &amp;lt;md@linux.it&amp;gt;.\n\nFiles: *\nCopyright:\n Copyright (c) 1994-1998 Peter Tobias\n Copyright (c) 1998-2001 Anthony Towns\n Copyright (c) 2002-2022 Marco d&amp;#39;Itri\nLicense: GPL-2\n This program is free software; you can redistribute it and/or modify\n it under the terms of the GNU General Public License, version 2, as\n published by the Free Software Foundation.\n .\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU General Public License for more details.\n .\n You should have received a copy of the GNU General Public License along\n with this program; if not, write to the Free Software Foundation,\n Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n .\n On Debian systems, the complete text of the GNU General Public License\n version 2 can be found in &amp;#39;/usr/share/common-licenses/GPL-2&amp;#39;.\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Basic TCP/IP networking system&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Basic TCP/IP networking system\nThis package provides the necessary infrastructure for basic TCP/IP based\nnetworking.\n.\nIn particular, it supplies common name-to-number mappings in /etc/services,\n/etc/rpc, /etc/protocols and /etc/ethertypes.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;externalRefs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceCategory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PACKAGE-MANAGER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;purl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceLocator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pkg:deb/debian/netbase@6.4?arch=all&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@arm64_debian12_netbase//:spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generated from netbase@6.4&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tzdata&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-tzdata&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;versionInfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024a-0+deb12u1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;supplier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person: GNU Libc Maintainers \\u003cdebian-glibc@lists.debian.org\\u003e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/t/tzdata/tzdata_2024a-0+deb12u1_all.deb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;algorithm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SHA256&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;checksumValue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0ca0baec1fca55df56039047a631fc1541c5a44c1c4879d553aaa3a70844eb12&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;homepage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://www.iana.org/time-zones&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nSource: https://www.iana.org/time-zones\nUpstream-Contact: The Internet Assigned Numbers Authority (IANA)\n                  Commentary should be addressed to tz@iana.org\n\nFiles: *\nCopyright: The Internet Assigned Numbers Authority (IANA)\nLicense: public-domain\n This database is in the public domain.\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time zone and daylight-saving time data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time zone and daylight-saving time data\nThis package contains data required for the implementation of\nstandard local time for many representative locations around the\nglobe. It is updated periodically to reflect changes made by\npolitical bodies to time zone boundaries, UTC offsets, and\ndaylight-saving rules.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;externalRefs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceCategory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PACKAGE-MANAGER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;purl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;referenceLocator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pkg:deb/debian/tzdata@2024a-0+deb12u1?arch=all&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@arm64_debian12_tzdata//:spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SPDXID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;downloadLocation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;copyrightText&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NOASSERTION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generated from tzdata@2024a-0+deb12u1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-DOCUMENT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DESCRIBES&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-base-files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GENERATED_FROM&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DEPENDS_ON&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-netbase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GENERATED_FROM&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DEPENDS_ON&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef-arm64-underscore-debian12-underscore-tzdata&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GENERATED_FROM&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spdxElementId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relatedSpdxElement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relationshipType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DEPENDS_ON&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On notera que &lt;span class="caps"&gt;SPDX&lt;/span&gt; reprend le principe des triplets de &lt;a href="https://en.wikipedia.org/wiki/Resource_Description_Framework"&gt;&lt;span class="caps"&gt;RDF&lt;/span&gt;&lt;/a&gt; qui, promis juré, devait servir de base au web 3.0.&amp;nbsp;Ces &lt;code&gt;relationships&lt;/code&gt; décrivent un graphe avec des liaisons nommées (et des noms de nodes rigolos, un genre d&amp;#8217;échappement&amp;nbsp;littéral).&lt;/p&gt;
&lt;p&gt;On notera aussi que le moulinage des informations issus des paquets Debian ont des soucis bizzaries d&amp;#8217;&lt;span class="caps"&gt;UTF8&lt;/span&gt; avec&amp;nbsp;des &lt;code&gt;\\u003c&lt;/code&gt; et&amp;nbsp;des &lt;code&gt;\\u003e&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;SPDX&lt;/span&gt; permet de désigner tous les paquets utilisés, avec leur version, leur &lt;span class="caps"&gt;URL&lt;/span&gt; figée dans le temps, et le nom du responsable.
Tout ce qu&amp;#8217;il faut pour lancer une moulinette de chasse au &lt;span class="caps"&gt;CVE&lt;/span&gt;, pour mettre à jour la couche impactée sans devoir refaire les couches&amp;nbsp;supérieures.&lt;/p&gt;
&lt;h3 id="outils-pour-gerer-les-images-de-conteneur"&gt;Outils pour gérer les images de&amp;nbsp;conteneur&lt;/h3&gt;
&lt;p&gt;C&amp;#8217;est instructif de manipuler les images&amp;nbsp;avec &lt;code&gt;curl&lt;/code&gt; et &lt;code&gt;jq&lt;/code&gt;, mais il existe aussi des outils civilisés pour manipuler des registres d&amp;#8217;images de conteneur : &lt;a href="https://github.com/google/go-containerregistry"&gt;crane&lt;/a&gt; ou &lt;a href="https://github.com/containers/skopeo"&gt;skopeo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour vérifier les signatures des images, l&amp;#8217;outil de référence est &lt;a href="https://github.com/sigstore/cosign"&gt;cosign&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="autres-projets-dimages-minimalistes"&gt;Autres projets d&amp;#8217;images&amp;nbsp;minimalistes&lt;/h3&gt;
&lt;h4 id="wolfi"&gt;Wolfi&lt;/h4&gt;
&lt;p&gt;Chainguard, les gens qui participent au développement de sigstore, propose
&lt;a href="https://wolfi.dev/"&gt;Wolfi&lt;/a&gt;, une distribution Linux basée sur &lt;em&gt;apk&lt;/em&gt; (comme Alpine), mais avec &lt;em&gt;glibc&lt;/em&gt;, ciblant uniquement les conteneurs, et donc sans kernel, sans &lt;em&gt;init&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Les images de base Wolfi peuvent s&amp;#8217;utiliser &amp;#8220;comme avant&amp;#8221;, avec un &lt;em&gt;Dockerfile&lt;/em&gt; et &lt;em&gt;apk&lt;/em&gt;, comme on le ferait avec une image&amp;nbsp;Alpine.&lt;/p&gt;
&lt;p&gt;Les avant-gardistes, eux, &lt;a href="https://edu.chainguard.dev/open-source/apko/getting-started-with-apko/"&gt;construiront leurs images Wolfi avec&amp;nbsp;apko&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="apko"&gt;Apko&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/chainguard-dev/apko"&gt;Apko&lt;/a&gt; est un outil minimaliste écrit en go, utilisable depuis un conteneur ou depuis un &lt;span class="caps"&gt;OS&lt;/span&gt; (Linux ou non), qui va créer l&amp;#8217;archive (multi architecture) d&amp;#8217;une image de conteneur et plein de SBOMs, à partir d&amp;#8217;un &lt;span class="caps"&gt;YAML&lt;/span&gt; et d&amp;#8217;un dépôt &lt;em&gt;apk&lt;/em&gt;.
Apko peut même pousser l&amp;#8217;image dans un&amp;nbsp;registre.&lt;/p&gt;
&lt;p&gt;Comme Apko est un rebelle, il peut créer des fat containers avec &lt;a href="https://skarnet.org/software/s6/index.html"&gt;s6&lt;/a&gt; comme&amp;nbsp;init.&lt;/p&gt;
&lt;p&gt;Apko se fiche de Wolfi, de Alpine, de Bazel, de Docker, de Podman, de Containerd, de Kubernetes et de plein d&amp;#8217;autres choses.
Mais comme il est poli, il sait aussi bosser avec&amp;nbsp;eux.&lt;/p&gt;
&lt;p&gt;Apko fait des builds déterministes, multi architecture, et donc ne lance pas de commandes pendant le build.
En bon monomaniaque, Apko mise tout sur &lt;em&gt;apk&lt;/em&gt;, et donc pousse a utiliser son projet &lt;em&gt;melange&lt;/em&gt; pour créer les &lt;em&gt;apk&lt;/em&gt; qui seront utilisés par Apko.
Ou vous pouvez truander avec un &lt;em&gt;Dockerfile&lt;/em&gt;, à&amp;nbsp;l&amp;#8217;ancienne.&lt;/p&gt;
&lt;h5 id="melange"&gt;Melange&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/chainguard-dev/melange"&gt;Melange&lt;/a&gt; est un autre projet de Chainguard pour créer des paquets &lt;em&gt;apk&lt;/em&gt; multi architectures à partir d&amp;#8217;un incontournable &lt;span class="caps"&gt;YAML&lt;/span&gt;.
Pour être utilisé hors des Linux basés sur &lt;em&gt;apk&lt;/em&gt;, Chainguard a réimplementé &lt;a href="https://pkg.go.dev/github.com/chainguard-dev/go-apk"&gt;apk en go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le projet est tout frais (en version 0.6), peu documenté, mais prometeur,&lt;a href="https://github.com/chainguard-dev/melange/graphs/contributors"&gt;actif depuis 2 ans&lt;/a&gt;, avec une belle liste de pull&amp;nbsp;requests.&lt;/p&gt;
&lt;p&gt;La création pour les architectures non-natives passe par &lt;a href="https://en.wikipedia.org/wiki/Binfmt_misc"&gt;binfmt_misc&lt;/a&gt; qui s&amp;#8217;appuie sur &lt;em&gt;qemu&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Les builds classiques sont gérés (cargo, rust, npm, make install …), et il est même possible de créer un build Melange à partir d&amp;#8217;un projet ruby, python ou&amp;nbsp;apk.&lt;/p&gt;
&lt;p&gt;Le code parle de &lt;a href="https://github.com/chainguard-dev/melange/tree/main/pkg/container"&gt;conteneurs Docker/k8s/dagger/bubblewrap&lt;/a&gt; mais pas la documentation.
Pour les développeurs Mac, il y a une intégration à &lt;a href="https://lima-vm.io/"&gt;Lima&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les builds simples tombent en marche, de manière assez magique, mais j&amp;#8217;attends une bonne bagarre avec le débug d&amp;#8217;un build qui ne passe pas pour avoir un avis plus&amp;nbsp;affirmé.&lt;/p&gt;
&lt;p&gt;Chainguard croit très fort à &lt;em&gt;apk&lt;/em&gt; comme artéfact universel, brique de base pour composer un&amp;nbsp;conteneur.&lt;/p&gt;
&lt;h4 id="ko"&gt;Ko&lt;/h4&gt;
&lt;p&gt;Golang est le &amp;#8220;fruit bas&amp;#8221; des langages à déployer dans un conteneur.
Golang crée un gros binaire statique, et peut cross-compiler très&amp;nbsp;simplement.&lt;/p&gt;
&lt;p&gt;Google a crée &lt;a href="https://github.com/ko-build/ko"&gt;ko&lt;/a&gt;, avant de lui dédier son propre groupe dans Github et de postuler comme projet &lt;span class="caps"&gt;CNCF&lt;/span&gt; (il est niveau &amp;#8220;bac à sable&amp;#8221; pour&amp;nbsp;l&amp;#8217;instant).&lt;/p&gt;
&lt;p&gt;On retrouve les arguments du moment : build natif, &lt;span class="caps"&gt;SBOM&lt;/span&gt;, &lt;span class="caps"&gt;YAML&lt;/span&gt;, publication dans un&amp;nbsp;registre.&lt;/p&gt;
&lt;p&gt;Dans les pinaillages, Ko recommande de ne pas pas utiliser &lt;em&gt;cgo&lt;/em&gt;, et demande à ce que les éventuelles dépendances soient fournies dans l&amp;#8217;image de base.
Par contre, Ko sait déployer des&amp;nbsp;assets.&lt;/p&gt;
&lt;h4 id="jib"&gt;Jib&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/GoogleContainerTools/jib"&gt;Jib&lt;/a&gt;, du même Google, permet de créer des images de conteneurs à partir de projets&amp;nbsp;Java.&lt;/p&gt;
&lt;p&gt;Jib a la tâche encore plus facile que Ko, Java est par principe multi architectures.
L&amp;#8217;image de base est OpenJDK, mais il est possible de paramétrer ce&amp;nbsp;choix.&lt;/p&gt;
&lt;p&gt;Jib est une extension pour Maven ou Gradle, ou s&amp;#8217;utilise comme outil indépendant et il n&amp;#8217;utilise pas de&amp;nbsp;conteneur.&lt;/p&gt;
&lt;p&gt;Tout le travail de Jib est de déposer les différents &lt;em&gt;jar&lt;/em&gt; sur différentes couches d&amp;#8217;images de conteneurs, et lors d&amp;#8217;un nouveau build de l&amp;#8217;application, seuls les &lt;em&gt;jar&lt;/em&gt; modifiés seront redéployés sur leur couche&amp;nbsp;respective.&lt;/p&gt;
&lt;h3 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;&lt;span class="caps"&gt;OCI&lt;/span&gt; continue de taper sur Docker pour lui expliquer la différence entre implémentation et&amp;nbsp;spécification.&lt;/p&gt;
&lt;p&gt;Des fois, c&amp;#8217;est de mauvaise foi (à chaque fois que Redhat vante son écosystème concurrent), souvent c&amp;#8217;est justifié.
L&amp;#8217;absence de marché clair pour Docker inc est triste, et la constance de Docker Desktop a demandé des sous (l&amp;#8217;achat d&amp;#8217;une licence) sur presque toutes les nouvelles commandes n&amp;#8217;améliore pas leur&amp;nbsp;karma.&lt;/p&gt;
&lt;p&gt;Le &lt;em&gt;Dockerfile&lt;/em&gt; reste le format standard et universel pour créer des conteneurs, mais la norme est le format de l&amp;#8217;image.
Il est normal de pouvoir créer une image de conteneur comme on le souhaite (comme &lt;a href="https://github.com/opencontainers/umoci"&gt;umoci&lt;/a&gt; le propose), et d&amp;#8217;explorer d&amp;#8217;autres pistes.
Il est par exemple intéressant de pouvoir travailler couche par couche pour composer une image, en ne changeant que ce qu&amp;#8217;il y a de nouveau.
La &lt;a href="https://docs.docker.com/build/building/multi-platform/"&gt;gestion des architectures multiples est une vraie question auquel Docker&lt;/a&gt; ne répond que&amp;nbsp;partiellement.&lt;/p&gt;
&lt;p&gt;Pouvoir débuguer en local/staging est normal, mais l&amp;#8217;outillage doit être différent en production. Vous n&amp;#8217;avez pas forcément la main sur le nombre d&amp;#8217;instances de votre conteneur, il y a peu de chance que vous y ayez un accès privilégié, et vous n&amp;#8217;allez pas attendre que l&amp;#8217;incident&amp;nbsp;revienne.&lt;/p&gt;
&lt;p&gt;La généralisation des traces et des journaux structurés sont là depuis longtemps et maintenant indispensable.
La vague &lt;em&gt;ebpf&lt;/em&gt; prolonge ce mouvement et va offrir plus de visibilité sur le comportement des applications en&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;Les offres d&amp;#8217;hébergement de conteneur de haut niveau, comme &lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt; propose une interface simple et va masquer les détails d&amp;#8217;implémentation, pour plutôt proposer une intégration fine avec un ensemble de services pour le client ou le&amp;nbsp;développeur.&lt;/p&gt;
&lt;p&gt;Le postulat d&amp;#8217;Heroku est toujours valide : &amp;#8220;tu git push, ça finit en prod&amp;#8221;, sauf que maintenant c&amp;#8217;est explicite, normalisé, et&amp;nbsp;sécurisé.&lt;/p&gt;</content><category term="Ops"></category><category term="distroless"></category><category term="container"></category><category term="docker"></category><category term="bazel"></category><category term="wolfi"></category><category term="apko"></category></entry><entry><title>Campagne internationale pour la sécurité informatique : sécuriser la chaine d’approvisionement</title><link href="http://blog.garambrogne.net/la-maison-blanche-veut-securiser-Internet-1.html" rel="alternate"></link><published>2024-03-18T09:04:00+01:00</published><updated>2024-03-18T09:04:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2024-03-18:/la-maison-blanche-veut-securiser-Internet-1.html</id><summary type="html">&lt;p&gt;La Maison-Blanche demande un audit sur la sécurité des logiciels open source. Partie 1/3 : sécuriser la chaine&amp;nbsp;d&amp;#8217;approvisionement.&lt;/p&gt;</summary><content type="html">&lt;h1 id="campagne-internationale-pour-la-securite-informatique"&gt;Campagne internationale pour la sécurité&amp;nbsp;informatique&lt;/h1&gt;
&lt;p&gt;Il y a quelques jours, &lt;a href="https://www.whitehouse.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf"&gt;la Maison-Blanche demande un audit sur la sécurité de ses logiciels&lt;/a&gt;, tout en reconnaissant l&amp;#8217;utilisation massive (et indispensable) de l&amp;#8217;écosystème open source pour les services de&amp;nbsp;l&amp;#8217;État.&lt;/p&gt;
&lt;p&gt;Le sujet de l&amp;#8217;audit est large (et très technique), et a pour objectif d&amp;#8217;améliorer drastiquement la sécurité des logiciels.
Les pistes sont bien connues, mais là, c&amp;#8217;est écrit noir sur blanc par la&amp;nbsp;Maison-Blanche.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sécurisation des fournisseurs de paquets (logiciels et&amp;nbsp;bibliothèques)&lt;/li&gt;
&lt;li&gt;Mis en place d&amp;#8217;outils pour quantifier la qualité et la sécurité du&amp;nbsp;code&lt;/li&gt;
&lt;li&gt;Remplacement des logiciels codé avec des langages sans gestion stricte de la mémoire (memory&amp;nbsp;safe).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.blog/category/security/"&gt;Github fournit des outils sur ce thème&lt;/a&gt; depuis quelque temps, aucune surprises, mais entendre le président des États-Unis dauber sur le C/C++, c&amp;#8217;est une première.
Oui, le document de la Maison-Blanche cite le nom de Biden à tour de bras, j&amp;#8217;imagine que c&amp;#8217;est l&amp;#8217;usage dans ce pays, mais hors &lt;span class="caps"&gt;US&lt;/span&gt;, on a l&amp;#8217;impression que c&amp;#8217;est une attaque&amp;nbsp;personnelle.&lt;/p&gt;
&lt;p&gt;Le document est plein d&amp;#8217;intentions, on attend le résultat de l&amp;#8217;audit pour avoir des éléments concrets, mais il contient quand même plein de liens bien plus concrets comme &lt;a href="https://www.cisa.gov/sites/default/files/2023-09/CISA-Open-Source-Software-Security-Roadmap-508c.pdf"&gt;une roadmap sur la sécurité open&amp;nbsp;source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Comme le sujet est touffu, et plein d&amp;#8217;acronymes, le billet sera coupé en trois, en suivant le découpage de la Maison-Blanche&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sécuriser la chaine d&amp;#8217;approvisionnement (ce&amp;nbsp;billet)&lt;/li&gt;
&lt;li&gt;&lt;a href="la-maison-blanche-veut-securiser-Internet-2.html"&gt;Quantifier la qualité du&amp;nbsp;code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Protéger l&amp;#8217;accès à la&amp;nbsp;mémoire&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dependances-logicielles"&gt;Dépendances&amp;nbsp;logicielles&lt;/h2&gt;
&lt;p&gt;Une application est composée de code pris sur l&amp;#8217;étagère, sous forme de source ou de paquets (gros binaire, paquet système, image Docker…) ou de code développé maison qui va utiliser des bibliothèques&amp;nbsp;existantes.&lt;/p&gt;
&lt;p&gt;Pour ne pas réinventer la roue (et la maintenir), une application va dépendre de paquets (au sens large) gérés par des&amp;nbsp;tiers.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est un des aspects principaux de l&amp;#8217;open&amp;nbsp;source.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://xkcd.com/2347/"&gt;&lt;img alt="Dependency by XKCD" class="image-process-article-image" src="/images/dependency_2x.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Le sujet est pris en main par la &lt;strong&gt;Linux Foundation&lt;/strong&gt;, via l&amp;#8217;&lt;a href="https://openssf.org/"&gt;Opensource Security Foundation&lt;/a&gt;, via &lt;a href="https://alpha-omega.dev/"&gt;Alpha-Omega&lt;/a&gt; fondé par les gros du cloud (Amazon, Google et Microsoft) pour supporter les outils essentiels de l&amp;#8217;open source, du début à la fin (la ref ⍺ - Ω, vous&amp;nbsp;l&amp;#8217;avez?).&lt;/p&gt;
&lt;p&gt;Les cascades de fondations et de comités, avec pour l&amp;#8217;instant des zones floues ou redondantes, laissent présager moult réunions et visioconférences.
Mais trêve de mauvaise langue, ils ont déjà produit des choses concrètes : code, données et financement de projets existants.
L&amp;#8217;adoubement de la fondation Linux devrait calmer la fascination de la dispersion et des projets qui n&amp;#8217;aboutissent&amp;nbsp;jamais.&lt;/p&gt;
&lt;h2 id="chaine-dapprovisionnement"&gt;Chaine&amp;nbsp;d&amp;#8217;approvisionnement&lt;/h2&gt;
&lt;p&gt;Gérer la dépendance à des produits tiers se joue sur la durée, il peut se passer plein de choses durant ce&amp;nbsp;voyage.&lt;/p&gt;
&lt;h3 id="failles-officielles"&gt;Failles&amp;nbsp;officielles&lt;/h3&gt;
&lt;p&gt;Découvrir une chouette faille de sécurité est un titre de gloire, surtout si ça permet de faire des choses créatives et&amp;nbsp;imprévues.&lt;/p&gt;
&lt;p&gt;Pour éviter les doublons et pour que tout le monde parle de la même chose, l&amp;#8217;usage est d&amp;#8217;enregistrer sa vulnérabilité auprès de &lt;span class="caps"&gt;CVE&lt;/span&gt;, &lt;a href="https://cve.mitre.org/"&gt;Common Vulnerabilities and Exposures&lt;/a&gt; géré par l&amp;#8217;organisme Mitre, soutenu par le département de la sécurité intérieure des Etats-Unis.
&lt;span class="caps"&gt;CVE&lt;/span&gt; est le standard de fait, mais sa gestion est problématique, cf &lt;a href="https://daniel.haxx.se/blog/2024/02/21/disputed-not-rejected/"&gt;Curl conteste certains de ses CVEs&lt;/a&gt;.
L&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; de &lt;span class="caps"&gt;CVE&lt;/span&gt; est peu pratique, et ses données, très peu formatées, sont dures à&amp;nbsp;exploiter.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;OpenSSF propose un nouveau format, développé conjointement avec la communauté open source : l&amp;#8217;&lt;a href="https://osv.dev/"&gt;&lt;span class="caps"&gt;OSV&lt;/span&gt;&lt;/a&gt; (pour Open Source&amp;nbsp;Vulnerability).&lt;/p&gt;
&lt;p&gt;Les paquets sont identifiés par un écosystème (le dépôt officiel), un nom et une version, ou directement par un commit&amp;nbsp;hash.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;OSV&lt;/span&gt; &lt;a href="https://google.github.io/osv.dev/data/"&gt;agrège de multiples sources&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;OSV&lt;/span&gt; fournit une &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt; et un cli (en go) qui sait lire la &lt;a href="https://google.github.io/osv-scanner/supported-languages-and-lockfiles/"&gt;liste de dépendances gelées de différents formats&lt;/a&gt; ainsi que les listes de paquets installés de Alpine ou Debian, les images Docker basées sur&amp;nbsp;Debian.&lt;/p&gt;
&lt;p&gt;Tout l&amp;#8217;outillage d&amp;#8217;&lt;span class="caps"&gt;OSV&lt;/span&gt; est lié à &lt;span class="caps"&gt;GCE&lt;/span&gt;, avec des services pénibles comme AppEngine, ou l&amp;#8217;utilisation de &lt;span class="caps"&gt;GCS&lt;/span&gt; (du S3, quoi) comme source.
La documentation ressemble à un publireportage &lt;span class="caps"&gt;GCE&lt;/span&gt;, ce qui est dommage.
D&amp;#8217;un autre côté, proposer un schéma de table, agréger les failles, lire des listes de paquets n&amp;#8217;est pas un challenge technique, mais surtout un travail politique pour convaincre et devenir la&amp;nbsp;référence.&lt;/p&gt;
&lt;h4 id="paquets-malhonnetes"&gt;Paquets&amp;nbsp;malhonnêtes&lt;/h4&gt;
&lt;p&gt;Au-delà des failles (involontaires), il existe aussi des paquets volontairement dangereux qui &lt;a href="https://github.com/ossf/package-analysis/blob/main/docs/case_studies.md"&gt;peuvent faire par exemple ce genre de choses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le projet &lt;a href="https://github.com/ossf/package-analysis"&gt;package-analysis&lt;/a&gt; effectue l&amp;#8217;analyse dynamique de paquet, en le lançant dans un environnement isolé ( &lt;a href="https://gvisor.dev/"&gt;gVisor, un conteneur paranoïaque&lt;/a&gt;, pour observer les fichiers qu&amp;#8217;il tripote, les adresses où il se&amp;nbsp;connecte.&lt;/p&gt;
&lt;p&gt;Les paquets scélérats, compromis, ou avec des noms trompeurs (aka typosquatting) sont répertoriés par le projet &lt;a href="https://github.com/ossf/malicious-packages"&gt;malicious-packages&lt;/a&gt; pour créer des &lt;span class="caps"&gt;OSV&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="paquets-de-confiance"&gt;Paquets de&amp;nbsp;confiance&lt;/h3&gt;
&lt;p&gt;Il arrive qu&amp;#8217;un paquet légitime soit corrompu durant son cycle de&amp;nbsp;vie.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;attaque peut être effectuée à différents&amp;nbsp;niveaux.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Des patchs malicieux peuvent être proposés, prétendant améliorer un point alors qu&amp;#8217;il en fragilise un autre. &lt;a href="https://www.theverge.com/2021/4/30/22410164/linux-kernel-university-of-minnesota-banned-open-source"&gt;Un étudiant a réussi à faire passer un patch malicieux dans le kernel Linux avant de s&amp;#8217;en vanter. Sa fac, l&amp;#8217;université du Minnesota, s&amp;#8217;est fait bannir avec lui&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;En truandant l&amp;#8217;authentification d&amp;#8217;un mainteneur, il est possible de publier une version scélérate d&amp;#8217;un paquet sur un&amp;nbsp;dépôt.&lt;/li&gt;
&lt;li&gt;Via l&amp;#8217;intégration continue, il est possible de modifier ou de publier des&amp;nbsp;bêtises.&lt;/li&gt;
&lt;li&gt;Il est tentant de modifier le cache d&amp;#8217;un dépôt, bien que les signatures soit censées empêcher ce genre&amp;nbsp;d&amp;#8217;attaque.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="depots-pour-bibliotheques-de-langage"&gt;Dépôts pour bibliothèques de&amp;nbsp;langage&lt;/h4&gt;
&lt;p&gt;Les langages disposent de bibliothèques de code disponibles via des dépôts&amp;nbsp;spécifiques.&lt;/p&gt;
&lt;p&gt;La sensibilisation à la sécurité pour la publication de bibliothèque a été bien trop tardive.
Même si la possibilité de signer un paquet est arrivé tôt, sans prosélytisme, ça a eut du mal à prendre, et il faut une chaine de confiance, de bout en bout, pour que ce soit&amp;nbsp;utilisable.&lt;/p&gt;
&lt;h5 id="python-pypi"&gt;Python&amp;nbsp;Pypi&lt;/h5&gt;
&lt;p&gt;Dans leur liste de diffusion, &lt;a href="https://mail.python.org/pipermail/distutils-sig/2016-May/028933.html"&gt;les paqueteurs Python évoquent le drame du faible taux de signature en 2016&lt;/a&gt;, de toute façons, seul le format binaire, &lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/#signed-wheel-files"&gt;les whl, gèrent les signatures&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Python a des spécifications pour l&amp;#8217;empaquetage, &lt;a href="https://www.pypa.io/en/latest/"&gt;Pypa&lt;/a&gt;, un hangar qui progresse bien, &lt;a href="https://warehouse.pypa.io/"&gt;Warehouse&lt;/a&gt;, mais côté client, c&amp;#8217;est très&amp;nbsp;confus.&lt;/p&gt;
&lt;p&gt;Il y a plusieurs formats pour définir les dépendances, rien de bien précis pour le verrouillage, que n&amp;#8217;utilise pas l&amp;#8217;outil par défaut, &lt;em&gt;pip&lt;/em&gt;.
Les challengers, &lt;a href="https://python-poetry.org/"&gt;poetry&lt;/a&gt; et &lt;a href="https://github.com/frostming/unearth"&gt;unearth&lt;/a&gt; manquent d&amp;#8217;ambitions, alors que &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt; rêve de la perfection de&amp;nbsp;rust.&lt;/p&gt;
&lt;p&gt;Plutôt que de batailler avec des clés de développeurs, Python se dirige vers la notion de &lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;diffuseur de confiance&lt;/a&gt;.
On indique à Pypi qu&amp;#8217;un build précis d&amp;#8217;une &lt;span class="caps"&gt;CI&lt;/span&gt; (projet, branche…) est de confiance.
La &lt;span class="caps"&gt;CI&lt;/span&gt; va publier vers Pypi l&amp;#8217;artéfact, avec le token &lt;span class="caps"&gt;OIDC&lt;/span&gt; contenant les détails du build, si le token est valide, et si les détails correspondent à ce qui a été paramétré, Pypi accepte&amp;nbsp;l&amp;#8217;artéfact.&lt;/p&gt;
&lt;p&gt;Même si cette approche semble simple, fonctionne pour une personne ou une &lt;span class="caps"&gt;CI&lt;/span&gt;, elle ne survit pas à la compromission du serveur Pypi, personne ne pourra savoir quels paquets ont été&amp;nbsp;bidouillés.&lt;/p&gt;
&lt;p&gt;Il faut que les paquets aient une signature auditable, qui soit faite en dehors du serveur&amp;nbsp;Pypi.&lt;/p&gt;
&lt;h5 id="golang"&gt;Golang&lt;/h5&gt;
&lt;p&gt;Golang ne prévoit rien pour signer, il délègue toute la confiance au dépôt de source distant (Github, quoi).
Par contre, &lt;a href="https://go.dev/ref/mod#go-sum-files"&gt;Golang maintient une base de hash pour les différentes versions d&amp;#8217;un paquet, avec un journal transparent&lt;/a&gt;, ce qui permet d&amp;#8217;utiliser un miroir pour télécharger un&amp;nbsp;paquet.&lt;/p&gt;
&lt;p&gt;Les applications Golang sont livrées sous forme binaire, que vous pouvez signer de manière&amp;nbsp;classique.&lt;/p&gt;
&lt;h5 id="ruby-gem"&gt;Ruby&amp;nbsp;Gem&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://guides.rubygems.org/security/"&gt;Ruby documente l&amp;#8217;utilisation de certificat asymétrique avec ses gems&lt;/a&gt; mais avoue ne pas savoir comment gérer une chaine de confiance, et qu&amp;#8217;ils en&amp;nbsp;discutent.&lt;/p&gt;
&lt;p&gt;Par contre, &lt;a href="https://guides.rubygems.org/trusted-publishing/adding-a-publisher/"&gt;une gem peut faire confiance à une source &lt;span class="caps"&gt;OIDC&lt;/span&gt;&lt;/a&gt; avec l&amp;#8217;exemple d&amp;#8217;une action de la &lt;span class="caps"&gt;CI&lt;/span&gt; Github (et ils citent la documentation pypi pour comprendre le&amp;nbsp;fonctionnement).&lt;/p&gt;
&lt;p&gt;Même fragilité que Pypi en cas de compromission du&amp;nbsp;serveur.&lt;/p&gt;
&lt;h5 id="node-npm"&gt;Node&amp;nbsp;npm&lt;/h5&gt;
&lt;p&gt;Longtemps mauvais élève, &lt;a href="https://docs.npmjs.com/generating-provenance-statements"&gt;Npm vante maintenant la notion de provenance avec Sigstore&lt;/a&gt; et propose de &lt;a href="https://docs.npmjs.com/verifying-registry-signatures"&gt;vérifier la signature du dépôt utilisé&lt;/a&gt;&amp;nbsp;: &lt;code&gt;npm audit signatures&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En signant les paquets en dehors du dépôt et en utilisant un journal public, ce que permet l&amp;#8217;utilisation de &lt;a href="https://www.sigstore.dev/"&gt;Sigstore&lt;/a&gt;, le dépôt npm est moins fragile que pypi et&amp;nbsp;rubygem.&lt;/p&gt;
&lt;p&gt;Les concurrents de Nodejs ne sont pas très ambitieux sur la&amp;nbsp;sécurité:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.deno.com/runtime/manual/basics/modules/integrity_checking"&gt;Deno se contente d&amp;#8217;écrire les hashs des dépendances dans un fichier &lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bun.sh/docs/install/registries"&gt;Bun utilise le registre de npm, sans rien ajouter&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="rust-crates"&gt;Rust&amp;nbsp;crates&lt;/h5&gt;
&lt;p&gt;Pour l&amp;#8217;instant, les paquets publiés sur crate.io ne sont pas signés, mais il y a en préparation &lt;a href="https://github.com/trustification/rust-rfcs/blob/sigstore-rfc/text/0000-sigstore-integration.md"&gt;une &lt;span class="caps"&gt;RFC&lt;/span&gt; pour intégrer Sigstore&lt;/a&gt;.
La &lt;a href="https://doc.rust-lang.org/cargo/guide/continuous-integration.html"&gt;documentation parle d&amp;#8217;intégration continue&lt;/a&gt;, mais pas de déploiement continu.
Pour l&amp;#8217;instant, crate.io authentifie les utilisateurs en OAuth2 avec Github, et permet la création de token pour publier, on a donc un lien entre un crate publié et un compte Github, mais c&amp;#8217;est un peu&amp;nbsp;peu.&lt;/p&gt;
&lt;h5 id="paquets-linux"&gt;Paquets&amp;nbsp;Linux&lt;/h5&gt;
&lt;p&gt;La gestion de paquets des distributions Linux est mature depuis bien longtemps, et toutes gèrent les signatures (sauf&amp;nbsp;Slackware).&lt;/p&gt;
&lt;p&gt;La plupart des distributions utilisent gpg pour signer les paquets (sauf Alpine qui se distinguent avec de la clé openssl&amp;nbsp;nue).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://archlinux.org/master-keys/"&gt;ArchLinux prend la peine d&amp;#8217;établir un vraie rituel de signature&lt;/a&gt;&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5 clés maitres ont le pouvoir signer les clés des&amp;nbsp;mainteneurs.&lt;/li&gt;
&lt;li&gt;5 personnes détiennent une clé maitre ainsi que la clé de résiliation d&amp;#8217;une autre clé&amp;nbsp;maitre.&lt;/li&gt;
&lt;li&gt;Une clé de mainteneur est légitime pour signer un paquet si elle est signée par au moins 3 clés&amp;nbsp;maitres.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les autres distributions se contentent de fournir un trousseau avec des personnes de&amp;nbsp;confiance.&lt;/p&gt;
&lt;p&gt;Même si les distributions Linux disposent d&amp;#8217;une &lt;span class="caps"&gt;PKI&lt;/span&gt; pour signer leurs paquets, et empaquetent une partie des bibliothèques de langage, les paquets Linux ne sont pas utilisable pour développer des applications&amp;nbsp;métiers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le cycle de vie d&amp;#8217;un paquet Linux est lié à une version de la distribution, seuls les mises à jour de sécurité seront rétro-appliquées (backportées, quoi), les bugs fonctionnels resteront en l&amp;#8217;état, et certaines distributions se permettent des patchs qui ne seront pas supportés par le mainteneur de la&amp;nbsp;bibliothèque.&lt;/li&gt;
&lt;li&gt;Une bibliothèque emballée dans un paquet Linux sera disponible dans une unique version majeure, utilisé par tous les paquets qui en ont&amp;nbsp;besoin.&lt;/li&gt;
&lt;li&gt;Même si les langages compilés modernes créent des binaires statiques, les paquets Linux doivent être reproductibles, et donc les bibliothèques des sources doivent être empaquetées. Cette règle empêche d&amp;#8217;avoir des paquets officiels pour des applications agressives sur les versions de bibliothèques, comme Mongodb, Clickhouse ou même&amp;nbsp;Nodejs.&lt;/li&gt;
&lt;li&gt;Le vendoring, largement utilisé dans le développement contemporain, en gérant de manière isolée (et redondante) les dépendances logicielles est un tabou des distributions Linux. Il empêche la rationalisation et le travail de correction des&amp;nbsp;mainteneurs.&lt;/li&gt;
&lt;li&gt;Les formats de paquets historiques sont vieillissants, confus et la somme d&amp;#8217;un empilement d&amp;#8217;outils. Leur cout est négligeable par rapport à la valeur de la cohérence d&amp;#8217;une distribution, mais hors de prix pour des applications&amp;nbsp;métiers.&lt;/li&gt;
&lt;li&gt;Les distributions Linux récentes sont tentées par la livraison continue, &lt;em&gt;rolling release&lt;/em&gt;, sans jamais figer de version. Cette approche favorise la fraicheur des paquets, mais ne garanti plus la constance du comportement des applications ni même la possibilité d&amp;#8217;avoir des mises à jour espacées dans le temps. Conçu pour les postes de travail, ce genre de distribution impose des stratégies de mises à jour rigoureuses sur un serveur, comme&amp;nbsp;l&amp;#8217;immutabilité&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="paquets-linux-universels"&gt;Paquets Linux&amp;nbsp;universels&lt;/h5&gt;
&lt;p&gt;Fournir des paquets Linux pour un éditeur est une&amp;nbsp;tannée.&lt;/p&gt;
&lt;p&gt;En ciblant des serveurs, il est imaginable d&amp;#8217;imposer des distributions majeures comme Redhat, Debian ou Ubuntu, mais pour des applications grand public, il y aura trop de variations possibles.
S&amp;#8217;ajoutent à ça les soucis de dépendances à des versions précises de&amp;nbsp;bibliothèques.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;utilisateur, installer une application non disponible dans sa distribution, sans forcément avoir accès aux sources n&amp;#8217;est pas une très bonne idée d&amp;#8217;un point de vue sécurité.
Un paquet Linux classique s&amp;#8217;installe avec les droits de super utilisateur et fait bien ce qu&amp;#8217;il&amp;nbsp;veut.&lt;/p&gt;
&lt;p&gt;Depuis l&amp;#8217;avènement des conteneurs, Linux sait correctement isoler des applications, même si l&amp;#8217;intégration à un bureau ne fait pas partie des objectifs d&amp;#8217;un Docker (même si &lt;a href="https://blog.jessfraz.com/post/docker-containers-on-the-desktop/"&gt;Jessfraz a relevé le challenge&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Freedesktop a effectué un travail de titan pour normaliser les points d&amp;#8217;entrées d&amp;#8217;un bureau Linux, permettant une intégration élégante d&amp;#8217;applications disparates.
Il faut donc que les applications, bien que bordées, puissent profiter de&amp;nbsp;Freedesktop.&lt;/p&gt;
&lt;h6 id="flatpack"&gt;Flatpack&lt;/h6&gt;
&lt;p&gt;&lt;a href="https://flatpak.org/"&gt;Flatpak&lt;/a&gt; se positionne comme solution universelle de déploiement d&amp;#8217;application sur&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Il s&amp;#8217;appuie sur &lt;a href="https://github.com/containers/bubblewrap"&gt;Bubble Wrap&lt;/a&gt; pour l&amp;#8217;isolation (namespace et seccomp), cgroup étant géré par&amp;nbsp;systemd.&lt;/p&gt;
&lt;p&gt;Plutôt que de bricoler un chroot obèse comme image de base, &lt;a href="https://docs.flatpak.org/en/latest/available-runtimes.html"&gt;FlatPack propose des runtimes&lt;/a&gt; qui seront mutualisés entre les différents paquets (et mis à jour indépendamment de l&amp;#8217;application, en s&amp;#8217;engageant à garder stable l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Le dédoublonnage, et le versionnage des fichiers sont gérés par &lt;em&gt;ostree&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Une &lt;a href="https://docs.flatpak.org/en/latest/sandbox-permissions-reference.html"&gt;liste compréhensible de droits&lt;/a&gt; permet de choisir ce que peut faire une&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;Pour les mises à jour, un outil permet de le faire en ligne de commande, ou un plugin permet à Flatpack de s&amp;#8217;incruster dans le gestionnaire de paquets de&amp;nbsp;Gnome.&lt;/p&gt;
&lt;h6 id="snap"&gt;Snap&lt;/h6&gt;
&lt;p&gt;&lt;a href="https://snapcraft.io/"&gt;Snap&lt;/a&gt; voit plus loin que le bureau Linux (il a été conçu pour l&amp;#8217;&lt;span class="caps"&gt;IOT&lt;/span&gt;), mais son choix d&amp;#8217;utiliser Apparmor brime la famille de distributions Linux qui activent SELinux automatiquement : Redhat (qui pousse Flatpack, le concurrent).
SELinux et Apparmor se branchent au même &lt;a href="https://en.wikipedia.org/wiki/Linux_Security_Modules"&gt;endroit dans le noyau Linux&lt;/a&gt; : ils sont&amp;nbsp;exclusifs.&lt;/p&gt;
&lt;p&gt;Les images de bases, appelées &lt;em&gt;core&lt;/em&gt; sont basées sur&amp;nbsp;Ubuntu.&lt;/p&gt;
&lt;p&gt;Quand Ubuntu a décidé d&amp;#8217;utiliser snap quand on installe un navigateur web avec un paquet &lt;em&gt;deb&lt;/em&gt;, ça a un peu fait grincer des dents.
Ce choix permet d&amp;#8217;avoir les navigateurs dans leur version courante, dans différentes versions d&amp;#8217;Ubuntu, même les très stables &lt;span class="caps"&gt;LTS&lt;/span&gt; (support à long terme).
Je ne souhaite à personne de backporter les patchs de sécurités dans les bases de code tentaculaires des navigateurs&amp;nbsp;webs.&lt;/p&gt;
&lt;p&gt;Un Snap utilise SquashFS (un système de fichier compressé) comme format de&amp;nbsp;paquet.&lt;/p&gt;
&lt;p&gt;Snap se met à jour automatiquement, avec tout le confort moderne : livraison par delta, modification atomique, retour arrière&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="https://snapcraft.io/docs/supported-interfaces"&gt;interfaces&lt;/a&gt; permettent de réclamer des droits avec le système, et même d&amp;#8217;en créer de nouveaux pour que les snaps discutent entre&amp;nbsp;eux.&lt;/p&gt;
&lt;p&gt;La documentation ne l&amp;#8217;évoque que partiellement, mais Snap utilise des &lt;a href="https://ubuntu.com/core/docs/reference/assertions"&gt;signatures à tous les étages&lt;/a&gt;, avec des chaines de confiances entre le publieur, le dépôt, le&amp;nbsp;paquet.&lt;/p&gt;
&lt;h5 id="distributions-linux-immuables"&gt;Distributions Linux&amp;nbsp;immuables&lt;/h5&gt;
&lt;p&gt;Les distributions Linux peuvent être considérées comme un unique paquet, immuable, avec des stratégies de mises à jour atomiques, et la possibilité de rétro-déploiement (rollback), on parle alors de &lt;a href="https://github.com/castrojo/awesome-immutable"&gt;distributions immuables&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ne laissant aucun choix d&amp;#8217;installation de paquets, il n&amp;#8217;y a pas de multiples combinaisons à tester pour valider une nouvelle&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;Il est possible de composer une distribution immuable à partir de paquets Linux (et de profiter des patchs, des mises à jour et de la cohérence entre&amp;nbsp;eux).&lt;/p&gt;
&lt;p&gt;Ces distributions peuvent avoir des stratégies de mises à jour agressive, ayant la garantie de pouvoir revenir à un état stable en cas d&amp;#8217;incident de mises à jour ou de régression.
Le déploiement des mises à jour est différentiel (pour limiter le temps de téléchargement), et surtout signé, pour avoir un confort et une sécurité comparable à un &lt;span class="caps"&gt;OS&lt;/span&gt; classique.
Démarrer sur une partition en lecture seule améliore grandement la sécurité de l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt;, il sera impossible de corrompre cette couche (les couches supérieures devront elles, faire&amp;nbsp;attention).&lt;/p&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;OS&lt;/span&gt; immuables utilisent souvent un système de paquet d&amp;#8217;applications neutre, comme FlatPack ou des conteneurs, ne fournissant que des services de&amp;nbsp;bases.&lt;/p&gt;
&lt;p&gt;Le pionnier du Linux immuable, CoreOS a été racheté par RedHat qui propose maintenant des variantes de RedHat/Fedora immuables (et forcément spécialisé) et fournit &lt;a href="https://ostreedev.github.io/ostree/"&gt;OStree&lt;/a&gt; comme outil de base pour créer des arborescences de fichiers immuables.
Au moment du rachat, Coreos a été forké en &lt;a href="https://www.flatcar.org/"&gt;FlatCar&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id="images-conteneurs"&gt;Images&amp;nbsp;conteneurs&lt;/h5&gt;
&lt;p&gt;Les conteneurs, popularisés par Docker, permettent le déploiement d&amp;#8217;applications indépendamment de l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; hôte qui va principalement fournir un noyau Linux (et donc des ressources matérielles), et contrôler son&amp;nbsp;utilisation.&lt;/p&gt;
&lt;p&gt;Les conteneurs sont isolés les uns des autres (namespace, cgroup, seccomp…), et si un conteneur est compromis ce sera compliqué pour atteindre les autres&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;p&gt;Les conteneurs permettent d&amp;#8217;avoir le même environnement sur le poste du développeur, dans l&amp;#8217;intégration continue et en pré-production puis&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;Les conteneurs adorent les signatures, et savent les utiliser de bout en&amp;nbsp;bout.&lt;/p&gt;
&lt;p&gt;Une image conteneur va embarquer tous les fichiers nécessaires pour lancer une application.
Les arborescences de fichiers sont composés de couches, seule la plus haute permet l&amp;#8217;écriture, permettant ainsi la mutualisation des couches (en lecture seule, donc) permettant un gain de place&amp;nbsp;certain.&lt;/p&gt;
&lt;p&gt;La plupart des images sont construites avec des paquets système, avant d&amp;#8217;attaquer la couche applicative qui utilisera des paquets logiciels.
Un conteneur ne doit faire qu&amp;#8217;une seule chose, et donc ne devrait pas nécessiter trop de&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Chaque conteneur pourra contenir ses propres versions de paquets, et un serveur pourra contenir moult conteneurs, multipliant ainsi les failles possibles et les besoins de mises à jour.
Il existe de multiples outils pour découvrir les systèmes de paquets utilisés, puis de confronter leurs versions à des bases de failles, comme &lt;em&gt;osv-scanner&lt;/em&gt; ou d&amp;#8217;autres services&amp;nbsp;payants.&lt;/p&gt;
&lt;p&gt;Dans une vision naïve, l&amp;#8217;image de base contiendra le contenu complet d&amp;#8217;une installation Linux; une copie de tous les fichiers, on sait jamais.
120Mo pour une Debian&amp;nbsp;12.&lt;/p&gt;
&lt;p&gt;Ensuite, en activant quelques options (comme ne jamais déployer la documentation) et en dégraissant la liste des paquets d&amp;#8217;une installation de base, on arrive aux distributions dites &lt;em&gt;slim&lt;/em&gt;.
75Mo pour une Debian 12&amp;nbsp;Slim.&lt;/p&gt;
&lt;p&gt;Google propose de faire mieux avec &lt;a href="https://github.com/GoogleContainerTools/distroless"&gt;Distroless&lt;/a&gt;, toujours avec une Debian.
2 Mo pour une version &lt;em&gt;static&lt;/em&gt; qui n&amp;#8217;utilise que 3 paquets, même pas de &lt;em&gt;libc&lt;/em&gt;, juste de quoi lancer une application statique.
La version &lt;em&gt;base&lt;/em&gt; pèse 20Mo, avec une &lt;em&gt;libc&lt;/em&gt; et &lt;em&gt;openssl&lt;/em&gt;, 15Mo sans &lt;em&gt;openssl&lt;/em&gt;.
Des variantes spécifiques à des runtimes sont aussi fourni : python, node et&amp;nbsp;java.&lt;/p&gt;
&lt;p&gt;Au-delà de l&amp;#8217;exploit, ce minimalisme drastique améliore la sécurité de l&amp;#8217;image en limitant la surface d&amp;#8217;attaque, mais surtout va limiter les faux positifs dans les analyses de&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;Les conteneurs sont issus des travaux de Google pour son Borg, qui a fourni au noyau Linux les fonctions de base pour isoler les processus, ce qui a permis la création de Docker qui a assumé l&amp;#8217;évangélisation, puis de lancer Kubernetes (le Borg pour le reste du monde).
Kubernetes a sorti de la boucle Docker en créant l&amp;#8217;Open Container Initiative et en normalisant la couche juste en dessous de Docker : &lt;a href="https://containerd.io/"&gt;Containerd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;RedHat en a profité du coup de mou de Docker pour venger cet affront innovation en créant &lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; qui utilise poliment les normes existantes et recrée tout l&amp;#8217;écosystème de Docker&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Podman est basé sur &lt;a href="https://cri-o.io/"&gt;cri-o&lt;/a&gt; l&amp;#8217;outil de bas niveau utilisé par&amp;nbsp;Containerd&lt;/li&gt;
&lt;li&gt;Podman utilise les images &lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Podman n&amp;#8217;utilise pas de démon lui préférant&amp;nbsp;systemd&lt;/li&gt;
&lt;li&gt;Podman croit très fort aux &lt;a href="https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md"&gt;conteneurs démarré sans droit d&amp;#8217;admin (axa rootless)&lt;/a&gt;, même si tout ne fonctionne de manière optimal pour&amp;nbsp;l&amp;#8217;instant&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="notaire"&gt;Notaire&lt;/h3&gt;
&lt;p&gt;Pour signer des commits de code ou des paquets, il faut utiliser un système de clé privée/public, avec une chaine de confiance permettant de diffuser les clés publiques&amp;nbsp;signées.&lt;/p&gt;
&lt;h4 id="gpg"&gt;&lt;span class="caps"&gt;GPG&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://gnupg.org/"&gt;GnuPG&lt;/a&gt; tente depuis longtemps de démocratiser la cryptographie asymétrique.
Le concept de chaine de confiance signé entre pairs n&amp;#8217;a jamais fonctionné comme il faut.
L&amp;#8217;écosystème est bancal, mal maintenu&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le serveur de clé est un&amp;nbsp;bricolage&lt;/li&gt;
&lt;li&gt;la révocation d&amp;#8217;une clé est une&amp;nbsp;tannée&lt;/li&gt;
&lt;li&gt;le format de clé est confus, personne ne sait quel type de clé il utilise et si la taille de la clé est&amp;nbsp;suffisante.&lt;/li&gt;
&lt;li&gt;le vaillant mainteneur est seul, avec des micro-contributions d&amp;#8217;autres&amp;nbsp;personnes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://keybase.io/"&gt;Keybase&lt;/a&gt; a fait un gros travail de pédagogie en fournissant des outils agréables à utiliser.
Keybase se fiche de la chaine de confiance pour crédibiliser une clé, mais propose de la lier à différente présence sur Internet, avec un système de challenge (compte de réseaux sociaux, site web personnel, porte monnaie de crypto…).
Keybase a été racheté par Zoom qui avait besoin de se payer une réputation suite à différents incidents, puis laissé en l&amp;#8217;état, en&amp;nbsp;stagnation.&lt;/p&gt;
&lt;p&gt;Debian utilise gpg et met à disposition &lt;a href="https://keyring.debian.org/"&gt;son trousseau&lt;/a&gt;, pour avoir un moyen simple de vérifier une signature, avec une politique de cooptation pour les nouveaux&amp;nbsp;arrivants.&lt;/p&gt;
&lt;p&gt;Git permet de signer avec gpg un commit ou un tag, et le kernel Linux, l&amp;#8217;utilisateur initial de git, publie aussi &lt;a href="https://korg.docs.kernel.org/pgpkeys.html"&gt;son trousseau&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je pense que l&amp;#8217;on peut &lt;a href="https://trends.google.fr/trends/explore?date=all&amp;amp;q=%2Fg%2F11c5wlscyg,%2Fm%2F09p9v"&gt;en 2024 affirmer que &lt;span class="caps"&gt;GPG&lt;/span&gt; n&amp;#8217;a pas tenu ses promesses&lt;/a&gt;, surtout son idée de chaine de&amp;nbsp;confiance.&lt;/p&gt;
&lt;h4 id="the-update-framework"&gt;The Update&amp;nbsp;Framework&lt;/h4&gt;
&lt;p&gt;Le &lt;span class="caps"&gt;CNCF&lt;/span&gt; (Cloud Native Computing Foundation) a proposé une norme pour la mise à jour de logiciels : &lt;a href="https://theupdateframework.io/"&gt;&lt;span class="caps"&gt;TUF&lt;/span&gt; (The Update Framework)&lt;/a&gt;.
Les travaux initiaux sont basés sur le système de mises à jour de &lt;a href="https://www.torproject.org/"&gt;&lt;span class="caps"&gt;TOR&lt;/span&gt;&lt;/a&gt;, et ont commencé en&amp;nbsp;2009.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TUF&lt;/span&gt; propose un framework pour sécuriser le procédure de mise à jour de paquets, en se focalisant sur les métadatas, sans empiéter sur la gestion de&amp;nbsp;paquets.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TUF&lt;/span&gt; utilise une autorité de certification, avec la clé privée qui est hors ligne, la clé publique est fourni avec le client.
Tous les efforts sont focalisés sur diminuer les dégâts en cas de compromissions de clés, pour ça &lt;span class="caps"&gt;TUF&lt;/span&gt; utilise une délégation de confiance avec des rôles précis, et des certificats de courtes&amp;nbsp;durées.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TUF&lt;/span&gt; a beaucoup plus à l&amp;#8217;industrie automobile qui l&amp;#8217;utilise via le projet &lt;a href="https://uptane.org/"&gt;uptane&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker a implémenté &lt;span class="caps"&gt;TUF&lt;/span&gt; avec &lt;a href="https://github.com/notaryproject/notary"&gt;Notary&lt;/a&gt;, avant de le sortir de la marque Docker.
Notary utilise une très classique &lt;span class="caps"&gt;PKI&lt;/span&gt; pour signer/vérifier, mais surtout lie aux images une &lt;a href="https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#trust-policy"&gt;police de confiance&lt;/a&gt;, un ensemble de règles disant qui peut signer où, avec quelle autorité de&amp;nbsp;certification.&lt;/p&gt;
&lt;p&gt;Python proscranise depuis 10 ans avec sa &lt;a href="https://peps.python.org/pep-0480/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt;-0480&lt;/a&gt; pour savoir si ils vont faire du &lt;span class="caps"&gt;TUF&lt;/span&gt;.
Pourtant, l&amp;#8217;implémentation de référence est en&amp;nbsp;Python.&lt;/p&gt;
&lt;h4 id="sigstore"&gt;Sigstore&lt;/h4&gt;
&lt;p&gt;En s&amp;#8217;appuyant sur les concepts de &lt;span class="caps"&gt;TUF&lt;/span&gt;, &lt;a href="https://www.sigstore.dev/"&gt;Sigstore&lt;/a&gt; propose un système de signature sans clé (keyless signing, mais le terme enthousiaste cache une clé à courte vie), pour tout l&amp;#8217;écosystème open source, en espérant une adoption massive et une sécurité accrue, comme l&amp;#8217;a fait &lt;a href="https://letsencrypt.org/"&gt;Let&amp;#8217;s Encrypt&lt;/a&gt; pour le &lt;span class="caps"&gt;TLS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Sigstore est directement sous l&amp;#8217;égide la Linux Foundation, sans passer par le &lt;span class="caps"&gt;CNCF&lt;/span&gt; (qui dépend de la Linux Foundation&amp;nbsp;aussi).&lt;/p&gt;
&lt;p&gt;Pour afficher son côté neutre, les clés d&amp;#8217;autorités sont des clés matérielles, détenues par 5 experts, venus de l&amp;#8217;industrie et de l&amp;#8217;université, pour une durée d&amp;#8217;à peu près 18 mois, avec renouvèlement des signatures tous les 4&amp;nbsp;mois.&lt;/p&gt;
&lt;p&gt;Le principe de fonctionnement de Sigstore est décrit dans une publication scientifique &lt;a href="https://dl.acm.org/doi/pdf/10.1145/3548606.3560596"&gt;Sigstore: Logiciel signé pour tout le monde&lt;/a&gt;, signé par deux devs de chez &lt;a href="https://www.chainguard.dev/"&gt;Chainguard&lt;/a&gt; et un chercheur de l&amp;#8217;université de Purdue.
L&amp;#8217;un des développeurs publie des trucs sur &lt;span class="caps"&gt;TOR&lt;/span&gt;, assez à cheval sur la sécurité, les chaines de confiance et les mises à jours&amp;nbsp;nickels.&lt;/p&gt;
&lt;p&gt;Chainguard, startup de la supply chain, a été fondé par des gens qui ont bossé chez Microsoft, Google, dans l&amp;#8217;écosystème Kubernetes, avec &lt;a href="https://knative.dev"&gt;KNative&lt;/a&gt; par exemple.
Il est donc logique que l&amp;#8217;on retrouve du Github (qui appartient à Microsoft) et du Google à tous les étages de&amp;nbsp;Sigstore.&lt;/p&gt;
&lt;h5 id="deux-services-isoles"&gt;Deux services&amp;nbsp;isolés&lt;/h5&gt;
&lt;p&gt;Sigstore s&amp;#8217;appuie sur deux services&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/fulcio"&gt;Fulcio&lt;/a&gt;, la &lt;span class="caps"&gt;PKI&lt;/span&gt; qui va signer la clé temporaire utilisée pour signer le paquet, après que l&amp;#8217;utilisateur se soit authentifié avec &lt;a href="https://fr.wikipedia.org/wiki/OpenID_Connect"&gt;OpenID Connect&lt;/a&gt; (chez Github ou Google par exemple), en promettant d&amp;#8217;utiliser de l&amp;#8217;authentification à deux facteurs (&lt;span class="caps"&gt;OIDC&lt;/span&gt; ne précise pas l&amp;#8217;utilisation de &lt;span class="caps"&gt;2FA&lt;/span&gt; dans son&amp;nbsp;token).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/rekor"&gt;Rekor&lt;/a&gt;, va enregistrer les étapes de la signature d&amp;#8217;un paquet dans un journal public qui n&amp;#8217;efface rien (reprenant la logique de publicité des autorités de certification de &lt;a href="https://certificate.transparency.dev/"&gt;Certificate Transparency&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="oidc"&gt;&lt;span class="caps"&gt;OIDC&lt;/span&gt;&lt;/h5&gt;
&lt;p&gt;sigstore.dev utilise &lt;a href="https://dexidp.io/"&gt;Dex&lt;/a&gt; pour l&amp;#8217;authentification &lt;span class="caps"&gt;OIDC&lt;/span&gt;, avec Github, Google et Azur comme cibles, ce qui permet de choisir depuis une page web sans avoir à préciser un &lt;span class="caps"&gt;OIDC&lt;/span&gt; tiers lors de la&amp;nbsp;signature.&lt;/p&gt;
&lt;h5 id="clients"&gt;Clients&lt;/h5&gt;
&lt;p&gt;La discussion avec Sigstore se fait en &lt;span class="caps"&gt;REST&lt;/span&gt;, avec les outils de &lt;span class="caps"&gt;SSL&lt;/span&gt;.
Vous pouvez reproduire la danse de Sigstore en bricolant sur un coin de table&amp;nbsp;avec &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;openssl&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt; et &lt;code&gt;base64&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sigstore fournit un client de référence, &lt;a href="https://github.com/sigstore/cosign"&gt;cosign&lt;/a&gt; écrit en go.
Sa bibliothèque Python, &lt;a href="https://github.com/sigstore/sigstore-python"&gt;sigstore-python&lt;/a&gt;, propose aussi un cli (capable de &lt;a href="https://github.com/di/id#supported-environments"&gt;deviner le token &lt;span class="caps"&gt;OIDC&lt;/span&gt; dans une &lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Github (même s&amp;#8217;il ne fait que de l&amp;#8217;Oauth2 et pas de l&amp;#8217;&lt;span class="caps"&gt;OIDC&lt;/span&gt;) et &lt;a href="https://docs.gitlab.com/ee/ci/yaml/signing_examples.html"&gt;Gitlab&lt;/a&gt; fournissent les outils pour utiliser Sigstore (vérification et signature) dans l&amp;#8217;intégration continue, dans le but d&amp;#8217;atteindre le niveau 3 de la certification &lt;a href="https://slsa.dev/"&gt;&lt;span class="caps"&gt;SLSA&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La doc de Sigstore évoque même Jenkins, il n&amp;#8217;y a donc pas d&amp;#8217;inquiétudes sur la possibilité d&amp;#8217;intégrer Sigstore à n&amp;#8217;importe quelle &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;h5 id="validation"&gt;Validation&lt;/h5&gt;
&lt;p&gt;La procédure de validation d&amp;#8217;un paquet est tout à fait classique, avec du json, du base64 et des certificats&amp;nbsp;x509.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validation de la clé publique&amp;nbsp;:&lt;/li&gt;
&lt;li&gt;signature par l&amp;#8217;autorité de&amp;nbsp;certification&lt;/li&gt;
&lt;li&gt;émetteur&amp;nbsp;crédible&lt;/li&gt;
&lt;li&gt;source &lt;span class="caps"&gt;OIDC&lt;/span&gt;&amp;nbsp;crédible&lt;/li&gt;
&lt;li&gt;auteur crédible (un mail ou un build depuis une &lt;span class="caps"&gt;CI&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;l&amp;#8217;horodatage signé est dans la période de validité du&amp;nbsp;certificat&lt;/li&gt;
&lt;li&gt;Vérification que la création de la clé apparait comme il faut dans le journal&amp;nbsp;Rekor&lt;/li&gt;
&lt;li&gt;Calcul du hash de l&amp;#8217;artéfact et validation de sa signature avec cette clé publique&amp;nbsp;éphémère&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="signature"&gt;Signature&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Création d&amp;#8217;une paire de clé&amp;nbsp;publique/privée&lt;/li&gt;
&lt;li&gt;Création d&amp;#8217;un certificat X509 avec des attributs confirmables par le token &lt;span class="caps"&gt;OIDC&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Le certificat contient un challenge (aléatoire) et la signature effectuée avec la clé&amp;nbsp;privée.&lt;/li&gt;
&lt;li&gt;Récupération d&amp;#8217;un token &lt;span class="caps"&gt;OIDC&lt;/span&gt;, via sa &lt;span class="caps"&gt;CI&lt;/span&gt;, ou via Fulcio qui est une application&amp;nbsp;OAuth2&lt;/li&gt;
&lt;li&gt;Envoie du certificat éphémère public et du token &lt;span class="caps"&gt;OIDC&lt;/span&gt; à Fulsio, qui vérifie la signature du token, puis &lt;a href="https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md"&gt;la cohérence des champs entre le certificat et le token&lt;/a&gt;. Fulcio raconte tout ça à Rekor, renvoie le certificat signé pour 10&amp;nbsp;minutes&lt;/li&gt;
&lt;li&gt;Calcul du hash de l&amp;#8217;artéfact et signature avec la clé&amp;nbsp;privée&lt;/li&gt;
&lt;li&gt;Création d&amp;#8217;un &lt;a href="https://en.wikipedia.org/wiki/Trusted_timestamping"&gt;horodatage signée&lt;/a&gt; selon le &lt;a href="https://www.rfc-editor.org/rfc/rfc3161"&gt;&lt;span class="caps"&gt;RFC3161&lt;/span&gt;&lt;/a&gt; avec un service tiers comme &lt;a href="https://freetsa.org"&gt;freeTSA&lt;/a&gt; ou &lt;a href="https://github.com/sigstore/timestamp-authority"&gt;Sigstore Timestamp&amp;nbsp;Authority&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Envoi de la signature, du hash et du certificat sur&amp;nbsp;Rekor&lt;/li&gt;
&lt;li&gt;Envoi de l&amp;#8217;horodatage signé sur&amp;nbsp;Rekor&lt;/li&gt;
&lt;li&gt;Création d&amp;#8217;un bundle, un json avec des blobs en base64 à fournir avec&amp;nbsp;l&amp;#8217;artéfact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le certificat pourra être utilisé plusieurs fois pendant sa durée de vie.
La clé privée sera détruite, sans jamais avoir touché le disque&amp;nbsp;dur.&lt;/p&gt;
&lt;p&gt;Fulcio préconise l&amp;#8217;utilisation de gestionnaire de clés (matériel, services &lt;span class="caps"&gt;KMS&lt;/span&gt; des clouds, &lt;a href="https://www.hashicorp.com/products/vault"&gt;Vault d&amp;#8217;Hashicorp&lt;/a&gt;) et en dernier recours, d&amp;#8217;une clé privée sur un&amp;nbsp;fichier.&lt;/p&gt;
&lt;p&gt;Pour héberger un service &lt;span class="caps"&gt;TSA&lt;/span&gt;, il faut prévoir du hardware pour avoir une horloge rigoureuse, comme un &lt;span class="caps"&gt;GPS&lt;/span&gt;.&lt;/p&gt;
&lt;h5 id="paquets"&gt;Paquets&lt;/h5&gt;
&lt;p&gt;Sigstore fournit des outils spécifiques aux gestionnaires de paquets suivant&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;cosign&lt;/em&gt; sait publier la signature d&amp;#8217;une image de conteneur sur un dépôt &lt;a href="https://opencontainers.org/"&gt;&lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/a&gt; (comme&amp;nbsp;Docker)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/sigstore-go"&gt;Golang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/sigstore-python"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/sigstore-rs"&gt;Rust&lt;/a&gt; (instable, en version 0.8 pour&amp;nbsp;l&amp;#8217;instant)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/sigstore-java"&gt;Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sigstore/sigstore-js"&gt;Javascript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il est possible de signer un commit git via Sigstore, avec &lt;a href="https://github.com/sigstore/gitsign"&gt;gitsign&lt;/a&gt;, qui utilise pour ça &lt;span class="caps"&gt;GPG&lt;/span&gt; avec des documents au format x509. Oui, git sait utiliser une &lt;span class="caps"&gt;PKI&lt;/span&gt; comme tout le&amp;nbsp;monde.&lt;/p&gt;
&lt;p&gt;Pour les paquets, plus que le nom de la personne qui a signé, ou le numéro de série du build, c&amp;#8217;est la notion de &lt;a href="https://slsa.dev/spec/v1.0/provenance"&gt;provenance&lt;/a&gt; qui&amp;nbsp;importe.&lt;/p&gt;
&lt;p&gt;Dans ce fouillis de cascade de normes, il ne faut non plus oublier &lt;a href="https://in-toto.io/"&gt;in-toto&lt;/a&gt; (sour l&amp;#8217;égide de la Linux Foundation), pour spécifier les méta-datas d&amp;#8217;un&amp;nbsp;paquet.&lt;/p&gt;
&lt;h4 id="openpubkey"&gt;OpenPubKey&lt;/h4&gt;
&lt;p&gt;Un autre consortium, composé de &lt;a href="https://www.bastionzero.com/"&gt;BastionZero&lt;/a&gt; et de Docker, propose leur propre norme pour signer des clés publiques après une authentification &lt;span class="caps"&gt;OIDC&lt;/span&gt; : &lt;a href="https://github.com/openpubkey/openpubkey"&gt;OpenPubKey&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;OpenPubKey est aussi un projet de la Linux Foundation, et il est utilisé par le registre public de&amp;nbsp;Docker.&lt;/p&gt;
&lt;p&gt;Ils ont publié leur &lt;a href="https://eprint.iacr.org/2023/296"&gt;white paper&lt;/a&gt; expliquant leur&amp;nbsp;démarche.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée d&amp;#8217;OpenPubkey est de créer une paire de clés publique/privée et un défi (un nombre aléatoire), de le signer avec la clé privé.
La clé publique, le défi, la signature soit utilisé&amp;nbsp;comme &lt;code&gt;nonce&lt;/code&gt; pour demander un jeton &lt;span class="caps"&gt;OIDC&lt;/span&gt;.
Normalement,&amp;nbsp;un &lt;code&gt;nonce&lt;/code&gt; est un nombre aléatoire utilisé lors de l&amp;#8217;échange OAuth2, en utilisant le triplet défi-clé-signature, avec un défi de taille suffisante, ça ne remettra pas en cause l&amp;#8217;entropie lors de&amp;nbsp;l&amp;#8217;échange.&lt;/p&gt;
&lt;p&gt;Pour un jeton &lt;span class="caps"&gt;OIDC&lt;/span&gt; émis lors d&amp;#8217;une &lt;span class="caps"&gt;CI&lt;/span&gt;, il n&amp;#8217;y a pas&amp;nbsp;de &lt;code&gt;nonce&lt;/code&gt;, et ce sera&amp;nbsp;donc &lt;code&gt;aud&lt;/code&gt; qui sera détourné pour faire signer sa clé par &lt;span class="caps"&gt;OIDC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Le jeton ne permettra pas de faire grand chose (son scope est &amp;#8220;openid profile email&amp;#8221;) et sera rapidement périmé.
Lors de la vérification de la clé publique, le&amp;nbsp;champs &lt;code&gt;exp&lt;/code&gt; du token ne sera pas pris en compte (il est périmé comme jeton &lt;span class="caps"&gt;OIDC&lt;/span&gt;, mais pas comme preuve), et il sera possible d&amp;#8217;utiliser la date d&amp;#8217;émission&amp;nbsp;(&lt;code&gt;iat&lt;/code&gt;) comme date fiable d&amp;#8217;émission de la&amp;nbsp;preuve.&lt;/p&gt;
&lt;p&gt;Le jeton peut être vérifié à partir de la clé publique du service &lt;span class="caps"&gt;OIDC&lt;/span&gt;, mais il y aura une rotation (entre 15 jours et 3 mois pour les gros services &lt;span class="caps"&gt;OIDC&lt;/span&gt;), et le papier évoque l&amp;#8217;utilisation d&amp;#8217;un journal de signature, pour avoir une conservation sur un temps&amp;nbsp;long.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.sigstore.dev/openpubkey-and-sigstore/"&gt;Sigstore cause de OpenPubKey dans son blog&lt;/a&gt;, reconnait la simplicité de leur approche, mais doute fort de la possibilité de vérifier une clé sur un temps long, et hurle sur l&amp;#8217;idée de laisser trainer un token &lt;span class="caps"&gt;JWT&lt;/span&gt; n&amp;#8217;importe où (oui, ce n&amp;#8217;est clairement pas&amp;nbsp;orthodoxe).&lt;/p&gt;
&lt;h3 id="mises-a-jour-des-dependances"&gt;Mises à jour des&amp;nbsp;dépendances&lt;/h3&gt;
&lt;p&gt;Il est important d&amp;#8217;avoir une politique fluide de mises à jour de paquets, et de faire des déploiements d&amp;#8217;un service sans nouvelles fonctionnalités/corrections, juste pour des mises à&amp;nbsp;jour.&lt;/p&gt;
&lt;p&gt;Pour la chasse aux paquets troués, tâche ô combien rébarbatives, il faut un robot et une bonne couverture de tests.
Tous les gestionnaires de paquets ont une &lt;span class="caps"&gt;API&lt;/span&gt; distante et un cli pour faire ça, mais des outils comme le &lt;a href="https://github.com/dependabot/dependabot-core"&gt;dependabot&lt;/a&gt; de Github, bien que non libre, a le bon gout de fluidifier les mises à jour, en créant une demande de fusion (pull request), qui va déclencher l&amp;#8217;intégration continue, et donc les&amp;nbsp;tests.&lt;/p&gt;
&lt;p&gt;Il ne faut pas que cette tâche dépende de la disponibilité ou de l&amp;#8217;humeur d&amp;#8217;un développeur, c&amp;#8217;est un travail de&amp;nbsp;robot.&lt;/p&gt;
&lt;h2 id="securiser-la-chaine-dapprovisionnement"&gt;Sécuriser la chaine&amp;nbsp;d&amp;#8217;approvisionnement&lt;/h2&gt;
&lt;p&gt;Pour ce point, il faut des gestionnaires de paquets en bon état, de l&amp;#8217;authentification forte pour les contributeurs, une &lt;span class="caps"&gt;PKI&lt;/span&gt; pour signer un peu partout, la possibilité d&amp;#8217;auditer les signatures et de l&amp;#8217;automatisation pour les montées en&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;Tout ça&amp;nbsp;existe.&lt;/p&gt;</content><category term="Dev"></category><category term="github"></category><category term="openssf"></category><category term="tuf"></category><category term="sigstore"></category><category term="security"></category></entry><entry><title>Comment vendre une base de données open source</title><link href="http://blog.garambrogne.net/comment-vendre-une-base-de-donnees-open-source.html" rel="alternate"></link><published>2023-10-02T09:20:00+02:00</published><updated>2023-10-02T09:20:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2023-10-02:/comment-vendre-une-base-de-donnees-open-source.html</id><summary type="html">&lt;p&gt;Influxdb vient de sortir la version 3 de sa base de données (orientée time-series), avec une petite astuce sur le modèle de licence, pour continuer à faire de l&amp;#8217;open-source sans se faire piller, comme certain de ses&amp;nbsp;prédécesseurs.&lt;/p&gt;</summary><content type="html">&lt;h1 id="comment-vendre-une-base-de-donnees-open-source"&gt;Comment vendre une base de données open&amp;nbsp;source&lt;/h1&gt;
&lt;p&gt;Influxdata vient de sortir la version 3 d&amp;#8217;Influxdb sa base de données &amp;#8220;temporelles&amp;#8221;, avec une petite astuce sur le modèle de licence, pour continuer à faire de l&amp;#8217;open-source sans se faire piller, comme certain de ses&amp;nbsp;prédécesseurs.&lt;/p&gt;
&lt;p&gt;Mais commençons par le&amp;nbsp;commencement.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lac d'Aiguebelette" class="image-process-article-image" src="/images/comment-vendre-le-lac-d-aiguebelette.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="lhistoire-ancienne"&gt;L&amp;#8217;histoire&amp;nbsp;ancienne&lt;/h2&gt;
&lt;h3 id="postgresql"&gt;Postgresql&lt;/h3&gt;
&lt;p&gt;1996 &lt;span class="caps"&gt;MIT&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Ingres, est né en 1982 comme projet universitaire à Berkley, pour travailler sur la notion de base de données relationnelle. Le leader de l&amp;#8217;équipe Ingres va créer une entreprise, Ingres, puis finalement revenir à la fac et créer un deuxième produit, Post-Ingres, aka Postgres.
Bascule vers le &lt;span class="caps"&gt;SQL&lt;/span&gt; en 1994, changement de noms en PostgreSQL en 96, début de la présence sur Internet avec la version 6, peu de temps après Mysql finalement. A part le faux départ Ingres, Postgresql a toujours été open source (&lt;span class="caps"&gt;MIT&lt;/span&gt;, donc team &lt;span class="caps"&gt;BSD&lt;/span&gt;) et maintenu par sa&amp;nbsp;communauté.&lt;/p&gt;
&lt;p&gt;Postgres a un écosystème complet, &lt;a href="https://github.com/dhamaniasad/awesome-postgres"&gt;awesome&lt;/a&gt; comme on dit, qui va de la haute disponibilité avec &lt;a href="https://github.com/zalando/patroni"&gt;Patroni&lt;/a&gt; de Zalando (en &lt;span class="caps"&gt;MIT&lt;/span&gt;), à de la sauvegarde continue avec &lt;a href="https://pgbarman.org/"&gt;Barman&lt;/a&gt;
 de &lt;a href="http://www.2ndquadrant.com/"&gt;2ndquadrant&lt;/a&gt; (en &lt;span class="caps"&gt;GPL&lt;/span&gt;-3).&lt;/p&gt;
&lt;p&gt;Personne n&amp;#8217;est arrivé à accaparer Postgresql, fidèle à sa licence &lt;span class="caps"&gt;MIT&lt;/span&gt;, il permet l&amp;#8217;apparition d&amp;#8217;entreprise proposant des services complexes (et open source), en parallèle avec des outils&amp;nbsp;communautaires.&lt;/p&gt;
&lt;h3 id="mysql"&gt;Mysql&lt;/h3&gt;
&lt;p&gt;1995 &lt;span class="caps"&gt;GPL&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Mysql a été crée comme un clone libre du maintenant oublié &lt;strong&gt;msql&lt;/strong&gt;, par Mysql &lt;span class="caps"&gt;AB&lt;/span&gt;, comme le récite Wikipedia et toutes les pages qui le cite.
Pas de trace d&amp;#8217;une version antérieur à la version 3, il est donc facile d&amp;#8217;imaginer qu&amp;#8217;il y a eut deux versions privées, avant de créer l&amp;#8217;entreprise éponyme, puis de commencer la conquête d&amp;#8217;Internet en 1995, qui est une bonne date pour commencer un truc sur Internet : c&amp;#8217;est l&amp;#8217;arrivée du grand public sur le grand réseau&amp;nbsp;mondial.&lt;/p&gt;
&lt;p&gt;Mysql, est suffisamment souple pour propulser tout le web, du simple &lt;span class="caps"&gt;LAMP&lt;/span&gt; (Linux/Apache/Mysql/&lt;span class="caps"&gt;PHP&lt;/span&gt;) aux premiers GAFAs (Facebook, Flickr, Twitter, Wikipedia, Youtube, plus tard Github&amp;nbsp;…).&lt;/p&gt;
&lt;p&gt;Le bizness model ne suit pas, rachat par Sun, qui frustre le vendeur qui fork en MariaDB, et patatra, Oracle rachète un Sun moribond, et met la main sur ce qui reste moralement son&amp;nbsp;concurrent.&lt;/p&gt;
&lt;p&gt;Les gros utilisateurs bricolent de la réplication à échelle titanesque&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.blog/2016-08-01-gh-ost-github-s-online-migration-tool-for-mysql/"&gt;Github parle de son &lt;strong&gt;gh-ost&lt;/strong&gt;&lt;/a&gt; qui deviendra un produit (très confidentiel) sous le nom d&amp;#8217;&lt;a href="https://code.openark.org/blog/"&gt;openark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.twitter.com/engineering/en_us/a/2010/introducing-gizzard-a-framework-for-creating-distributed-datastores"&gt;Twitter parle de son &lt;strong&gt;Gizzard&lt;/strong&gt;&lt;/a&gt; qui a commencé par répliqué du Mysql avant de passer à autre chose. Mysql qui est toujours utilisé d&amp;#8217;après leur billet récapitulatif de 2017 &lt;a href="https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale"&gt;Scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://engineering.fb.com/?s=mysql"&gt;Facebook parle régulièrement de Mysql&lt;/a&gt; et fournit &lt;a href="http://myrocks.io/"&gt;myrocks&lt;/a&gt;, un fork de Mysql 5.6, sous licence &lt;span class="caps"&gt;GPL&lt;/span&gt;-2, qui utilise son moteur de stockage, rocksdb (successeur du leveldb de Google). &lt;a href="https://docs.percona.com/percona-server/8.0/myrocks/install.html"&gt;Percona propose une version qui tourne comme module pour Mysql&amp;nbsp;8.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vitess.io/"&gt;Vitess&lt;/a&gt; est le proxy utilisé par Youtube pour scaler du Mysql. Ce proxy a mis beaucoup de temps pour causer mysql avec les clients (et enfin devenir un vrai&amp;nbsp;proxy).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;#8217;entreprise &lt;a href="https://en.wikipedia.org/wiki/Percona"&gt;Percona&lt;/a&gt; est la référence de fait pour Mysql, hors Oracle, et proposent des outils, de l&amp;#8217;infogérence et surtout du&amp;nbsp;consulting.&lt;/p&gt;
&lt;p&gt;Il ne faut pas se mentir, le rachat de Mysql par Oracle en 2010 (via Sun en 2008), a cassé l&amp;#8217;ambiance. Mariadb permet de rester droit dans ses bottes face au grand méchant Oracle, mais c&amp;#8217;est tout, le fork d&amp;#8217;arrière garde n&amp;#8217;a pas les épaules pour proposer des innovations, ou même toucher quoi que ce&amp;nbsp;soit.&lt;/p&gt;
&lt;p&gt;Mysql a une chouette histoire, mais cette histoire est figé dans le marbre, et cette stabilité depuis toujours, pour toujours est ce que recherche ses utilisateurs.
À part pour créer un Wordpress de plus, qui donc choisi Mysql pour un nouveau projet après&amp;nbsp;2020?&lt;/p&gt;
&lt;h3 id="sqlite"&gt;Sqlite&lt;/h3&gt;
&lt;p&gt;2000 Public&amp;nbsp;Domain&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/SQLite"&gt;Sqlite&lt;/a&gt; est une base de données relationnelles, avec tout ce qu&amp;#8217;il faut de &lt;span class="caps"&gt;SQL&lt;/span&gt;, mais sans réseau, sans accès concurrents.
Longtemps cantonné au rôle de base de données pour crétin qui ne savent pas installer une vraie base de données, Sqlite est maintenant la référence pour stocker des données complexes de manière pérenne, largement utilisé dans les téléphones&amp;nbsp;portables.&lt;/p&gt;
&lt;h2 id="lere-du-20"&gt;L&amp;#8217;ère du&amp;nbsp;2.0&lt;/h2&gt;
&lt;p&gt;Le Web a démarré en s&amp;#8217;appuyant sur des outils existants, avant de commencer à créer des choses originales. Github devient rapidement incontournable et les utilisateurs distribuent des ⭐️ comme un gastronome de chez&amp;nbsp;Michelin.&lt;/p&gt;
&lt;h3 id="mongodb"&gt;Mongodb&lt;/h3&gt;
&lt;p&gt;2007 24k⭐️ &lt;span class="caps"&gt;SSPL&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Mongodb arrive en fanfare, pour sauver le web du méchant &lt;span class="caps"&gt;SQL&lt;/span&gt; et du modèle&amp;nbsp;relationnel.&lt;/p&gt;
&lt;p&gt;Bruyant et malhonnête (écriture sans sync pour aller plus vite, surconsommation de disques durs avant l&amp;#8217;arrivé de Tiger…), il s&amp;#8217;est ensuite calmé, et à forcé les autres bases de données à évoluer pour avoir du typage plus complexe (listes, tableaux…) ou plus simple (hstore, json…), plus proche des ORMs, par ce que ne nous mentons pas, de la base de données sans modèle, ça n&amp;#8217;existe pas, le modèle est soit explicite, soit tacites.&amp;nbsp;Mongo&lt;/p&gt;
&lt;p&gt;Mongodb sera la première victime de la cloudification, et de manière prévisible, ça fera beaucoup de&amp;nbsp;bruits.&lt;/p&gt;
&lt;h3 id="rethinkdb"&gt;Rethinkdb&lt;/h3&gt;
&lt;p&gt;2009 26k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;En 2009, &lt;a href="https://github.com/rethinkdb/rethinkdb"&gt;Rethinkdb&lt;/a&gt; trouve que Mongodb est un petit bras, et qu&amp;#8217;il est possible d&amp;#8217;exposer un &lt;span class="caps"&gt;AST&lt;/span&gt; dans le client pour devenir une requête dans la db. Création d&amp;#8217;une boite avec le core dev, licence Apache&amp;nbsp;2.&lt;/p&gt;
&lt;p&gt;Ça ne prends pas, mais 26k étoiles sur&amp;nbsp;Github.&lt;/p&gt;
&lt;p&gt;Pivot sur l&amp;#8217;aspect temps réel (des événements pour les clients restent synchronisés), ça ne prends pas non&amp;nbsp;plus.&lt;/p&gt;
&lt;p&gt;Gros coup de frein en&amp;nbsp;2015.&lt;/p&gt;
&lt;p&gt;Le core-dev tente un reboot base sur FondationDB, &lt;a href="https://github.com/srh/refound"&gt;Refound&lt;/a&gt;, qui cartonne avec 11 étoiles sur&amp;nbsp;Github.&lt;/p&gt;
&lt;h3 id="arangodb-ex-advocadodb"&gt;Arangodb, ex&amp;nbsp;Advocadodb&lt;/h3&gt;
&lt;p&gt;2012 13k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;En 2012, encore dans la hype de Mongodb qui est un petit bras, &lt;a href="https://github.com/arangodb/arangodb"&gt;Arangodb&lt;/a&gt; propose en plus du modèle orienté document, un modèle orienté graphe, et même du clef/valeur.
En plus des requêtes en javascript comme Mongo, il y a un Query&amp;nbsp;Language.&lt;/p&gt;
&lt;p&gt;Gros coup de frein en 2017, mais un score honorable avec 13 milles étoiles&amp;nbsp;Github.&lt;/p&gt;
&lt;h3 id="redis"&gt;Redis&lt;/h3&gt;
&lt;p&gt;2009&amp;nbsp;62k⭐️&lt;/p&gt;
&lt;p&gt;Redis débute sa vie comme moteur de cache, du clef/valeur non relationnel avec des types complexes et les opérations adéquates pour les manipuler. Redis méprise les disques durs, tout doit tenir en &lt;span class="caps"&gt;RAM&lt;/span&gt;, mais bon, les tailles des barrettes ont grandis avec lui, et il y a quand même de la persistance (instantanés et journalisation) pour repartir directement cas de reboot. Il gère la réplication et la haute&amp;nbsp;dispo.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;échappera pas à la&amp;nbsp;cloudification.&lt;/p&gt;
&lt;h3 id="elasticsearch"&gt;Elasticsearch&lt;/h3&gt;
&lt;p&gt;2010&amp;nbsp;65k⭐️&lt;/p&gt;
&lt;p&gt;Elasticsearch commence par rendre accessible le trop théorique Lucene, outil de recherche full text, pour finalement devenir une base de données orientées colonnes, agréable à utiliser en&amp;nbsp;cluster.&lt;/p&gt;
&lt;p&gt;Elasticsearch a ensuite su pivoter, pour devenir un stockeur de logs, puis de time series, et de se diversifier avec tout un écosystème un peu confus avec des morceaux de libre mélangé avec des morceaux propriétaires, mais&amp;nbsp;open-source.&lt;/p&gt;
&lt;p&gt;Ça ne l&amp;#8217;empêchera pas de se faire cloudifier&amp;nbsp;aussi.&lt;/p&gt;
&lt;h3 id="clickhouse"&gt;Clickhouse&lt;/h3&gt;
&lt;p&gt;2009 31k⭐️&amp;nbsp;Apache&lt;/p&gt;
&lt;p&gt;Profitant de la hype des bases orientés colonnes, fort pratique pour analyser le comportement de ses clients, Yandex, le Yahoo des russes, libère son &lt;a href="https://clickhouse.yandex/"&gt;Clickhouse&lt;/a&gt; sous licence Apache.
Yandex permet d&amp;#8217;entasser trop de données, de jeter du &lt;span class="caps"&gt;SQL&lt;/span&gt; dessus, et d&amp;#8217;obtenir des résultats rapidement sans passer par la case map-reduce du monde&amp;nbsp;Hadoop.&lt;/p&gt;
&lt;p&gt;ClickHouse est maintenant une boîte à San Francisco et Amsterdam, lève des fonds, et de chouettes projets commencent à l&amp;#8217;utiliser (comme &lt;a href="https://github.com/PostHog/posthog"&gt;PostHog&lt;/a&gt;). C&amp;#8217;est la phase &amp;#8220;lune de miel&amp;#8221;, tout est merveilleux, il faut devenir un standard de fait, puis on parlera bizness à ce moment&amp;nbsp;là.&lt;/p&gt;
&lt;p&gt;De manière très classique, Clickhouse se gère facilement sur une seul instance, bare metal ou conteneur, par contre, pour le cluster, il y a &lt;span class="caps"&gt;LE&lt;/span&gt; chois technique de l&amp;#8217;équipe, avec un gros zookeeper, et comme ça piaille, ils ont fourni une &lt;span class="caps"&gt;API&lt;/span&gt; pour utiliser autre chose, mais comme ils n&amp;#8217;utiliseront pas les alternatives en prod, bah, ce sera en l&amp;#8217;état, à la communauté de bosser. Des outils tiers commencent à apparaitre, comme &lt;a href="https://github.com/PostHog/HouseWatch"&gt;HouseWatch&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="leveldb"&gt;Leveldb&lt;/h3&gt;
&lt;p&gt;2011 34k⭐️ New &lt;span class="caps"&gt;BSD&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Google explique sa stratégie d&amp;#8217;écriture sans modification (mais en consolidant périodiquement), et l&amp;#8217;importance d&amp;#8217;avoir des clefs ordonnées dans une base clef/valeur. Héritière de &lt;a href="https://research.google/pubs/pub27898/"&gt;BigTable&lt;/a&gt;, &lt;a href="https://github.com/google/leveldb"&gt;leveldb&lt;/a&gt; est une réécriture complète, par ce que BigTable s&amp;#8217;appuie sur des bibliothèques qui n&amp;#8217;ont pas vocation à être&amp;nbsp;libérées.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est du simple clef/valeur ordonnées, avec des itérations efficaces sur les clefs (d&amp;#8217;où l&amp;#8217;intérêt de les garder ordonnées), et des écritures par&amp;nbsp;lot.&lt;/p&gt;
&lt;p&gt;Pas de produit, pas de&amp;nbsp;service.&lt;/p&gt;
&lt;h3 id="rocksdb"&gt;Rocksdb&lt;/h3&gt;
&lt;p&gt;2012 26k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;Facebook aime bien le concept de Leveldb et fork en &lt;a href="https://github.com/facebook/rocksdb/"&gt;Rocksdb&lt;/a&gt; pour d&amp;#8217;aller plus loin sur le tuning spécifique au &lt;span class="caps"&gt;SSD&lt;/span&gt; (qui était nouveau à cette époque), d&amp;#8217;exposer les journaux d&amp;#8217;écritures pour avoir de la réplication, et de l&amp;#8217;utiliser comme couche basse de persistance, un peu partout, comme Mysql par exemple avec Myrocks, ou simplement du&amp;nbsp;cache.&lt;/p&gt;
&lt;h3 id="foundationdb"&gt;Foundationdb&lt;/h3&gt;
&lt;p&gt;2017 13k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;Apple reprend le concept d&amp;#8217;une base clefs/valeurs ordonnées avec des écritures par lots, mais se focalise sur la notion de réplication cohérente au sein d&amp;#8217;un cluster et
 sort discrètement &lt;a href="https://github.com/apple/foundationdb"&gt;Foundationdb&lt;/a&gt;.
Comme &amp;#8220;fondation&amp;#8221;, elle se positionne comme outil de base niveau, sans protocole réseau spécifié, mais en fournissant le client adéquat dans différents langages (C, Ruby, Python, Java, Golang).
Orienté performance, gros volume et fiabilité, &lt;a href="https://apple.github.io/foundationdb/data-modeling.html"&gt;Foundationdb demande à l&amp;#8217;utilisateur de faire des efforts&lt;/a&gt;, en dénormalisant son modèle, en utilisant une serialisation qui ne casse pas l&amp;#8217;ordre des clefs, et en recommandant de ne pas dépasser le ko pour les données, au pire 10k si on sait que l&amp;#8217;on ne pas écrire/itérer trop souvent dans cette table, et la limite dure est&amp;nbsp;100ko.&lt;/p&gt;
&lt;p&gt;Foundationdb se positionne clairement comme outil sur lequel construire des services (et potentiellement facturer), ce qu&amp;#8217;Apple fait en&amp;nbsp;interne.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://deno.com/kv"&gt;Deno &lt;span class="caps"&gt;KV&lt;/span&gt;&lt;/a&gt; utilise Fundationdb, mais reste une arme secrète pour l&amp;#8217;offre d&amp;#8217;hébergement de Deno, les offres plus ou moins serverless basées sur javascript sont en pleine&amp;nbsp;ébullition.&lt;/p&gt;
&lt;h3 id="les-heritiers-de-postgresql"&gt;Les héritiers de&amp;nbsp;Postgresql&lt;/h3&gt;
&lt;p&gt;Postgresql avec sa communauté maintient un logiciel vivant, et sa licence incite la création d&amp;#8217;entreprise, qui en retour financeront (ou dédieront des développeurs) le&amp;nbsp;projet.&lt;/p&gt;
&lt;h4 id="citusdb"&gt;Citusdb&lt;/h4&gt;
&lt;p&gt;2016 9k⭐️ &lt;span class="caps"&gt;AGPL&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.citusdata.com/"&gt;Citusdb&lt;/a&gt; fournit une &lt;em&gt;extension&lt;/em&gt;, pour distribuer les données et les calculs dans un cluster, du stockage orienté colonnes&amp;nbsp;(compressé).&lt;/p&gt;
&lt;p&gt;La licence Affero brime les offres d&amp;#8217;hébergement, et Citus réserve l&amp;#8217;offre en Cloud à Azure dans son offre &lt;a href="https://learn.microsoft.com/fr-fr/azure/cosmos-db/distributed-relational"&gt;Cosmos-db&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="timescaledb"&gt;Timescaledb&lt;/h4&gt;
&lt;p&gt;2018 16k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/timescale/timescaledb"&gt;TimescaleDB&lt;/a&gt; propose une &lt;em&gt;extension&lt;/em&gt;, pour gérer les timeseries, avec du sharding basé sur la date de l&amp;#8217;événement, du stockage orienté colonne (compressé). La version autohébergement est sous licence Apache, et ils gardent pour eux la version&amp;nbsp;Cloud.&lt;/p&gt;
&lt;h4 id="neon"&gt;Neon&lt;/h4&gt;
&lt;p&gt;2021 10k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/neondatabase/neon"&gt;Neon&lt;/a&gt; se positionne comme un Aurora open source, en séparant le calcul du stockage. &lt;a href="https://neon.tech/blog/architecture-decisions-in-neon"&gt;Le stockage s&amp;#8217;appuie sur le &lt;span class="caps"&gt;WAL&lt;/span&gt; (Write Ahead Log) répliqué, pour créer des blocs immuables régulièrement consolidé (du &lt;span class="caps"&gt;LSM&lt;/span&gt;), qui seront à chaud sur un &lt;span class="caps"&gt;SSD&lt;/span&gt;, à froid sur un S3&lt;/a&gt;. Le sharding est une possibilité, mais pas la priorité&amp;nbsp;immédiate.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée étant d&amp;#8217;avoir du serverless (du &lt;span class="caps"&gt;SQL&lt;/span&gt; over &lt;span class="caps"&gt;HTTP&lt;/span&gt;/Websocket), avec la possibilité d&amp;#8217;éteindre les noeuds de calcul, sans remettre en cause la persistance, et de pouvoir dimensionner le calcul et le stockage indépendement.
le site envoie du rêve en parlant de stockage sans fond, mais aussi de cold start qui se chronomètre en seconde, ce service n&amp;#8217;est pas universel, ni&amp;nbsp;magique.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;instant, il faut patcher les sources de Postgres (les modifications ont vocations à remonter) compiler et déployer. Neon n&amp;#8217;a pas trop à craindre de la concurrence de son offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="alloydb"&gt;Alloydb&lt;/h4&gt;
&lt;p&gt;2022&amp;nbsp;propriétaire&lt;/p&gt;
&lt;p&gt;Google fork Postgresql pour créer &lt;a href="https://dbdb.io/db/alloydb"&gt;Alloydb&lt;/a&gt;, avec une architecture avec des nodes calcul/stockage, ressemblant à Neon, mais avec plus de violence : le stockage est orienté colonnes, la cible est donc de gros volumes de données sans modification, avec des reqêtes&amp;nbsp;complexes.&lt;/p&gt;
&lt;p&gt;Hors documentation utilisateur, Google communique peu sur les rouages de ce produit, qui doit recycler des outils existants, des éléments de &lt;a href="https://en.wikipedia.org/wiki/Spanner_(database)"&gt;Spanner&lt;/a&gt;, crée en 2012, avec un moteur &lt;span class="caps"&gt;SQL&lt;/span&gt; en 2017, l&amp;#8217;année de l&amp;#8217;offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;&amp;nbsp;public.&lt;/p&gt;
&lt;p&gt;Alloydb semble être la réponse définitive à tous les utilisateurs de Spanner qui ralent sur la compatibilité Postgresql, Spanner ne gérant que le&amp;nbsp;protocole.&lt;/p&gt;
&lt;h3 id="les-heritiers-de-sqlite"&gt;Les héritiers de&amp;nbsp;Sqlite&lt;/h3&gt;
&lt;h4 id="rqlite"&gt;Rqlite&lt;/h4&gt;
&lt;p&gt;2014 14k⭐️ &lt;span class="caps"&gt;MIT&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;[Rqlite] se présente comme une base de donnée relationnelle distribuée, utilisant sqlite comme moteur de stockage. Il permet d&amp;#8217;avoir des transactions, de la cohérence écriture puis lecture (la bagarre avec la réplication asynchrone), tout en restant simple et&amp;nbsp;léger.&lt;/p&gt;
&lt;p&gt;Projet communautaire sans offre d&amp;#8217;une&amp;nbsp;entreprise.&lt;/p&gt;
&lt;h4 id="litestream"&gt;Litestream&lt;/h4&gt;
&lt;p&gt;2020 9k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;En 2020, &lt;a href="https://litestream.io/"&gt;Litestream&lt;/a&gt; prouve qu&amp;#8217;il est efficace de répliquer les modifications d&amp;#8217;un sqlite vers quelque chose de pérenne, comme un S3, et de proposer des services&amp;nbsp;simples.&lt;/p&gt;
&lt;p&gt;Pas d&amp;#8217;entreprise ni d&amp;#8217;hébergement, c&amp;#8217;est un projet &amp;#8220;because we&amp;nbsp;can&amp;#8221;.&lt;/p&gt;
&lt;h4 id="litefs"&gt;Litefs&lt;/h4&gt;
&lt;p&gt;2022 3k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;Superfly utilise &lt;a href="https://github.com/superfly/litefs"&gt;LiteFS&lt;/a&gt; la même réplication des journaux que Litestream,  pour répliquer massivement et de manière asynchrone les écritures,
suivant un motif primaire/secondaire avec un consensus Raft (du Consul).
LiteFS est une couche de persistance pour l&amp;#8217;offre serverless de Superfly, adapté pour des cas avec peu d&amp;#8217;écriture concurrentes, et beaucoup de lecture qui bénéficieront de faible latence.
Il se positionne comme plus performant que Rqlite, et plus qu&amp;#8217;un simple backup comme&amp;nbsp;Litestream.&lt;/p&gt;
&lt;p&gt;Litefs est la vitrine technologique (en bêta) d&amp;#8217;une offre Cloud volontairement sobre, pour se différencier des gros du&amp;nbsp;Cloud.&lt;/p&gt;
&lt;h3 id="parquet"&gt;Parquet&lt;/h3&gt;
&lt;p&gt;2012 2k⭐️ Apache&amp;nbsp;2&lt;/p&gt;
&lt;p&gt;La fondation Apache adore rationaliser les écosystèmes éparpillés, et fournir des fondations saines sur lesquelles construire de s choses plus&amp;nbsp;grande.&lt;/p&gt;
&lt;p&gt;Inspiré par le &lt;a href="https://research.google/pubs/pub36632/"&gt;Dremel white paper&lt;/a&gt; de Google, conçu pour Hadoop (et &lt;span class="caps"&gt;HDFS&lt;/span&gt;),
&lt;a href="https://parquet.apache.org/"&gt;Parquet&lt;/a&gt; est un format de stockage orienté colonne : les écritures se font par lot, on réécrit pour modifier, on peut lire que les colonnes de son choix.
Le format est compact, compressé, optimisé pour la lecture et les performances.
Imaginé pour remplir du &lt;span class="caps"&gt;HDFS&lt;/span&gt; et être lu par du java, il s&amp;#8217;avère tout à fait apte à remplir du S3 et à être écrit/lu par tout les langages gérés par Arrow.
Toutes les audaces sont permises, on peut écrire avec un logiciel, et lire avec un autre, utilisant une technologie différente, comme vous le faisiez avec du &lt;span class="caps"&gt;CSV&lt;/span&gt; ou du &lt;span class="caps"&gt;JSON&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="arrow"&gt;Arrow&lt;/h4&gt;
&lt;p&gt;2016 12k⭐️ Apache 2
&lt;a href="https://arrow.apache.org/"&gt;Arrow&lt;/a&gt; est la bibliothèque officielle pour lire et écrire du parquet, pour exposer des colonnes en &lt;span class="caps"&gt;RAM&lt;/span&gt; avec les primitives pour faire de l&amp;#8217;&lt;span class="caps"&gt;IPC&lt;/span&gt; (discussion entre process) et du &lt;span class="caps"&gt;RPC&lt;/span&gt; (pareil, entre deux machines), avec tout ce qu&amp;#8217;il faut de zéro copie et de binding dans moult langages (dont &lt;a href="https://arrow.apache.org/docs/python/numpy.html"&gt;Numpy&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id="datafusion"&gt;Datafusion&lt;/h4&gt;
&lt;p&gt;2016 4k⭐️ Apache 2
&lt;a href="https://arrow.apache.org/datafusion"&gt;Datafusion&lt;/a&gt; est un moteur &lt;span class="caps"&gt;SQL&lt;/span&gt; utilisant Arrow, qui peut utiliser du Parquet, mais aussi du &lt;span class="caps"&gt;JSON&lt;/span&gt;, du &lt;span class="caps"&gt;CSV&lt;/span&gt; et d&amp;#8217;autres formats.
Les réponses sont du Arrow, et donc lisible en natif dans de multiples&amp;nbsp;langages.&lt;/p&gt;
&lt;h4 id="ballista"&gt;Ballista&lt;/h4&gt;
&lt;p&gt;2016 1k⭐️ Apache 2
&lt;a href="https://arrow.apache.org/ballista"&gt;Ballista&lt;/a&gt; permet de faire des requêtes avec Datafusion, mais en&amp;nbsp;distribué.&lt;/p&gt;
&lt;h4 id="flight"&gt;Flight&lt;/h4&gt;
&lt;p&gt;2019&amp;nbsp;Apache2&lt;/p&gt;
&lt;p&gt;&lt;a href="https://arrow.apache.org/blog/2019/10/13/introducing-arrow-flight/"&gt;Flight&lt;/a&gt;, basé sur grpc, implémente le début d&amp;#8217;un protocole réseau&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;poignées de mains (handshake en &lt;span class="caps"&gt;VO&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;discussion direct entre noeuds avec un ticket pour amorcer une discussion&amp;nbsp;authentifiée&lt;/li&gt;
&lt;li&gt;découverte de méta&amp;nbsp;data&lt;/li&gt;
&lt;li&gt;authentification&lt;/li&gt;
&lt;li&gt;chiffrage&lt;/li&gt;
&lt;li&gt;middleware&lt;/li&gt;
&lt;li&gt;tracing (avec&amp;nbsp;OpenTracing)&lt;/li&gt;
&lt;li&gt;du &lt;span class="caps"&gt;RDMA&lt;/span&gt; est prévu pour plus tard (les machines discutent de &lt;span class="caps"&gt;RAM&lt;/span&gt; à &lt;span class="caps"&gt;RAM&lt;/span&gt;, sans passer par le &lt;span class="caps"&gt;CPU&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="flightsql"&gt;FlightSQL&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://arrow.apache.org/blog/2022/02/16/introducing-arrow-flight-sql/"&gt;FlightSQL&lt;/a&gt; normalise le protocole réseau comme le faisait &lt;span class="caps"&gt;ODBC&lt;/span&gt; et &lt;span class="caps"&gt;JDBC&lt;/span&gt;, en s&amp;#8217;appuyant sur&amp;nbsp;Flight.&lt;/p&gt;
&lt;p&gt;Il existe &lt;a href="https://arrow.apache.org/flight-sql-postgresql/current/"&gt;une extension pour causer à Postgresql en&amp;nbsp;FlightSQL&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="les-autres-bases-de-donnees"&gt;Les autres bases de&amp;nbsp;données&lt;/h3&gt;
&lt;p&gt;J&amp;#8217;oublie volontairement les bases de données dédiées au big data (coucou &lt;a href="https://www.scylladb.com/"&gt;Scylladb&lt;/a&gt; ou &lt;a href="https://github.com/cockroachdb/cockroach"&gt;Cockroachdb&lt;/a&gt;), tout ce qui est orienté graphe, et les bases trop exotiques  (ou qui sont passé sous mon&amp;nbsp;radar).&lt;/p&gt;
&lt;p&gt;Le projet &lt;a href="https://dbdb.io/"&gt;database of databases&lt;/a&gt; tente l&amp;#8217;exhaustivité, ce qui est assez&amp;nbsp;fascinant.&lt;/p&gt;
&lt;h2 id="la-rationalisation-par-le-nuage"&gt;La rationalisation par le&amp;nbsp;nuage&lt;/h2&gt;
&lt;p&gt;L&amp;#8217;innovation suit un chemin très classique : un démarrage plein de créativité, une rationalisation, et finalement une &lt;a href="https://en.wikipedia.org/wiki/Commoditization"&gt;banalisation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les hébergeurs ont commencé par proposer la triplette machine&amp;nbsp;virtuelle/stockage/réseau.&lt;/p&gt;
&lt;p&gt;En y collant une &lt;span class="caps"&gt;API&lt;/span&gt;, c&amp;#8217;est devenu le&amp;nbsp;Cloud.&lt;/p&gt;
&lt;p&gt;En étant suffisamment gros, vous avez du stock de machines, et donc la promesse de pouvoir ajouter rapidement de la ressource pour suivre un pic&amp;nbsp;d&amp;#8217;usage.&lt;/p&gt;
&lt;p&gt;Ensuite, sont arrivés les offres de services, parfois des services open source existant, comme Mysql ou Postgresql, parfois des services maisons que la concurrence n&amp;#8217;a&amp;nbsp;pas.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; sent l&amp;#8217;embrouille et crée en 2007 l&amp;#8217;&lt;a href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"&gt;&lt;span class="caps"&gt;AGPL&lt;/span&gt;&lt;/a&gt;, comme du &lt;span class="caps"&gt;GPL&lt;/span&gt;, mais avec l&amp;#8217;obligation de fournir les sources (sous la même licence) des patchs utilisés par un&amp;nbsp;hébergeur.&lt;/p&gt;
&lt;p&gt;S3, services spécifiques à &lt;span class="caps"&gt;AWS&lt;/span&gt; est devenu un standard de fait, cloné par tous les autres&amp;nbsp;clouds.&lt;/p&gt;
&lt;p&gt;Héberger des services open-source reste fairplay, c&amp;#8217;est même le but de la famille &lt;span class="caps"&gt;BSD&lt;/span&gt;.
Il y a juste une distorsion de concurrence avec la force de frappe commercial, les économies d&amp;#8217;échelles.
On peut ajouter à ça l&amp;#8217;effet winner take all, le premier rafle tout, et l&amp;#8217;assurance vie que propose ces demi dieux de l&amp;#8217;informatique : ok, le projet n&amp;#8217;a pas tenu la charge, mais qui aurait pu faire mieux que le glorieux Amazon/Google/Microsoft?! Fatalitas!! Ce n&amp;#8217;est pas de ma&amp;nbsp;faute.&lt;/p&gt;
&lt;p&gt;Deuxième étape, une fois l&amp;#8217;entreprise construite à partir de briques open-source (qui est aussi fait pour ça, pas de soucis), on peut passer à l&amp;#8217;étape &amp;#8220;moissonner le blé de l&amp;#8217;open-source&amp;#8221; : on prends un produit open-source, avec sa base utilisateur, sa réputation, ses threads de questions/réponses sur StackOverflow, et on l&amp;#8217;intègre proprement à son offre Cloud : on peut commencer à vidanger l&amp;#8217;offre &amp;#8220;as a service&amp;#8221; de l&amp;#8217;éditeur, vers son offre&amp;nbsp;Cloud.&lt;/p&gt;
&lt;p&gt;Si on a un peu de temps, on peut créer un élément spécifique à un logiciel open-source, un élément taillé sur mesure pour son architecture logiciel, sur un thème un peu casse-gueule, comme la persistance, redondance ou&amp;nbsp;performance.&lt;/p&gt;
&lt;p&gt;Le plus connu est &lt;a href="https://aws.amazon.com/rds/aurora/"&gt;Aurora&lt;/a&gt;, un moteur de stockage distribué, sur lequel on peut poser un tout à fait classique Mysql ou un&amp;nbsp;Postgresql.&lt;/p&gt;
&lt;p&gt;Là, la cible est facile, il n&amp;#8217;y a pas d&amp;#8217;entreprises à piller (Oracle ne compte pas trop sur la vente de licence&amp;nbsp;Mysql).&lt;/p&gt;
&lt;p&gt;Quand &lt;span class="caps"&gt;AWS&lt;/span&gt; a proposé une offre intégré avec Mongodb, ça a un peu plus dérangé l&amp;#8217;éditeur qui a riposté en changeant de licence pour &lt;a href="https://en.wikipedia.org/wiki/Server_Side_Public_License"&gt;&lt;span class="caps"&gt;SSPL&lt;/span&gt;&lt;/a&gt;, propriétaire.
&lt;span class="caps"&gt;AWS&lt;/span&gt; a simplement crée un clone de Mongodb, &lt;a href="https://en.wikipedia.org/wiki/Amazon_DocumentDB"&gt;Documentdb&lt;/a&gt;, propriétaire, avec une compatibilité sur le&amp;nbsp;protocole.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; lance une offre intégré pour Elasticsearch, qui bascule en &lt;span class="caps"&gt;SSPL&lt;/span&gt; et une partie de son code passe en &amp;#8220;open source lecture seule&amp;#8221;. &lt;span class="caps"&gt;AWS&lt;/span&gt; fork pour créer &lt;a href="https://github.com/opensearch-project/OpenSearch"&gt;OpenSearch&lt;/a&gt; qui reste sous Licence Apache&amp;nbsp;2.&lt;/p&gt;
&lt;p&gt;Redis anticipe et bascule en free-core, le Redis &amp;#8220;comme d&amp;#8217;habitude&amp;#8221; garde sa licence permissive, &lt;a href="https://redis.com/blog/redis-license-bsd-will-remain-bsd/"&gt;mais toutes les nouveautés, sous forme de modules, n&amp;#8217;ont pas le droit d&amp;#8217;être vendu par un hébergeur&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="le-cas-influxdb"&gt;Le cas&amp;nbsp;Influxdb&lt;/h2&gt;
&lt;p&gt;En 2013 le monde est prêt pour avoir une base de données temporelles open source : le Cloud permet d&amp;#8217;avoir des services distribués, d&amp;#8217;adapter les ressources à la charge, bref, on passe de la question binaire &amp;#8220;le site est il disponible?&amp;#8221; à la finesse de &amp;#8220;quelles sont les performances pour quelle&amp;nbsp;consommation&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Tellement prêt que Soundcloud crée en 2012, &lt;a href="https://github.com/prometheus/prometheus"&gt;Prometheus&lt;/a&gt; basé sur le Borgmon de Google. Ciblant le cloud, Prometheus est découpé en plusieurs services et suit le choix de Google : c&amp;#8217;est la base de données qui va chercher les valeurs sur les services (un préfixe en &lt;span class="caps"&gt;HTTP&lt;/span&gt;) en attendant que tout le monde cause le prom, il faut déployer des sidekicks, comme blackbox, qui font les entremetteurs pour aller chercher les&amp;nbsp;métriques.&lt;/p&gt;
&lt;h3 id="influxdb-1"&gt;Influxdb&amp;nbsp;1&lt;/h3&gt;
&lt;p&gt;Financé par Y-Combinator, Influxdb apparait en 2013, avec une approche minimaliste : un seul binaire à déployer, des agents poussent des données, avec une large compatibilité avec les agents déjà&amp;nbsp;déployés.&lt;/p&gt;
&lt;p&gt;Un faux &lt;span class="caps"&gt;SQL&lt;/span&gt; permet de faire des requêtes et des agrégations. Premier péché : personne ne doit créer un presque &lt;span class="caps"&gt;SQL&lt;/span&gt;, c&amp;#8217;est extrêmement frustrant, autant créer un vrai &lt;span class="caps"&gt;DSL&lt;/span&gt; sans comparaison possibles, ou avoir du vrai &lt;span class="caps"&gt;SQL&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;2015, changement de nom pour pour Influxdata, et créer une suite logicielle, avec &lt;a href="https://github.com/influxdata/telegraf"&gt;Telegraf&lt;/a&gt;, un agent permettant d&amp;#8217;envoyer des données depuis de multiples&amp;nbsp;sources.&lt;/p&gt;
&lt;p&gt;Coté interface utilisateur, Kibana, apparu en 2012 et dédié à Elasticsearch, fork en Grafana pour devenir multi bases, en&amp;nbsp;2013.&lt;/p&gt;
&lt;p&gt;La citation d&amp;#8217;Influxdb apparait dans un &lt;a href="https://github.com/grafana/grafana/commit/e3f56f2645683fd3345ce8e7800bc45411b0a981"&gt;commit pour la première fois en février 2014&lt;/a&gt;. Prometheus est cité dans un &lt;a href="https://github.com/grafana/grafana/commit/bf98cfeadcc8a2bc546d6ebb8af70ffbd15a22d6"&gt;commit pour la première fois en septembre&amp;nbsp;2015&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Influxdata a donc une stack complète pour lire et écrire des&amp;nbsp;métriques.&lt;/p&gt;
&lt;p&gt;Influxdata se concentre rapidement sur son bizness model, propose un &lt;a href="https://github.com/influxdata/influxdb-relay"&gt;relais&lt;/a&gt; permettant de faire la réplication master/master (avec des données horodatées, immuables, on doit être dans le cas de réplication le plus simple), puis oups, l&amp;#8217;interromps, ça brime son offre as a service. Des forks seront maintenus un&amp;nbsp;temps.&lt;/p&gt;
&lt;p&gt;Influxdb utilise une astuce pour avoir une base de code unique qui permet de fournir un monolithe open source, et de générer une version en microservices, spécifiques à l&amp;#8217;offre commerciale, avec du protobuf et du&amp;nbsp;raft.&lt;/p&gt;
&lt;p&gt;La foule a choisi Prometheus qui a une communauté (bientôt cannibalisé par Grafana).
Influxdb se repositionne comme timeseries pour le Edge, avec une offre on premise (les données restent chez vous), en plus du cloud, qui supporte un accès intermittent à&amp;nbsp;Internet.&lt;/p&gt;
&lt;h3 id="influxdb-2"&gt;Influxdb&amp;nbsp;2&lt;/h3&gt;
&lt;p&gt;Il faut un second&amp;nbsp;souffle.&lt;/p&gt;
&lt;p&gt;Personne n&amp;#8217;utilise directement l&amp;#8217;influxql, il est tellement plus simple de cliquer partout depuis l&amp;#8217;&lt;span class="caps"&gt;UI&lt;/span&gt; de&amp;nbsp;Grafana.&lt;/p&gt;
&lt;p&gt;Pour sa version 2, Influxdb sort un vrai &lt;span class="caps"&gt;DSL&lt;/span&gt; : &lt;a href="https://www.influxdata.com/products/flux/"&gt;Flux&lt;/a&gt;, et tord le bras de utilisateur pour qu&amp;#8217;ils lâchent influxql, qui n&amp;#8217;est plus géré dans la version libre, il faut une licence pour ça.
Flux a une syntaxe élégante, basé sur la notion de pipe,&amp;nbsp;les &lt;code&gt;|&amp;gt;&lt;/code&gt; que  l&amp;#8217;on voit apparaitre dans les langages récents pour chainer les fonctions, sans avoir des tempêtes de&amp;nbsp;parenthèses.&lt;/p&gt;
&lt;p&gt;Le machine learning, qui deviendra l&amp;#8217;intelligence artificiel, commence à se généraliser, Numpy a envie de manger de la métrique, pour faire de la prévision, ou même la très casse gueule recherche&amp;nbsp;d&amp;#8217;incohérences.&lt;/p&gt;
&lt;p&gt;Le protocole réseau des requêtes réponds en &lt;span class="caps"&gt;JSON&lt;/span&gt;, comme tout bon &lt;span class="caps"&gt;REST&lt;/span&gt;, mais c&amp;#8217;est loin d&amp;#8217;être optimal pour envoyer des myriades de&amp;nbsp;float.&lt;/p&gt;
&lt;p&gt;Flux ne remplacera jamais Numpy ou même R, faire des requêtes dans deux langages en même temps est une punition, avec le risque de filtrer les données coté client (et non du coté de la base de données, ajoutant la latence de la connexion) et d&amp;#8217;autre sous&amp;nbsp;optimisation.&lt;/p&gt;
&lt;h3 id="influxdb-3"&gt;Influxdb&amp;nbsp;3&lt;/h3&gt;
&lt;p&gt;Nouveau pivot, Influxdb conserve Golang pour les couches hautes, mais bascule sur Rust pour la couche de persistance/requêtes, et crée &lt;a href="https://github.com/influxdata/influxdb_iox"&gt;IOx, prononcez eye-ox, oeil de boeuf&lt;/a&gt;.
IOx s&amp;#8217;appuie sur Parquet, qui fait sauter les contraintes de cardinalité qui peuvent mordre très fort et Datafusion qui permet d&amp;#8217;avoir du vrai &lt;span class="caps"&gt;SQL&lt;/span&gt; avec des possibilité de jointures sur des bases de données relationnel&amp;nbsp;externes.&lt;/p&gt;
&lt;p&gt;influxql et Flux sont dépréciés, on peut parler de faux départ pour cette technologie pourtant&amp;nbsp;élégante.&lt;/p&gt;
&lt;p&gt;Avec le stockage en Parquet, techniquement, si vous avez envie de lire les données avec Clickhouse ou n&amp;#8217;importe quel &lt;span class="caps"&gt;OLAP&lt;/span&gt; sympathique qui cause le Parquet, faites vous&amp;nbsp;plaisir.&lt;/p&gt;
&lt;p&gt;IOx est déployé dans &lt;a href="https://www.influxdata.com/blog/the-plan-for-influxdb-3-0-open-source/"&gt;Influxdb 3.0 annoncé la semaine dernière&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En normalisant(sous traitant?) les couches basses, Influxdb supprime deux drames, et se concentre sur son coeur de&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Nouvelle version, nouvelles&amp;nbsp;licences.&lt;/p&gt;
&lt;p&gt;Edge, un Open-core, monolithique, libre, sans&amp;nbsp;compactage.&lt;/p&gt;
&lt;p&gt;Community, gratuit, comme Edge, mais permet des requêtes sur des temps long, et du&amp;nbsp;compactage.&lt;/p&gt;
&lt;p&gt;Influxdata se réserve la partie cluster&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serverless, proche du mutualisé d&amp;#8217;antan, où l&amp;#8217;on paye à&amp;nbsp;l&amp;#8217;usage.&lt;/li&gt;
&lt;li&gt;Dédié pour envoyer du gros débit de manière&amp;nbsp;prévisible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si vous avez des besoins de edge, vous n&amp;#8217;avez qu&amp;#8217;à synchroniser vos fichiers Parquet sur un S3 (ou un de ses clones), qui assurera la redondance.
Les fichiers sont horodaté, ce sera facile d&amp;#8217;effacer les vieilles données, ou de faire de la consolidation, pour perdre en granularité en échange de&amp;nbsp;place.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Construire une base de données à partir de rien, tout seul est utopiste.
Il faut mutualiser l&amp;#8217;effort de développement et la construction de la base utilisateur, et sans se faire piller tout de suite.
La couche de persistance, et donc le risque de perdre des données, est terrifiante.
Pas mal de base de données innovantes (Mongodb, Influxdb, Prometheus…) ont débuté sur un moteur de stockage bricolé sur un coin de table avant de pivoter (ou racheter) sur quelques choses de plus&amp;nbsp;crédibles.&lt;/p&gt;
&lt;p&gt;Les efforts pour mutualiser les couches basses (stockage, requête, réplication…) du Big Data sont resté dans l&amp;#8217;écosystème Java, avec un ticket d&amp;#8217;entrée trop haut, golang s&amp;#8217;est coincé tout seul avec un cout trop élevé pour faire des aller-retours avec du C qui le force à paraphraser en pure Go. Cockroach l&amp;#8217;explique dans son billet expliquant &lt;a href="https://www.cockroachlabs.com/blog/pebble-rocksdb-kv-store/"&gt;la bascule de Rocksb vers Pebble&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les tentatives de recycler le Innodb de Mysql n&amp;#8217;ont pas abouti, mais on a vu apparaître des simples base de données clef/valeur (local comme Rocksdb, distribué comme Fundationdb) qui sont des bases saines pour construire une&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;Rust est la bonne réponse à Java pour fournir des outils juste au dessus de la couche de persistance, sans tirer tout un écosystème (et ses guerres de&amp;nbsp;communauté).&lt;/p&gt;
&lt;p&gt;Postgres, avec sa licence permissive et sa modularité reste incontournable pour ajouter des approches différentes (colonnes, géographie, graphes, vecteurs…) mais en gardant tout le reste (&lt;span class="caps"&gt;SQL&lt;/span&gt;, protocole réseau, client, réplication…).
Pas mal de base de données récentes causent le dialecte Postgres pour profiter des clients (et &lt;span class="caps"&gt;UI&lt;/span&gt;)&amp;nbsp;existants.&lt;/p&gt;
&lt;p&gt;Distribuer une base de données sur plusieurs machines est compliqué, mais profiter d&amp;#8217;une reprise sur l&amp;#8217;incident qui arrive au milieu de la nuit est quand même&amp;nbsp;chouette.&lt;/p&gt;
&lt;p&gt;Les white papers sont disponibles, les briques logicielles libres existent, mais le travail pour fournir un service distribué est difficilement mutualisable et monétisable, surtout quand on se fait&amp;nbsp;piller.&lt;/p&gt;
&lt;p&gt;Utiliser des chouettes jouets sans pouvoir ouvrir le capot est frustrant, en plus d&amp;#8217;être moins efficace et moins pérenne.
Les gros du Cloud, comme tout le monde, ont besoin d&amp;#8217;utiliser et produire de l&amp;#8217;open source :
&lt;a href="https://fr.wikipedia.org/wiki/Des_nains_sur_des_%C3%A9paules_de_g%C3%A9ants"&gt;Nanos gigantum umeris&amp;nbsp;insidentes&lt;/a&gt;&lt;/p&gt;</content><category term="Ops"></category><category term="open source"></category><category term="cloud"></category><category term="influxdata"></category><category term="influxdb"></category><category term="database"></category></entry><entry><title>Ubus roi</title><link href="http://blog.garambrogne.net/ubus-roi.html" rel="alternate"></link><published>2023-08-31T16:23:00+02:00</published><updated>2023-08-31T16:23:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2023-08-31:/ubus-roi.html</id><summary type="html">&lt;p&gt;&lt;a href="https://openwrt.org/docs/techref/ubus"&gt;ubus&lt;/a&gt; est le microbus système développé par OpenWRT. Pourquoi yet another&amp;nbsp;bus?&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://openwrt.org/docs/techref/ubus"&gt;ubus&lt;/a&gt; est le microbus système développé par OpenWRT.
u étant la version &lt;span class="caps"&gt;ASCII&lt;/span&gt; de&amp;nbsp;µ.&lt;/p&gt;
&lt;p&gt;Pourquoi yet another&amp;nbsp;bus?&lt;/p&gt;
&lt;p&gt;&lt;img alt="De par ma chandelle verte!" class="image-process-article-image" src="/images/ubus.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="openwrt"&gt;OpenWRT&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt;
est une distribution Linux ciblant l&amp;#8217;embarqué : des appareils avec peu de mémoire,
un stockage lent et maigrichon, et un processeur minimaliste.
L&amp;#8217;appareil fera peu de chose, mais bien, sans bouffer des&amp;nbsp;gigawatts.&lt;/p&gt;
&lt;p&gt;OpenWRT est conçu sur la notion de contrainte, faisant passer &lt;a href="https://www.alpinelinux.org/"&gt;Alpine&lt;/a&gt; pour une distrib de&amp;nbsp;débauché.&lt;/p&gt;
&lt;p&gt;OpenWRT est surtout connu pour son utilisation dans les routeurs wifi, il est même possible d&amp;#8217;en acheter avec directement un openWRT dessus, sans avoir besoin de le&amp;nbsp;flasher.&lt;/p&gt;
&lt;h3 id="composabilite"&gt;Composabilité&lt;/h3&gt;
&lt;p&gt;OpenWRT s&amp;#8217;appuie sur la &lt;a href="https://en.wikipedia.org/wiki/Unix_philosophy"&gt;philosophie &lt;span class="caps"&gt;UNIX&lt;/span&gt;&lt;/a&gt; : un ensemble de services qui ne font qu&amp;#8217;une seul chose, mais correctement.
Cette approche théorique est élégante dans un shell, où, avec quelques commandes et autant de pipes, on obtient à peu prés ce que l&amp;#8217;on veut.
Cette composabilité favorise l&amp;#8217;émergence de logiciels largement utilisé pour mériter des audits et des &lt;a href="https://cve.mitre.org/"&gt;CVEs&lt;/a&gt;,
et suffisamment de contributions pour avoir des corrections et des évolutions (en&amp;nbsp;théorie).&lt;/p&gt;
&lt;p&gt;Les services en ligne perdent les atouts du shell et de sa composabilité.
Le shell ne prévoit rien pour la notion d&amp;#8217;événement de plus, dans ce glorieux passé,
à part des cliques sur un clavier, un reboot de la machine, il ne se passait pas grand chose.
Non, le cron n&amp;#8217;est pas une réponse acceptable, et encore moins l&amp;#8217;immonde&amp;nbsp;polling.&lt;/p&gt;
&lt;p&gt;Pour que des services puissent discuter entre eux, il faut passer par un bus.
&lt;span class="caps"&gt;UNIX&lt;/span&gt; fournit gentiment des sockets &lt;span class="caps"&gt;UNIX&lt;/span&gt; et même des files d&amp;#8217;attentes (le très peu&amp;nbsp;connu &lt;code&gt;/dev/mqueue&lt;/code&gt;),
mais ces outils ont été conçu à l&amp;#8217;ère des processeurs mono-coeurs, et fournissent que des &lt;span class="caps"&gt;IPC&lt;/span&gt; (communications inter processus) pas de la gestion&amp;nbsp;d&amp;#8217;événements.&lt;/p&gt;
&lt;h3 id="ui-centralise"&gt;&lt;span class="caps"&gt;UI&lt;/span&gt;&amp;nbsp;centralisé&lt;/h3&gt;
&lt;p&gt;OpenWRT est entièrement configurable en ligne de commande via un accés &lt;span class="caps"&gt;SSH&lt;/span&gt;, mais sa vraie force est de proposer une interface web, maintenant écrite en lua : LuCI.
LuCI a permis de tirer une (petite) dépendance dans cet univers de minimalisme : &lt;a href="https://www.lua.org/"&gt;Lua&lt;/a&gt;, la pépite du &amp;#8220;code as&amp;nbsp;config&amp;#8221;.&lt;/p&gt;
&lt;h2 id="d-bus"&gt;D-bus&lt;/h2&gt;
&lt;p&gt;Le besoin de coordination et d&amp;#8217;évenement a très vite était ressenti pour les desktops (le fameux &amp;#8220;est-ce que Linux est raidi pour le dekstop?&amp;#8221;).
Mais &lt;a href="https://kde.org"&gt;&lt;span class="caps"&gt;KDE&lt;/span&gt;&lt;/a&gt;, le premier bureau pas tout moche, avait un soucis de licence (avec la licence de &lt;a href="https://www.qt.io/"&gt;Qt&lt;/a&gt;, soyons précis),
ce qui a conduit l&amp;#8217;apparition de &lt;a href="https://www.gnome.org/"&gt;Gnome&lt;/a&gt; (basé sur &lt;a href="https://www.gtk.org/"&gt;gtk&lt;/a&gt;, concurrent de Qt, crée pour &lt;a href="https://www.gimp.org/"&gt;Gimp&lt;/a&gt;),
les deux se sont tirés la bourre, et ont fait le maximum de choix différents, pour, euh pourquoi? pour se différencier.
Chacun a bien sur choisi un bus&amp;nbsp;différent.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.freedesktop.org"&gt;Freedesktop&lt;/a&gt; a été crée pour arrêter le massacre, et établir consensus puis des normes.
Freedesktop a fournit &lt;a href="https://en.wikipedia.org/wiki/D-Bus"&gt;D-bus&lt;/a&gt;, débuté en 2002, avec une &lt;span class="caps"&gt;API&lt;/span&gt; stable en 2006.
Fortement inspiré de &lt;span class="caps"&gt;DCOP&lt;/span&gt;, d-bus a rapidement été intégré dans &lt;span class="caps"&gt;KDE&lt;/span&gt;, Gnome a un peu plus trainé la patte.
Mais c&amp;#8217;esst de l&amp;#8217;histoire ancienne, d-bus va maintenant au delà du desktop
et gère maintenant le réseau avec &lt;a href="https://en.wikipedia.org/wiki/NetworkManager"&gt;NetworkManager&lt;/a&gt;
le bluetooth avec &lt;a href="http://www.bluez.org/"&gt;bluez&lt;/a&gt;,
mais surtout le boot avec &lt;a href="https://systemd.io/"&gt;systemd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Systemd qui tire d-bus a fait hurler sur le coup, mais la concurrence était tellement inférieur
et une norme contestable (mais utilisé) pour une couche basse a tellement plus de valeur que de vaines&amp;nbsp;divergences.&lt;/p&gt;
&lt;p&gt;D-bus a gagné la guerre du bus desktop, mais n&amp;#8217;a jamais trop pris sur les serveurs, même si il reste un prérequis&amp;nbsp;système.&lt;/p&gt;
&lt;p&gt;Les applications métiers se concentrent sur les couches au dessus, avec des services maintenant distribués,
utilisant  soit de la découverte/configuration (&lt;a href="https://zookeeper.apache.org/"&gt;zookeeper&lt;/a&gt;, &lt;a href="https://etcd.io/"&gt;etcd&lt;/a&gt;, &lt;a href="https://www.consul.io/"&gt;consul&lt;/a&gt;)
soit du gros bus avec maintenant de la persistance/reprises d&amp;#8217;événement (&lt;a href="https://kafka.apache.org/"&gt;Kafka&lt;/a&gt;,
&lt;a href="https://nats.io/"&gt;nats&lt;/a&gt;, &lt;a href="https://redis.io/docs/data-types/streams/"&gt;Redis &lt;span class="caps"&gt;STREAM&lt;/span&gt;&lt;/a&gt;…).&lt;/p&gt;
&lt;p&gt;Trop gros, trop complexe, D-bus n&amp;#8217;a aucun intérêt pour des applications cotés&amp;nbsp;serveurs.&lt;/p&gt;
&lt;p&gt;Les bus prévus pour les applications distribués sont tout aussi sur-dimensionnés pour de&amp;nbsp;l&amp;#8217;embarqué.&lt;/p&gt;
&lt;h2 id="ubus"&gt;Ubus&lt;/h2&gt;
&lt;p&gt;OpenWRT fonctionne sur des petits Linux, bien incapable de gérer&amp;nbsp;D-bus.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://openwrt.org/docs/techref/ubus"&gt;Ubus&lt;/a&gt; (µbus ?) est un logiciel embarqué, cousu main en C, premier commit en 2010, avec une chaine de dépendance minimaliste, utilisant les libs maison pour mutualiser des fonctions pour grapiller de la place.
On parle de libs qui se pèsent en&amp;nbsp;kilo.&lt;/p&gt;
&lt;p&gt;Pas de &lt;span class="caps"&gt;README&lt;/span&gt; (la doc, c&amp;#8217;est pour les faibles), une arborescence&amp;nbsp;plate, &lt;code&gt;cmake&lt;/code&gt; et un compilateur C sont les seul prérequis pour&amp;nbsp;compiler.&lt;/p&gt;
&lt;p&gt;Ubus s&amp;#8217;appuie sur &lt;a href="https://openwrt.org/docs/techref/libubox"&gt;ubox&lt;/a&gt;, la bibliothèque fourre-tout de openwrt, et &lt;a href="https://github.com/json-c/json-c"&gt;json-c&lt;/a&gt; via un wrapper&amp;nbsp;maison.&lt;/p&gt;
&lt;p&gt;Pour ne pas se laisser distraire par des fonctionnalités exotiques, qui mettrait en péril le minimalisme du projet, mais sans pour autant bloquer les évolutions, lua est bindé à&amp;nbsp;l&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;Une architecture classique et de bon gout : core en C minimaliste, modules en&amp;nbsp;lua.&lt;/p&gt;
&lt;p&gt;Le principal usage de lua est un module http, qui expose ubus en &lt;span class="caps"&gt;REST&lt;/span&gt; avec l&amp;#8217;incontournable &lt;span class="caps"&gt;JSON&lt;/span&gt;, qui permet de lui causer&amp;nbsp;avec &lt;code&gt;curl&lt;/code&gt;
et même d&amp;#8217;exposer ubus au reste du monde (dans le &lt;span class="caps"&gt;LAN&lt;/span&gt;, hein, foufou, mais pas trop), avec de l&amp;#8217;authentification et des&amp;nbsp;ACLs.&lt;/p&gt;
&lt;p&gt;Le module http pour ubus est bien sur prévu pour s&amp;#8217;intégrer à LuCI, l&amp;#8217;&lt;span class="caps"&gt;UI&lt;/span&gt; web&amp;nbsp;d&amp;#8217;OpenWRT.&lt;/p&gt;
&lt;p&gt;Pour parler le ubus&amp;nbsp;(à &lt;code&gt;ubusd&lt;/code&gt;, donc), il n&amp;#8217;y a que 3 possibilités&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l&amp;#8217;outil en ligne de commande&amp;nbsp;: &lt;code&gt;ubus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;la lib officielle, le protocole n&amp;#8217;est pas spécifié, il existe juste une&amp;nbsp;implémentation&lt;/li&gt;
&lt;li&gt;l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt;, avec le&amp;nbsp;classique &lt;code&gt;curl&lt;/code&gt; ou whatever qui cause &lt;span class="caps"&gt;HTTP&lt;/span&gt;/1.1 et &lt;span class="caps"&gt;JSON&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ubus est très lié à l&amp;#8217;écosystème d&amp;#8217;OpenWRT, les sources ne compilent pas sur une simple Debian (et encore moins sur un Mac).
OpenWRT cible une multitude d&amp;#8217;architectures exotiques, et pour ça utilise une chaine de compilation (cross compilation, en l&amp;#8217;occurence),
disponible via Docker, par ce qu&amp;#8217;on est quand même en 2023 : &lt;a href="https://github.com/openwrt/docker"&gt;openwrt/docker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Une image &amp;#8220;bac à sable&amp;#8221; est fourni, pour avoir un shell dans un environnement OpenWRT, mais sans les services lancés (pas&amp;nbsp;de &lt;code&gt;ubusd&lt;/code&gt;, donc), l&amp;#8217;idée est de pouvoir simplement tester un&amp;nbsp;paquet.&lt;/p&gt;
&lt;p&gt;Le plus simple pour jeter un oeil reste de passer par un Vagrant avec &lt;a href="https://github.com/vladimir-babichev/vagrant-openwrt-box"&gt;vagran-openwrt-box&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="le-bus-roi"&gt;Le bus&amp;nbsp;roi&lt;/h2&gt;
&lt;p&gt;Pour pouvoir composer une application à base de services, le plus simple est de passer par un bus&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Un peu fatigué par les zélotes de Kafka (ou pire, des services spécifiques des gros du Cloud), ubus apporte une touche de fraicheur, avec une approche petit boutiste, bien loin des orgies grand&amp;nbsp;boutiennes.&lt;/p&gt;
&lt;h3 id="openwrt-linux-comme-avant"&gt;OpenWRT, Linux comme&amp;nbsp;avant&lt;/h3&gt;
&lt;p&gt;Utiliser OpenWRT donne l&amp;#8217;impression de voyager dans le passé, d&amp;#8217;effleurer l&amp;#8217;age d&amp;#8217;or d&amp;#8217;&lt;span class="caps"&gt;UNIX&lt;/span&gt;, avec son C omniprésent et des contraintes matériels&amp;nbsp;énormes.&lt;/p&gt;
&lt;p&gt;OpenWRT est minimaliste et traditionaliste, sans pour autant être complaisant et réactionnaire (chose assez facile à trouver dans le monde des firewalls) :
il utilise un kernel récent (un 5.10),
ne démarre pas les services depuis un dossier rempli de shell (il utilise son &lt;a href="https://openwrt.org/docs/techref/procd"&gt;procd&lt;/a&gt;),
utilise les possibilités d&amp;#8217;isolations qui font la joie de Docker (namespace, capabilities, cgroup2, seccomp,&amp;nbsp;bpf…).&lt;/p&gt;
&lt;p&gt;Minimaliste, mais&amp;nbsp;contemporain.&lt;/p&gt;
&lt;h3 id="aller-plus-loin"&gt;Aller plus&amp;nbsp;loin&lt;/h3&gt;
&lt;p&gt;Il existe des &lt;a href="https://openwrt.org/docs/guide-user/installation/openwrt-as-stock-firmware"&gt;boitiers avec OpenWRT installé dessus&lt;/a&gt;,
coutant quelques dizaines d&amp;#8217;euros, pour pouvoir jouer et profiter tout de suite de l&amp;#8217;écosystème&amp;nbsp;d&amp;#8217;OpenWRT.&lt;/p&gt;
&lt;p&gt;Si vous aimez l&amp;#8217;absurde et les jeux de mots bancals sur &lt;a href="https://fr.wikipedia.org/wiki/Ubu_roi"&gt;père Ubu&lt;/a&gt; allez jeter un oeil &lt;a href="https://www.youtube.com/watch?v=_vuzhkEkNSQ"&gt;l&amp;#8217;étonnante adaptation filmé par Jean Christophe Averty&lt;/a&gt;.&lt;/p&gt;</content><category term="Ops"></category><category term="systemd"></category><category term="daemon"></category><category term="OpenWRT"></category><category term="bus"></category></entry><entry><title>Libre, as a service</title><link href="http://blog.garambrogne.net/libre-as-a-service.html" rel="alternate"></link><published>2020-04-05T15:20:00+02:00</published><updated>2020-04-05T15:20:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2020-04-05:/libre-as-a-service.html</id><summary type="html">&lt;p&gt;Il faut degoogliser Internet, ok. Avec du logiciel libre, ok. Libre comme le discours, pas la bière,&amp;nbsp;ok.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Il faut degoogliser Internet, ok. Avec du logiciel libre, ok. Libre comme le discours, pas la bière,&amp;nbsp;ok.&lt;/p&gt;
&lt;h2 id="brassage-domestique"&gt;Brassage&amp;nbsp;domestique&lt;/h2&gt;
&lt;p&gt;Je dois pouvoir micro héberger, sur un gros mutu &lt;span class="caps"&gt;PHP&lt;/span&gt;, sans compétences particulières, euh non, en&amp;nbsp;fait.&lt;/p&gt;
&lt;p&gt;Il est indispensable de garder le contrôle de ses données, et de décentraliser Internet. C&amp;#8217;est une démarche politique et technique. Les deux ensembles.
Une bouse technique, mais correcte politiquement, c&amp;#8217;est un&amp;nbsp;échec.&lt;/p&gt;
&lt;h2 id="parlons-pognon"&gt;Parlons&amp;nbsp;pognon&lt;/h2&gt;
&lt;p&gt;Vous voulez déposer une application sur Internet, et que des gens puissent l&amp;#8217;utiliser. Ça va avoir un cout : en temps, en matos, en&amp;nbsp;sous.&lt;/p&gt;
&lt;p&gt;Le cout du développement, si c&amp;#8217;est un logiciel libre, installé tel quel, sans modification, bah il est gratos, comme dans bière&amp;nbsp;gratos.&lt;/p&gt;
&lt;p&gt;Ensuite, il faut un serveur, et un branchement vers Internet. Avec les ordis de la taille d&amp;#8217;une paume, et une &lt;span class="caps"&gt;ADSL&lt;/span&gt; à prix agressif, le cout d&amp;#8217;un hébergement à la maison va être faible. Les offres entrées de gammes de nos hébergeurs nationaux (ou plus loin) sont elles aussi&amp;nbsp;accessibles.&lt;/p&gt;
&lt;p&gt;Concrètement, pour un service simple et peu sollicité, un hébergement peu cher aura des performances&amp;nbsp;correctes.&lt;/p&gt;
&lt;p&gt;Jusqu&amp;#8217;ici tout va bien, le cout du développement et de l&amp;#8217;hébergement matériel sont&amp;nbsp;raisonnables.&lt;/p&gt;
&lt;p&gt;Mais c&amp;#8217;est que maintenant que l&amp;#8217;histoire&amp;nbsp;commence.&lt;/p&gt;
&lt;h2 id="notre-temps"&gt;Notre&amp;nbsp;temps&lt;/h2&gt;
&lt;p&gt;Vient maintenant la notion de&amp;nbsp;maintenance.&lt;/p&gt;
&lt;p&gt;Le développement, c&amp;#8217;est ton temps, l&amp;#8217;hébergement, c&amp;#8217;est le temps des&amp;nbsp;autres.&lt;/p&gt;
&lt;p&gt;Le service va avoir des incidents, qu&amp;#8217;ils soient matériels ou logiciels.
De simples bugs, sur lesquels on peut s&amp;#8217;assoir, ou des failles de sécurité, qu&amp;#8217;il faut patcher rapidement avant que ça dégénère. Pour certains services, comme du &lt;span class="caps"&gt;SMTP&lt;/span&gt;, un incident, ça veut dire un bannissement, et donc un arrêt partiel du service. Un service compromis qui va miner du bitcoin en mettant à genoux le serveur, ça veut dire aussi un arrêt du&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Un disque dur qui boude, un serveur qui se coince, ça veut dire restaurer un backup, et donc avoir fait un&amp;nbsp;backup.&lt;/p&gt;
&lt;p&gt;À un moment, vos utilisateurs vont utiliser votre service, sinon, il ne sert pas à grand-chose, et donc le secouer un peu, puis beaucoup.
Ce n&amp;#8217;est pas grave, vous centralisez les erreurs, enregistrez les métriques et faites un joli tableau de bord.
Une première passe pour trouver des usages abusifs, puis finalement, ils vont atteindre de vraies limites, et ça va ramer&amp;nbsp;sévère.&lt;/p&gt;
&lt;p&gt;Là, l&amp;#8217;hébergement de ce service va commencer à être pénible. Le cout du dev est gratos, le cout du matériel est symbolique, le cout de la maintenance explose.
Que faire? Passer en force et surdimensionner les serveurs, mais ça veut dire tout réinstaller (bah oui, sans provisionning, sans conteneur, ça a un cout sympathique), et ça transfert un cout en temps vers un cout en sous.
Avec plus de matos, ou du matos plus gros, ça va pouvoir secouer plus fort, avec plus d&amp;#8217;utilisateurs à mécontenter, et de nouveaux drames rendus possibles par le franchissement de la première vague de&amp;nbsp;drames.&lt;/p&gt;
&lt;h3 id="cramage"&gt;Cramage&lt;/h3&gt;
&lt;p&gt;À cette étape, il est possible de cramer l&amp;#8217;admin, qui va se trouver bien seul, avec plein de gens pour râler. Si c&amp;#8217;est pour du support utilisateur, un forum et des grunts peuvent décharger une partie de la charge. Pour les serveurs qui pètent, bah, c&amp;#8217;est&amp;nbsp;compliqué.&lt;/p&gt;
&lt;p&gt;Pour ne pas cramer un humain, une seule solution en avoir plusieurs, une équipe en fait. Pour rentabiliser, il leur faut donc plus d&amp;#8217;utilisateurs et donc une instance plus grosse. Il sera humainement possible de gérer plus de matos et donc céder à la facilité de continuer de passer en&amp;nbsp;force&lt;/p&gt;
&lt;p&gt;Ce n&amp;#8217;est pas sale de payer des gens. Que ce soit en micropaiement ou salarié par une structure associative. Ils mangent, payent un loyer et s&amp;#8217;habillent de teeshirt trop cher à cause des dessins qu&amp;#8217;il ya&amp;nbsp;dessus.&lt;/p&gt;
&lt;h3 id="automatisation-ou-lanti-luddisme"&gt;Automatisation ou&amp;nbsp;l&amp;#8217;anti-luddisme&lt;/h3&gt;
&lt;p&gt;À cette étape-là, infra as code n&amp;#8217;est plus une&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;Par contre, empiler du matos, juste pour gérer des pics d&amp;#8217;utilisation, ça va devenir hors de prix, et c&amp;#8217;est même probable que le code ne va pas gérer la répartition sur plusieurs machines. L&amp;#8217;impasse va vite&amp;nbsp;arriver.&lt;/p&gt;
&lt;p&gt;En parallèle, un autre problème va arriver : la complexité des services.
On est passé de l&amp;#8217;ère des sites webs à celui des applications, et tout ne rentre pas dans l&amp;#8217;http de l&amp;#8217;an 2000, pour avoir du tchat, de la visioconférence, oui, ça va passer par les navigateurs webs, mais via websocket, webrtc et autres streamings. Cette complexité va faire exploser les couts de développement, et la complexité côté serveur. Cette fois-ci, Varnish ne pourra plus sauver votre développement de&amp;nbsp;bourrin.&lt;/p&gt;
&lt;p&gt;Les utilisateurs ont rapidement pris gout aux interfaces léchées des applications web des GAFAs. Essayez de faire passer un utilisateur non militant de Gmail à &lt;a href="https://squirrelmail.org/"&gt;Squirrel mail&lt;/a&gt;, juste pour voir.
Pour développer un service de qualité, il faut des compétences complémentaires (&lt;span class="caps"&gt;UX&lt;/span&gt;, graphisme, support, traduction, développement…) et tous ces métiers ne se payent pas tous de gloire, leur métier ne leur laisse pas forcément autant de temps et de thunes en rab, pour le consacrer au logiciel&amp;nbsp;libre.&lt;/p&gt;
&lt;p&gt;Le matos a changé d&amp;#8217;échelle avec le remplacement des disques à plateaux qui se trainaient lamentablement par le &lt;span class="caps"&gt;SSD&lt;/span&gt;, et même avant qu&amp;#8217;on se rende compte du changement par du NVMe. En face, de la fibre ou de la grosse 4G. Le matos est prêt à tout engloutir, ce ne sera ni le stockage, ni la bande passante qui vont ralentir la soif de&amp;nbsp;pixels.&lt;/p&gt;
&lt;p&gt;Donc, voilà, des hordes d&amp;#8217;utilisateurs exigeants, avec des gros appétits, sont là pour apprécier vos services. Serez-vous à la&amp;nbsp;hauteur?&lt;/p&gt;
&lt;h2 id="un-nouvel-espoir"&gt;Un nouvel&amp;nbsp;espoir&lt;/h2&gt;
&lt;p&gt;Pour avoir une chance de réussir à proposer des services sympathiques en ligne,  alternatifs ou pas, il faut d&amp;#8217;abord mettre de l&amp;#8217;ordre dans tout ce&amp;nbsp;fatras.&lt;/p&gt;
&lt;h3 id="protocoles"&gt;Protocoles&lt;/h3&gt;
&lt;p&gt;L&amp;#8217;idée de base est de ne pas avoir de monopole, il faut donc avoir des systèmes décentralisés, des machins qui causent entre eux pour profiter de l&amp;#8217;effet d&amp;#8217;échelle. Un réseau social fonctionne par ce qu&amp;#8217;il y a déjà plein de gens dessus. Pour ça, il faut des protocoles, une locomotive pour tirer plein de monde, et des implémentations adaptés à des cas moins&amp;nbsp;courants.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/BitTorrent"&gt;Bittorrent&lt;/a&gt; a parfaitement réussi ça, et &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt; peut réussir si &lt;a href="https://pleroma.social/"&gt;Pleroma&lt;/a&gt; arrive à se démocratiser. &lt;a href="https://joinpeertube.org/fr/"&gt;Peertube&lt;/a&gt; est fonctionnel, en déclinant le concept du pair à pair à la vidéo. Il faut voir ce que donnera &lt;a href="https://ipfs.io/"&gt;&lt;span class="caps"&gt;IPFS&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le mail est &lt;span class="caps"&gt;LE&lt;/span&gt; premier service distribué largement utilisé, mais le spam et la stagnation des outils libres qui ont permis l&amp;#8217;apparition de gmail ont eu un effet dramatique. &lt;span class="caps"&gt;DKIM&lt;/span&gt; et &lt;span class="caps"&gt;SPIF&lt;/span&gt; sont maintenant un prérequis, mais l&amp;#8217;arbitraire des gros serveurs est maintenant une menace permanente. L&amp;#8217;incapacité du courriel à garantir la confidentialité des échanges, confirmée par l&amp;#8217;échec de &lt;a href="https://gnupg.org/"&gt;&lt;span class="caps"&gt;GPG&lt;/span&gt;&lt;/a&gt;, ont reléguer le mail au rôle de renouveleur de mots de&amp;nbsp;passe.&lt;/p&gt;
&lt;p&gt;Il faut des protocoles bien spécifiés pour être interopérables, et des implémentations qui scalent petit, et&amp;nbsp;grand.&lt;/p&gt;
&lt;p&gt;Étonnement, Gitlab et &lt;a href="https://gitea.io/"&gt;Gittea&lt;/a&gt; via git comme protocole permettent ça. Reste à normaliser la notion de fork et pull&amp;nbsp;request.&lt;/p&gt;
&lt;p&gt;De la distribution de service sans syndication efficace, bah, c&amp;#8217;est juste de la&amp;nbsp;dispersion.&lt;/p&gt;
&lt;h3 id="de-bons-outils"&gt;De bons&amp;nbsp;outils&lt;/h3&gt;
&lt;p&gt;Avant, on avait des disques qui ramaient, une pénurie de développeurs web, et des navigateurs pourrites. Donc pas de dev front. Il y eut ensuite l&amp;#8217;épisode Flash + &lt;span class="caps"&gt;IE6&lt;/span&gt;, pour enfin aboutir à la modernité : &lt;span class="caps"&gt;HTML5&lt;/span&gt; et les spécifications EcmaScript, la déferlante des smartphones, les disques qui vont trop vite et les gros&amp;nbsp;tuyaux.&lt;/p&gt;
&lt;p&gt;Pour les devs, on a maintenant des langages matures, des bouquins avec des gravures d&amp;#8217;animaux dessus, des frameworks, des workflows pour gérer le code, et de plus en plus d&amp;#8217;analyses&amp;nbsp;statiques.&lt;/p&gt;
&lt;p&gt;Il faut un écosystème logiciel suffisamment responsable pour avoir des bibliothèques maintenues et pas systématiquement&amp;nbsp;trouées.&lt;/p&gt;
&lt;h3 id="qualite"&gt;Qualité&lt;/h3&gt;
&lt;p&gt;Donc, pour proposer des services de qualité en ligne, il faut des technologies suffisamment répandues pour avoir un stock de développeurs, et une chance de reprise si le mainteneur a un pépin. J&amp;#8217;ai fait des patchs &lt;a href="https://www.erlang.org/"&gt;Erlang&lt;/a&gt;, par vice, mais je suis moyen chaud pour &lt;span class="caps"&gt;RTFM&lt;/span&gt; suffisamment pour proposer des patchs &lt;a href="https://www.haskell.org/"&gt;Haskell&lt;/a&gt;, &lt;a href="https://www.ponylang.io/"&gt;Pony&lt;/a&gt; ou autre &lt;a href="https://crystal-lang.org/"&gt;Crystal&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="qui-garde-les-gardiens"&gt;Qui garde les&amp;nbsp;gardiens&lt;/h3&gt;
&lt;p&gt;Un serveur vautré a une qualité de zéro, niveau expérience utilisateur. Il faut avoir un système pour prévenir quand ça se vautre, avec tout ce qu&amp;#8217;il faut de contexte pour pouvoir comprendre rapidement l&amp;#8217;incident, puis le corriger (dites bonjour à &lt;a href="https://sentry.io/"&gt;Sentry&lt;/a&gt;), et que ce surveillant soit lui même plus fiable que le système qu&amp;#8217;il surveille. Pour ça, il n&amp;#8217;y a pas 36 solutions, il faut de la redondance, et là, ça commence à gripper pour l&amp;#8217;auto hébergement. Pour l&amp;#8217;instant il n&amp;#8217;y a rien comme outils simples pour avoir une coopérative de monitoring, regroupant quelques auto-hébergeur.
Une fois que vous vous serez mangé un incident au moment où vous avez le plus envie de faire toute autre chose, bah, vous commencerez à vous renseigner sur la redondance et la tolérance de panne. Je ne parle pas de l&amp;#8217;inévitable &lt;span class="caps"&gt;RAID&lt;/span&gt; de disques, mais des outils comme &lt;a href="https://www.consul.io/"&gt;Consul&lt;/a&gt; pour coordonner une petite grappe de machines et relancer des services sur les machines survivantes, vous laissant le temps d&amp;#8217;organiser la réparation ou le changement d&amp;#8217;une pièce. Je parle juste de coordination de services, l&amp;#8217;omniprésent Kubernetes est largement au-delà du scope du petit&amp;nbsp;hébergement.&lt;/p&gt;
&lt;h3 id="efficience"&gt;Efficience&lt;/h3&gt;
&lt;p&gt;Concrètement, ce qui coince, maintenant, c&amp;#8217;est la facture énergétique, et l&amp;#8217;utilisation du &lt;span class="caps"&gt;CPU&lt;/span&gt;. La prévisibilité de son utilisation, pour être&amp;nbsp;précis.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est difficile de dimensionner un serveur. Techniquement le premier palier va être gros, la machine sera surdimensionnée, tout comme l&amp;#8217;est votre poste de développement. Avec peu d&amp;#8217;utilisateurs, si marche sur votre laptop, ça marchera sur le serveur. Par contre, avec plein d&amp;#8217;utilisateurs, le matériel va commencer à être utilisé comme il faut.
On va avoir un budget &lt;span class="caps"&gt;CPU&lt;/span&gt;/&lt;span class="caps"&gt;RAM&lt;/span&gt;/Disque par utilisateur. La notion de hit, avec les gens qui prennent le temps de lire, ça saute avec les applications et les flots &lt;span class="caps"&gt;HTTP&lt;/span&gt;. Pour avoir de la visibilité, il faut que ce budget ressource soit prévisible : si vous avez quelques workers avec 512Mo de &lt;span class="caps"&gt;RAM&lt;/span&gt; et un timeout de 30 secondes, ça va gérer les actions violentes sur le serveur, mais au détriment du nombre d&amp;#8217;actions en parallèle.
En permettant que quelques-uns puissent tout prendre, bah ça va brimer l&amp;#8217;ensemble du groupe. Pour offrir une qualité correcte à l&amp;#8217;ensemble, il faut des quotas raisonnables pour tous, et mettre dans des files d&amp;#8217;attente toutes les actions gourmandes : elles seront effectuées, mais sans se marcher dessus, et en prenant le temps qu&amp;#8217;il faut. Rigoler en voyant le load d&amp;#8217;un serveur, ça se faisait avant l&amp;#8217;an 2000, plus en 2020.
L&amp;#8217;asynchrone devient la règle, pour lisser les accès parallèles et assumer les différentes&amp;nbsp;latences.&lt;/p&gt;
&lt;h3 id="simple-ou-simpliste-complet-ou-complexe"&gt;Simple ou simpliste, complet ou complexe&amp;nbsp;?&lt;/h3&gt;
&lt;p&gt;La tolérance du langage pour faciliter son apprentissage se paye cash au niveau maintenance et sécurité. L&amp;#8217;analyse statique, que ce soit dans son éditeur ou la &lt;span class="caps"&gt;CI&lt;/span&gt; devient un prérequis. Typescript est venu ajouter la rigueur du typage fort à l&amp;#8217;historiquement bordélique Javascript. Même Python a maintenant des annotations de&amp;nbsp;type.&lt;/p&gt;
&lt;p&gt;La facilité de déploiement est un argument ambigu. Pouvoir poser un machin en &lt;span class="caps"&gt;PHP&lt;/span&gt; dans un bout d&amp;#8217;arborescence apache n&amp;#8217;est pas un déploiement facile. Les gros mutus sont d&amp;#8217;une autre époque, en fait. Proposer un déploiement simple sans drames de dépendances est l&amp;#8217;objectif. La réponse ultime est bien sur le conteneur (le concept, hein, je ne parle pas d&amp;#8217;implémentation). Les paquets systèmes ont failli pour déployer des services, mais ils gardent toute leur pertinence comme briques de base pour composer un service.
La norme existe : &lt;a href="https://cnab.io/"&gt;&lt;span class="caps"&gt;CNAB&lt;/span&gt;&lt;/a&gt;, reste à peaufiner les&amp;nbsp;implémentations.&lt;/p&gt;
&lt;p&gt;La facilité de mises à jour avec un rollback facile est un prérequis. Si on déploie, on met à jour. Sinon, ce sera du site&amp;nbsp;statique.&lt;/p&gt;
&lt;h3 id="visibilite"&gt;Visibilité&lt;/h3&gt;
&lt;p&gt;La visibilité, que ce soit les incidents ou les mesures (ce que ça fournit niveau métier, ce que ça mange), doit être systématique. &lt;a href="https://fr.wikipedia.org/wiki/Htop"&gt;htop&lt;/a&gt;&amp;nbsp;et &lt;code&gt;grep /var/log&lt;/code&gt; ne sont plus suffisants, hein. Sentry, même avec sa nouvelle licence est votre&amp;nbsp;ami.&lt;/p&gt;
&lt;p&gt;Arrêtez de tanner les gens avec des comptes et des mots de passe qui seront soit tout le temps le même, ou oubliées. Mozilla a lâché l&amp;#8217;affaire avec son &lt;span class="caps"&gt;SSO&lt;/span&gt;, ce qui est frustrant, mais &lt;span class="caps"&gt;JWT&lt;/span&gt; ou OAuth sont maintenant bien diffusé. Par contre, il faut un retour en grâce d&amp;#8217;OpenID, pour ne pas tout appuyer sur l&amp;#8217;OAuth d&amp;#8217;un des GAFAMs, ce qui serait un&amp;nbsp;drame.&lt;/p&gt;
&lt;h3 id="lusine-nouvelle"&gt;L&amp;#8217;usine&amp;nbsp;nouvelle&lt;/h3&gt;
&lt;p&gt;Arrêtez la comparaison industrie/artisanat pour l&amp;#8217;informatique. Un service installé à la main, sur un serveur installé avec un &lt;span class="caps"&gt;CD&lt;/span&gt;, ce n&amp;#8217;est pas comparable avec du pain ou un meuble d&amp;#8217;artisan. La valeur de l&amp;#8217;installation est dans la rigueur, les choix techniques, la connaissance de l&amp;#8217;ensemble pour prendre des choix éclairés, pas dans la sueur et la vélocité des commandes tapée sur un clavier &lt;a href="http://www.typematrix.com/"&gt;typematrix&lt;/a&gt;. Faites votre levain, et provisionnez vos serveurs. L&amp;#8217;installation c&amp;#8217;est le prélude, l&amp;#8217;histoire, c&amp;#8217;est la maintenance et les mises à jour de nouvelles&amp;nbsp;fonctions.&lt;/p&gt;
&lt;p&gt;Gmail n&amp;#8217;a pas gagné par ce qu&amp;#8217;il était meilleur, mais par ce que les offres du logiciel libre ont&amp;nbsp;échoué.&lt;/p&gt;</content><category term="Ops"></category></entry><entry><title>Déployer des microservices avec des dépendances</title><link href="http://blog.garambrogne.net/microservices-avec-dependances.html" rel="alternate"></link><published>2018-12-01T14:09:00+01:00</published><updated>2018-12-01T14:09:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2018-12-01:/microservices-avec-dependances.html</id><summary type="html">&lt;p&gt;Les microservices permettent de tout découper en petites entitées autonomes, mais comment garder une cohérence lors des multiples&amp;nbsp;déploiements?&lt;/p&gt;</summary><content type="html">&lt;h1 id="microservices"&gt;Microservices&lt;/h1&gt;
&lt;p&gt;Le principe de bases des microservices est simple : une application est composée d&amp;#8217;un ensemble de services, qui peuvent discuter entre&amp;nbsp;eux.&lt;/p&gt;
&lt;p&gt;La règle de base étant que ces microservices puissent avoir un cycle de vie (et de déploiement)&amp;nbsp;autonome.&lt;/p&gt;
&lt;p&gt;Si vous devez déployer plusieurs microservices d&amp;#8217;un coup, bravo, vous avez un monolithe&amp;nbsp;distribué.&lt;/p&gt;
&lt;h1 id="la-confusion-entre-microservice-et-conteneur"&gt;La confusion entre microservice et&amp;nbsp;conteneur&lt;/h1&gt;
&lt;p&gt;Attention, il n&amp;#8217;y a absolument pas de ratio un pour un entre microservice et conteneur. Un service peut être rendu par plusieurs conteneurs, regroupé ou non en pod.
L&amp;#8217;exemple typique étant le site web et son traitement asynchrone (rails+sidekiq, flask+celery …). Les serveurs de bases de données peuvent être mutualisés, tant qu&amp;#8217;on ne touche pas à la sacrosainte isolation des&amp;nbsp;bases.&lt;/p&gt;
&lt;h1 id="versionning"&gt;Versionning&lt;/h1&gt;
&lt;p&gt;Un service peut consommer un autre service.
Les différents microservices sont versionnés, que ce soit avec un hash git tout moche, ou un joli&amp;nbsp;semver.&lt;/p&gt;
&lt;p&gt;Même s’il est possible d&amp;#8217;appeler un service par son nom, il est quand même sage d&amp;#8217;avoir un graphe des différents services : qui consomme qui.
C&amp;#8217;est l&amp;#8217;approche&amp;nbsp;des &lt;code&gt;links&lt;/code&gt; de Docker-compose, Kubernetes préférant l&amp;#8217;anonymat&amp;nbsp;des &lt;code&gt;Services&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Les services sont des boites, la version du service doit rester privée, ce qui est public (et concerne les autres services), c&amp;#8217;est la version de son &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Du coup, chaque service peut réclamer un autre service avec une version d&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Un peu comme de la gestion de bibliothèque, quoi, on retombe sur le pattern bien maitrisé de gem/yarn/dep/pip et&amp;nbsp;compagnie.&lt;/p&gt;
&lt;p&gt;Pour chaque service, on peut déterminer sa version d&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;, et la liste des services avec les versions minimum attendues. Ces informations peuvent être rangées dans des étiquettes des images (des labels docker, quoi), ce qui permet de retrouver simplement l&amp;#8217;information sur un environnement&amp;nbsp;live.&lt;/p&gt;
&lt;p&gt;La procédure de déploiement peut débuter par un preflight : on vérifie que le service que l&amp;#8217;on souhaite déployer va trouver les services consommés dans les versions&amp;nbsp;attendues.&lt;/p&gt;
&lt;p&gt;Intégrer le preflight, à titre informatif, dans son cycle d&amp;#8217;intégration continue, est une bonne idée : &amp;#8220;attention, là maintenant, votre service n&amp;#8217;a pas les prérequis pour être déployé  en prod, il va peut être falloir discuter avec l&amp;#8217;autre&amp;nbsp;équipe&amp;#8221;.&lt;/p&gt;
&lt;p&gt;D&amp;#8217;un autre côté, présenter une &lt;span class="caps"&gt;API&lt;/span&gt;, mais sans la déployer, ce n&amp;#8217;est pas super fairplay non plus, ça sent le refactoring qui a du mal à&amp;nbsp;aboutir.&lt;/p&gt;
&lt;h1 id="deployer-a-blanc"&gt;Déployer à&amp;nbsp;blanc&lt;/h1&gt;
&lt;p&gt;Pour limiter les embrouilles de chronologie de déploiement pour des histoires de dépendances, chose que l&amp;#8217;approche micro services promettait d&amp;#8217;éviter, il ne faut pas hésiter à déployer des services à blanc, en déployant des bouts d&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; qui ne seront consommés que par des tests fonctionnels. On déploie d&amp;#8217;abord un serveur sans clients, puis dans un second temps les&amp;nbsp;clients.&lt;/p&gt;
&lt;p&gt;Le service est déployé, testé, un éventuel test de charge pour évaluer son comportement en charge, ou même déployé en fantôme, avec des clients qui l&amp;#8217;utilisent, mais sans le dire, soit en doublon d&amp;#8217;un service qu&amp;#8217;il souhaite remplacer, soit headless, sans que les utilisateurs le voie. Facebook avait fait ce genre de chose pour son&amp;nbsp;tchat.&lt;/p&gt;
&lt;p&gt;Lorsque l&amp;#8217;on déploie un service, on se réserve la possibilité de faire un rollback en cas de drame. Par contre, attention, quand on déploie une nouvelle &lt;span class="caps"&gt;API&lt;/span&gt;, qu&amp;#8217;on la valide incorrectement, puis en déployant le client qui va la consommer, on se rend compte d&amp;#8217;un problème : c&amp;#8217;est le drame, le seul rollback possible est une cascade de rollback, ce que l&amp;#8217;on souhaiter éviter à tout&amp;nbsp;prix.&lt;/p&gt;
&lt;p&gt;Pour les services très fortement sollicités, le déploiement se fera de manière progressive, en gardant les yeux rivés sur les performances et les taux d&amp;#8217;erreurs. Le déploiement progressif rend physiquement impossible, ou presque le déploiement par lot. Ça tombe bien, c&amp;#8217;est ce que l&amp;#8217;on souhaite&amp;nbsp;éviter.&lt;/p&gt;
&lt;h1 id="etendre-le-concept"&gt;Étendre le&amp;nbsp;concept&lt;/h1&gt;
&lt;p&gt;Cette notion de version d&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; peut être étendue aux modèles de données.
La modification devra se faire en deux étapes, la première va ajouter des colonnes, sans casser le code actuellement déployé. Ensuite, on pourra déployer le code utilisant le nouveau modèle, et enfin, déploiement des modifications qui vont enlever des&amp;nbsp;colonnes.&lt;/p&gt;
&lt;p&gt;Modifier des colonnes va être un peu plus compliqué. Soit le modèle logiciel (l&amp;#8217;&lt;span class="caps"&gt;ORM&lt;/span&gt;, à priori) va assurer la transition en calculant les modifications à la volée, puis en tache de fond, de gros batchs assurent une transition complète, sans subir un phénomène de longue traine. Mongodb se prête bien à ce genre&amp;nbsp;d&amp;#8217;astuce.&lt;/p&gt;
&lt;p&gt;La migration de modèle est un sujet complet, complémentaire du déploiement.
Allez jeter un oeil à &lt;a href="https://github.com/github/gh-ost"&gt;gh-ost&lt;/a&gt; pour avoir une étendu du&amp;nbsp;sujet.&lt;/p&gt;
&lt;h1 id="deprecier"&gt;Déprécier&lt;/h1&gt;
&lt;p&gt;Effacer des trucs, ça veut dire prendre le risque de le regretter plus tard.
À grande échelle, on passe du regret à l&amp;#8217;angoisse.
Du coup, il est tellement plus confortable d&amp;#8217;entasser à l&amp;#8217;infini, de se proposer des &lt;span class="caps"&gt;API&lt;/span&gt; avec une compatibilité ascendante depuis le premier jour.
Dans le même esprit, on ne rajoute pas de contrainte sur des données stockées (dans l&amp;#8217;approche de stockage massif à la &lt;a href="http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html"&gt;&lt;span class="caps"&gt;HDFS&lt;/span&gt;&lt;/a&gt;).
C&amp;#8217;est ce que propose &lt;a href="https://developers.google.com/protocol-buffers/"&gt;Protobuff&lt;/a&gt; et &lt;a href="https://thrift.apache.org/"&gt;Thrift&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il est quand même possible de connaitre les versions de clients déployés (en interne), et d&amp;#8217;avoir des stats sur les clients externes. Avec ces infos, on peut déprécier l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;, forcer la main pour mettre à jour les clients, puis retirer l&amp;#8217;ancienne &lt;span class="caps"&gt;API&lt;/span&gt;. Bazarder du code mort, ça fait toujours plaisir, et ça évitera d&amp;#8217;avoir des surprises plus tard. Mettre à disposition un proxy qui fera le passe plat entre différentes versions de l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; peut être un palliatif, mais une coupe franche est nettement plus&amp;nbsp;simple.&lt;/p&gt;
&lt;h1 id="automatiser"&gt;Automatiser&lt;/h1&gt;
&lt;p&gt;Il existe des grammaires pour les différentes &lt;span class="caps"&gt;API&lt;/span&gt; distantes. &lt;a href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; (ex-swagger), &lt;a href="https://grpc.io/"&gt;grpc&lt;/a&gt; et tant d&amp;#8217;autres sont basé sur cette approche.
Ce contrat doit être le seul lien entre les différents services, le sujet de discussion entre les différentes équipes. Ce contrat va permettre de générer du code, ou le valider, selon les cultures&amp;nbsp;logicielles.&lt;/p&gt;
&lt;p&gt;Avec du code bien rangé, il est possible de lancer automatiquement des tests sur les différents clients quand on modifie le serveur. Trivial pour des tests fonctionnels avec des services conteneurisés, envisageable pour des bibliothèques. C&amp;#8217;est là que débute le troll sur le &lt;a href="https://en.wikipedia.org/wiki/Monorepo"&gt;monorepo&lt;/a&gt;, mais un sujet orthogonal au déploiement de&amp;nbsp;microservices.&lt;/p&gt;</content><category term="Dev"></category><category term="microservices"></category><category term="conteneur"></category><category term="kubernetes"></category></entry><entry><title>Kubernetes est la réponse, c’est quoi déjà la question ?</title><link href="http://blog.garambrogne.net/kubernetes_est_la_reponse_c_est_quoi_deja_la_question.html" rel="alternate"></link><published>2018-06-20T19:23:00+02:00</published><updated>2018-06-20T19:23:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2018-06-20:/kubernetes_est_la_reponse_c_est_quoi_deja_la_question.html</id><summary type="html">&lt;p&gt;Drôle d&amp;#8217;ambiance autour de Kubernetes, en ce moment. Le Docker bashing à toujours la cote : des gens avec des avis tranchés, qui ont réussi avec arrogance, là où &lt;span class="caps"&gt;LXC&lt;/span&gt; pataugeait depuis toujours, pour finalement aboutir à une norme et des spécifications. Mais, en même temps, Kubernetes, qui est prêt pour la prod, lui, est encensé, attendu comme le&amp;nbsp;messie.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Drôle d&amp;#8217;ambiance autour de Kubernetes, en ce moment. Le Docker bashing à toujours la cote : des gens avec des avis tranchés, qui ont réussi avec arrogance, là où &lt;span class="caps"&gt;LXC&lt;/span&gt; pataugeait depuis toujours, pour finalement aboutir à une norme et des spécifications.
Mais, en même temps, Kubernetes, qui est prêt pour la prod, lui, est encensé, attendu comme le messie. Ça donne des &lt;a href="https://twitter.com/amicel/status/1009326802106552320"&gt;tweets de ce genre&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="services"&gt;Services&lt;/h2&gt;
&lt;p&gt;Commençons par le commencement, votre application est orientée service : c&amp;#8217;est l&amp;#8217;assemblage de différents services.
Un truc comme django/celery/postgresql/redis ou même php-fpm/mariadb/memcache/varnish/nginx ou que sais-je dans ce&amp;nbsp;gout-là.&lt;/p&gt;
&lt;p&gt;Si votre application est un gros binaire qui contient tout, un peu comme la boite contenant le mouton du Petit Prince, félicitation, vous faites du &lt;a href="https://fr.wikipedia.org/wiki/Zope"&gt;Zope&lt;/a&gt;, vous n&amp;#8217;avez pas à vous enquiquiner avec ces histoires de&amp;nbsp;Kubernetes.&lt;/p&gt;
&lt;h2 id="microservices"&gt;Microservices&lt;/h2&gt;
&lt;p&gt;La question n&amp;#8217;est pas là, utiliser des services est déjà suffisant, et ce sera encore plus fun avec des&amp;nbsp;microservices.&lt;/p&gt;
&lt;h2 id="conteneur"&gt;Conteneur&lt;/h2&gt;
&lt;p&gt;Docker quoi, on peut tourner autour du pot, mais même avec la normalisation de l&amp;#8217;&lt;a href="https://www.opencontainers.org/"&gt;&lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/a&gt;, Docker est pour l&amp;#8217;instant le standard de fait. Le travail d&amp;#8217;intégration sur les postes de travail, sur Windows/Mac/Linux, est trop énorme (et trop pénible) pour être paraphrasé par un&amp;nbsp;concurrent.&lt;/p&gt;
&lt;p&gt;Un conteneur, c&amp;#8217;est juste un format de livraison d&amp;#8217;un service standardisé, immuable, et la possibilité de le lancer avec des paramètres, de l&amp;#8217;isolation et des contraintes matérielles (&lt;span class="caps"&gt;RAM&lt;/span&gt;, &lt;span class="caps"&gt;CPU&lt;/span&gt;, &lt;span class="caps"&gt;IO&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Son immuabilité renforce et facilite la mise en place de tests fonctionnels et autres étapes de&amp;nbsp;qualification.&lt;/p&gt;
&lt;p&gt;La construction déterministe, et le résultat déterminé facilitent le déploiement, sur un ou plusieurs serveurs, et le retour en arrière en cas de&amp;nbsp;drame.&lt;/p&gt;
&lt;p&gt;Les conteneurs donnent aux développeurs la responsabilité de la création du conteneur, il choisit les paquets et applications nécessaires. Ces choix sont décrits de manière simple et lisible, qui seront versionnés, deux points qui facilitent l&amp;#8217;analyse statique et les&amp;nbsp;audits.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;étape de créations des images et les tests/analyses qui suivent doivent être construits depuis un service d&amp;#8217;intégration&amp;nbsp;continue.&lt;/p&gt;
&lt;p&gt;La recette de build automatique, les versions d&amp;#8217;outils explicites, et les différentes astuces pour faciliter le développement diminuent drastiquement le ticket d&amp;#8217;entrée pour un nouveau développeur sur un projet. Avec une &lt;span class="caps"&gt;VM&lt;/span&gt;, on comptait un jour, avec des conteneurs, une&amp;nbsp;heure.&lt;/p&gt;
&lt;h2 id="composition"&gt;Composition&lt;/h2&gt;
&lt;p&gt;Docker-compose est le standard de fait pour décrire la composition de services, hey wouais, l&amp;#8217;&lt;span class="caps"&gt;OCI&lt;/span&gt; n&amp;#8217;a encore rien dit&amp;nbsp;là-dessus.&lt;/p&gt;
&lt;p&gt;Tout comme&amp;nbsp;le &lt;code&gt;Dockerfile&lt;/code&gt; décrit de manière non ambigüe la création d&amp;#8217;une&amp;nbsp;image, &lt;code&gt;docker-compose.yml&lt;/code&gt; va décrire l&amp;#8217;assemblage de différents services, avec les paramètres et les liens entre&amp;nbsp;eux.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;outil &lt;code&gt;docker-compose&lt;/code&gt; va gérer tranquillement tout plein de détails pénibles, comme la création d&amp;#8217;un réseau isolé, les dépendances entres services, les logs, et même un peu de scale.
Il est bon pour mettre à jour le service que l&amp;#8217;on vient de mettre à jour et de relancer les services qui l&amp;#8217;utilisent, sans forcément tout&amp;nbsp;relancer.&lt;/p&gt;
&lt;p&gt;Techniquement, le&amp;nbsp;fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; expose à peu près tous les choix que peut faire un développeur pour décrire son application. Bon, Swarm a un peu cochonné le format, mais ça reste anecdotique. Pour ce qui manque, les &lt;em&gt;labels&lt;/em&gt; permettent de décrire les intentions sans trop de difficultés ou de&amp;nbsp;circonvolutions.&lt;/p&gt;
&lt;p&gt;Le format du&amp;nbsp;fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; permet de générer des configurations vers d&amp;#8217;autres outils :
 - &lt;a href="http://kompose.io/"&gt;Kompose&lt;/a&gt; pour Kubernetes
 - &lt;a href="https://github.com/paypal/dce-go"&gt;dce-go&lt;/a&gt; pour &lt;a href="http://mesos.apache.org/"&gt;Mesos&lt;/a&gt;
 - Bon, &lt;a href="https://www.nomadproject.io/"&gt;Nomad&lt;/a&gt; est tellement peu opiniated qu&amp;#8217;il faudra faire vos choix et votre&amp;nbsp;moulinette.&lt;/p&gt;
&lt;h2 id="supervision"&gt;Supervision&lt;/h2&gt;
&lt;p&gt;Le&amp;nbsp;service &lt;code&gt;dockerd&lt;/code&gt; (&lt;code&gt;containerd&lt;/code&gt; dans les faits) est aussi un superviseur, il va suivre et commander le cycle de vie des différents&amp;nbsp;conteneurs.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dockerd&lt;/code&gt; empiète un peu&amp;nbsp;sur &lt;code&gt;systemd&lt;/code&gt;, et les deux projets aiment ce jeter des cailloux&amp;nbsp;dessus.&lt;/p&gt;
&lt;p&gt;Le monde se divise en deux catégories, enfin, le monde, les services : systèmes et applicatif. Les applications s&amp;#8217;appuyant sur les services systèmes (en dessous), pour être exposées à des utilisateurs&amp;nbsp;(au-dessus).&lt;/p&gt;
&lt;p&gt;Systemd, ça gère la couche système, et seul l&amp;#8217;utilisateur root peut le&amp;nbsp;manipuler.&lt;/p&gt;
&lt;p&gt;La première cible de Docker est la couche applicative, et n&amp;#8217;importe qui avec le bon certificat &lt;span class="caps"&gt;SSL&lt;/span&gt; ou le bon groupe peut l&amp;#8217;utiliser. Ça ne veut pas pour autant dire que c&amp;#8217;est une super idée d&amp;#8217;avoir des utilisateurs qui tripote des dockers en prod depuis la&amp;nbsp;console.&lt;/p&gt;
&lt;p&gt;Il est tout à fait légitime d&amp;#8217;avoir un service lancé&amp;nbsp;par &lt;code&gt;systemd&lt;/code&gt; qui gère des conteneurs systèmes .C&amp;#8217;est comme ça qu&amp;#8217;est implémenté Kubernetes, d&amp;#8217;ailleurs, avec&amp;nbsp;son &lt;code&gt;kubelet&lt;/code&gt;, et &lt;a href="https://blog.mobyproject.org/containerd-namespaces-for-docker-kubernetes-and-beyond-d6c43f565084"&gt;les namespaces&amp;nbsp;de &lt;code&gt;containerd&lt;/code&gt; vont pouvoir encore mieux isoler ces groupes distincts de conteneurs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Par contre, vouloir orchestrer un ensemble de services formant une application avec un ensemble de&amp;nbsp;fichiers &lt;code&gt;`.service&lt;/code&gt; systemd est une drôle d&amp;#8217;idée. Les conteneurs sont des fils&amp;nbsp;des &lt;code&gt;docker-containerd-shim&lt;/code&gt; qui vont cafter au&amp;nbsp;démon &lt;code&gt;containerd&lt;/code&gt; l&amp;#8217;état du conteneur, excluant&amp;nbsp;complètement &lt;code&gt;systemd&lt;/code&gt; de la boucle, qui serait bien incapable de superviser quoi que ce&amp;nbsp;soit.&lt;/p&gt;
&lt;h2 id="instrumentation"&gt;Instrumentation&lt;/h2&gt;
&lt;p&gt;Docker va gérer des services, bien rangés dans des cgroups qui maintiennent des métriques, et branche &lt;span class="caps"&gt;STDOUT&lt;/span&gt; vers une sortie quelconque, un fichier ou un service de logging.
&lt;a href="https://12factor.net/"&gt;12 factors&lt;/a&gt; prétends que ça suffit, mais c&amp;#8217;est juste le strict minimum au niveau&amp;nbsp;observabilité.&lt;/p&gt;
&lt;p&gt;Il faut passer rapidement à des trucs plus sérieux, comme des rapports d&amp;#8217;erreurs avec Sentry, des logs structurés (au format fluentd), des mesures métiers avec les exports prometheus pour les technos asynchrones, et statsd pour les&amp;nbsp;autres.&lt;/p&gt;
&lt;h2 id="routage"&gt;Routage&lt;/h2&gt;
&lt;p&gt;C&amp;#8217;est bien, d&amp;#8217;avoir plein de conteneurs, mais vous allez exposer tout ça, dans la plupart des cas avec une seule &lt;span class="caps"&gt;IP&lt;/span&gt; et un seul port, 443 à priori (pour bien faire, cette &lt;span class="caps"&gt;IP&lt;/span&gt; sera flottante, et vous la collerez à une machine virtuelle en&amp;nbsp;vie).&lt;/p&gt;
&lt;p&gt;Il va donc falloir un peu de coordination entre les conteneurs et des règles de routages (et de la répartition de&amp;nbsp;charge).&lt;/p&gt;
&lt;h2 id="persistance"&gt;Persistance&lt;/h2&gt;
&lt;p&gt;C&amp;#8217;est bien d&amp;#8217;avoir des conteneurs &amp;#8220;quelque part&amp;#8221;, qui bougent en fonction des vagues. Mais cette mobilité n&amp;#8217;est possible qu&amp;#8217;avec des conteneurs sans état, état qui va bien devoir être quelque part. Pour ça, il faut une solution de stockage distribué, en mode blob, à la S3 (ou &lt;a href="https://www.minio.io/"&gt;Minio&lt;/a&gt;), ou en mode block, à la iSCSI. Donc, du &lt;a href="https://ceph.com/"&gt;Ceph&lt;/a&gt; ou une offre &lt;span class="caps"&gt;SAAS&lt;/span&gt; spécifique et propriétaire que votre fournisseur de nuage vous proposera&amp;nbsp;gentiment.&lt;/p&gt;
&lt;h2 id="orchestration"&gt;Orchestration&lt;/h2&gt;
&lt;p&gt;Pour gérer un ensemble de conteneurs sur plusieurs machines, il faut de l&amp;#8217;orchestration. L&amp;#8217;orchestrateur a la charge de décider comment les conteneurs vont être répartis sur les différentes machines, que faire quand un noeud disparait ou quand un nouveau&amp;nbsp;apparait.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;orchestrateur va s&amp;#8217;appuyer sur service clef/valeur distribué (comme &lt;a href="https://coreos.com/etcd/"&gt;etcd&lt;/a&gt;) pour mettre à disposition de tout le monde tout ce qui est paramétrage et maintenir une carte de&amp;nbsp;l&amp;#8217;existant.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;orchestrateur va réagir à un ensemble d&amp;#8217;évènements, et en réponse à des commandes, déclencher des évènements (et des actions) comme les stratégies de mises à jour des conteneurs, ou la mythique promesse du Cloud : lancer des &lt;span class="caps"&gt;VM&lt;/span&gt; pour empiler encore plus de conteneurs pour accompagner un pic de charge, puis les éteindre tranquillement une fois la vague&amp;nbsp;passée.&lt;/p&gt;
&lt;p&gt;Voilà, là, vous êtes à l&amp;#8217;étape Kubernetes, et pour ça, vous avez pieusement suivi toutes les étapes précédentes, car vous avez besoin de plusieurs serveurs pour héberger votre&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;La vieille promesse de l&amp;#8217;élastique du nuage n&amp;#8217;est possible qu&amp;#8217;avec de l&amp;#8217;orchestration, et donc des conteneurs, de l&amp;#8217;intégration continue, de l&amp;#8217;observabilité, une séparation nette entre l&amp;#8217;expression de l&amp;#8217;intention (sous la responsabilité des devs) et les choix de son implémentation (sous la responsabilité des&amp;nbsp;ops).&lt;/p&gt;</content><category term="Ops"></category><category term="kubernetes"></category><category term="docker"></category><category term="conteneur"></category></entry><entry><title>Google a très envie de manger tout l’hébergement Internet</title><link href="http://blog.garambrogne.net/google-veut-manger-Internet.html" rel="alternate"></link><published>2018-05-08T17:30:00+02:00</published><updated>2018-05-08T17:30:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2018-05-08:/google-veut-manger-Internet.html</id><summary type="html">&lt;p&gt;Google part conquérir l&amp;#8217;hébergement Internet et veut éradiquer les &lt;span class="caps"&gt;DSI&lt;/span&gt; avec toute la finesse d&amp;#8217;un tapis de&amp;nbsp;bombes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Le marché de l&amp;#8217;hébergement Cloud arrive à maturité. OpenStack a réussi à éradiquer tous les pouilleux, la place est libérée pour les trois gros, Amazon, Google et Azure, qui vont pouvoir s&amp;#8217;étriper&amp;nbsp;tranquilement.&lt;/p&gt;
&lt;p&gt;Le marché du cloud a été découvert par Amazon pour répondre à ses propres besoins.
Amazon s&amp;#8217;est contentée d&amp;#8217;ouvrir son outillage interne, très rapidement organisé en unités autonomes, chacune étant cliente les unes les autres. L&amp;#8217;offre d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt; est un mélange d&amp;#8217;outils standards (machines virtuelles, disques distants…) et de services spécifiques dont certains sont maintenant des standards de fait (S3) d&amp;#8217;autres non (dynamodb), pour ensuite proposer des services de plus haut niveau, comme les bases infogérées ou les &lt;span class="caps"&gt;CDN&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Cette offre a parfaitement accompagné la première vague du Web et corresponds tout à fait aux besoins des gros sites américains, qui ont besoin de grosses croissances pour arriver rapidement en position abusivement dominante à l&amp;#8217;échelle de l&amp;#8217;Occident, pour commencer à être bénéficiaire. Ces sites croissent comme des crabes, chaque changement de carapace correspondant à un tour de table, et il faut devenir bien plus gros. Le fameux &amp;#8220;ça scale&amp;#8221; tant vanté par le&amp;nbsp;Cloud.&lt;/p&gt;
&lt;p&gt;Google a bien vu grandir Amazon, et à toujours eut une technologie plus performante, plus intégrée, plus tout, en fait. Une partie des technos d&amp;#8217;Amazon sont issus des white papers de Google. Sauf que Google, à la différence d&amp;#8217;Amazon a une approche très cathédrale, très intégrée, très peu ouverte, en fait. Google a zappé l&amp;#8217;étape des machines virtuelles en passant directement aux conteneurs (les CPUs n&amp;#8217;étaient pas près quand la question s&amp;#8217;est posée) bien plus efficaces pour densifier, et tout à fait compatible avec un partage non agressif, coordonné des&amp;nbsp;ressources.&lt;/p&gt;
&lt;p&gt;Google, une fois ses services bien installés, a su faire des efforts pour avoir des clients qui consomment ses services (Android, Chrome), mais a été longtemps une grosse quiche pour ses offres d&amp;#8217;hébergement. Google semble tout simplement inadapté pour reprendre une idée qui n&amp;#8217;est pas la sienne. Un peu comme les humiliations de Google+ ou pire, &lt;a href="https://fr.wikipedia.org/wiki/Google_Wave"&gt;Google Wave&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Par contre, leurs services sont bons : gmail, calendar, docs. Ils sont surtout encore meilleurs pour bosser en groupe, et remplacent des outils forts pénibles et corporate, comme la suite Microsoft, ou pire encore, &lt;span class="caps"&gt;IBM&lt;/span&gt; Lotus. Outre leur ergonomie agréable, ces outils éradiquent les &lt;a href="https://fr.wikipedia.org/wiki/Bastard_Operator_From_Hell"&gt;&lt;span class="caps"&gt;BOFH&lt;/span&gt;&lt;/a&gt;, craints par tous, direction comme utilisateurs. Avec ces offres orientés pro, Google à mis un pied dans&amp;nbsp;l&amp;#8217;entreprise.&lt;/p&gt;
&lt;p&gt;Donc, les services sont bons, les clients sont bons, que reste-t-il à récupérer dans la chaine de valeur? Les serveurs, pas les siens qui sont optimums depuis une grosse décennie, mais ceux des&amp;nbsp;autres.&lt;/p&gt;
&lt;p&gt;Première vague, Google App Engine. Google reprends son approche interne, les applications sont contraintes, mais bénéficient d&amp;#8217;un ensemble de services de très bonne qualité (comme la BigTable). Sauf que non. Les utilisateurs n&amp;#8217;ont que faire de contraintes les obligeants à réinventer la roue alors qu&amp;#8217;ils existent tant de trucs open source permettant de sortir trop tôt un gros tas de boue mal fagoté. Les services proposés sont effectivement mythiques et permette de scaler plus haut que sa carte bleue, mais le ticket d&amp;#8217;entrées (technique) est rédhibitoire, tout ça pour un truc captif aux performances&amp;nbsp;fluctuantes.&lt;/p&gt;
&lt;p&gt;Entre temps apparait Docker, qui réassemble de manière cohérente les bouts de kernel que Google à écrit pour sa propre solution de conteneur, &lt;a href="https://en.wikipedia.org/wiki/Lmctfy"&gt;&lt;span class="caps"&gt;LMCTFY&lt;/span&gt;&lt;/a&gt;. La première appropriation du logiciel libre des conteneurs, &lt;span class="caps"&gt;LXC&lt;/span&gt;, n&amp;#8217;était guère plus qu&amp;#8217;un gros &lt;span class="caps"&gt;POC&lt;/span&gt; myope. Docker apporte la notion de services composables, immuables et paramétrable, et, arme secrète, Docker fonctionne très bien sur les ordinateurs des développeurs (Linux, Mac et Windows à égalité), et c&amp;#8217;est en fait le premier critère pour l&amp;#8217;adoption d&amp;#8217;une nouvelle&amp;nbsp;technologie.&lt;/p&gt;
&lt;p&gt;Google manque terriblement d&amp;#8217;empathie, ils sont capables d&amp;#8217;inventer des tas de concepts, mais sont incapables de se mettre au niveau des gros ploucs de  développeurs. Ou des gros ploucs de pipelettes des réseaux sociaux,&amp;nbsp;d&amp;#8217;ailleurs.&lt;/p&gt;
&lt;p&gt;Ils ont du coup mise en place une nouvelle approche, un peu comme leurs withes papers qui deviennent des outils utilisables par le reste du monde. Ils prennent une de leur technologie interne, qui à eut le temps de bien maturer, et ne conservent que les concepts ou les parties bas niveau (comme les patchs kernels) pour les confier à des gens extérieurs, des gens bizarres avec des sentiments. De fait, ils exfiltrent eux même leurs technologies, en petits bouts autonomes. Ils ont réussi ça avec golang, qui a une vraie communauté, et qui a d&amp;#8217;ailleurs servi de base à Docker, et à toute une génération des services de bas niveau. Cela dit en passant, un des gros échec d&amp;#8217;OpenStack est python, tout à fait inadapté à ce genre de tache, mais bon à l&amp;#8217;époque, les autres langages disponibles (java, C++) auraient aussi été un&amp;nbsp;drame.&lt;/p&gt;
&lt;p&gt;Donc, pour attaquer l&amp;#8217;hébergement, il faut d&amp;#8217;abord un socle, et faire des concessions aux ploucs. C&amp;#8217;est l&amp;#8217;offre Google Cloud, qui expose une couche super standard : de la machine virtuelle avec &lt;span class="caps"&gt;KVM&lt;/span&gt; (avec comme effet de bord de faire chier &lt;span class="caps"&gt;AMZ&lt;/span&gt; qui a choisi l&amp;#8217;autre virtualisation, Xen, qui n&amp;#8217;est pas dans le kernel vanilla). Réel effort de la part de Google qui n&amp;#8217;utilise pas de virtualisation en interne.
Ensuite les trucs de base, comme le réseau, les disques distants, du stockage objet compatible S3, du routage, du &lt;span class="caps"&gt;SQL&lt;/span&gt; infogéré, quelques services qui envoient du rêve (à base de big data et de machine&amp;nbsp;learning).&lt;/p&gt;
&lt;p&gt;Voilà, ça, c&amp;#8217;est le ticket d&amp;#8217;entrée pour commencer à discuter. Pour avoir des utilisateurs et donc du débug, Google utilise la technique super classique, une offre massive de crédits à des startoups ambitieuses, des incitations à la création de sociétés de services qui ne font que du Google Cloud, des espèces de franchises, en fait.
Pour montrer leur humanité, Google met en place un système astucieux de conseil sur le dimensionnement des services pour que l&amp;#8217;on crame moins de sous. Un peu comme une cafétéria qui conseillerait de ne pas remplir son plateau pour pas que ça finisse à la poubelle, en fait. Payer ce que l&amp;#8217;on utilise, ça semble bête, mais ça le différencie de &lt;span class="caps"&gt;AWS&lt;/span&gt;, qui eux ont pour motto &amp;#8220;la carte bleu comme seul&amp;nbsp;limite&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Voilà, Google a aussi son offre Cloud crédible, mais comment aller plus loin? la guerre des prix avec &lt;span class="caps"&gt;AWS&lt;/span&gt; est un peu vaine, et ce genre de blague finit souvent en victoire à la &lt;a href="https://fr.wikipedia.org/wiki/Victoire_%C3%A0_la_Pyrrhus"&gt;Pyrrhus&lt;/a&gt;. Non, la cible n&amp;#8217;est pas d&amp;#8217;aller tout de suite vers le &lt;a href="https://fr.wikipedia.org/wiki/Co%C3%BBt_marginal"&gt;coût marginal&lt;/a&gt; et la notion de &lt;a href="https://fr.wikipedia.org/wiki/Commodit%C3%A9"&gt;commodité&lt;/a&gt; (pas les &lt;span class="caps"&gt;WC&lt;/span&gt;, le barbarisme traduisant &amp;#8220;commodity&amp;#8221;). La cible est d&amp;#8217;aller là où il y a des sous, d&amp;#8217;aller s&amp;#8217;occuper des &lt;span class="caps"&gt;DSI&lt;/span&gt; et de l&amp;#8217;hébergement de leurs&amp;nbsp;serveurs.&lt;/p&gt;
&lt;p&gt;Pour ça, Google est en train de déployer la tactique finaude du tapis de bombe, le carpet bombing des années&amp;nbsp;1940.&lt;/p&gt;
&lt;p&gt;Première vagues, les services, gmail et Google Docs, les utilisateurs veulent du Google et trouvent ringard les services&amp;nbsp;existants.&lt;/p&gt;
&lt;p&gt;Deuxième vague, l&amp;#8217;hébergement classique. Techniquement, la notion de &lt;span class="caps"&gt;VM&lt;/span&gt; n&amp;#8217;est qu&amp;#8217;une transition, c&amp;#8217;est compliqué, dur à dimensionner, dur à maintenir au fil du temps. Comme solution plus adéquate, il y a bien sûr les conteneurs, standard maintenant officialisé par l&amp;#8217;&lt;a href="https://www.opencontainers.org/"&gt;&lt;span class="caps"&gt;OCI&lt;/span&gt;&lt;/a&gt; et installé sur tous les postes de développeurs. Docker est mature coté dévelopement, mais pas encore coté hébergement, pour ça, il faut de l&amp;#8217;orchestration, et il y a maintenant Kubernetes, offre exfiltrée à partir d&amp;#8217;une techno maison de Google, &lt;a href="https://ai.google/research/pubs/pub43438"&gt;Borg&lt;/a&gt;, qui devient tout aussi standard et officiel que les conteneurs de l&amp;#8217;&lt;span class="caps"&gt;OCI&lt;/span&gt;, mais dans un autre consortium, le &lt;a href="https://www.cncf.io/"&gt;&lt;span class="caps"&gt;CNCF&lt;/span&gt;&lt;/a&gt;, avec ses amis Envoy (pour le routage) et Prometheus (pour les&amp;nbsp;mesures).&lt;/p&gt;
&lt;p&gt;Kubernetes est open source, bien spécifié, bien maintenu, plein de vie. Pourquoi se méfier de son coté captif? Tout simplement par ce qu&amp;#8217;il est tout aussi complexe à héberger qu&amp;#8217;un &lt;a href="https://ceph.com/"&gt;Ceph&lt;/a&gt; dont tout le monde vante l&amp;#8217;open source sans pour autant le déployer. Kubernetes va éradiquer l&amp;#8217;offre Docker Swarm qui n&amp;#8217;a jamais convaincu grand monde, et débarquer par défaut sur tout les postes de dev avec le très confortable &lt;a href="https://kubernetes.io/docs/getting-started-guides/minikube/"&gt;minikube&lt;/a&gt;. Kubernetes a besoin de s&amp;#8217;appuyer sur des services de persistances (mode bloques et objets), bien pénible à héberger. En partant de bare metal, il faut donc assumer un Ceph en plus de Kubernetes comme ticket&amp;nbsp;d&amp;#8217;entrée.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est de plus en plus visible : la notion d&amp;#8217;open source fusionne avec la notion de standard et de portabilité. L&amp;#8217;hébergement de Kubernetes, même si on garde le choix entre 3 mammouths est de fait propriétaire, tout en gardant le développement open source. L&amp;#8217;open source se fait commodifier la gueule, en fait, mais avec le sourire. L&amp;#8217;open source est utilisé pour faire chier les autres, Microsoft peut en&amp;nbsp;témoigner.&lt;/p&gt;
&lt;p&gt;Une bonne vague d&amp;#8217;hardware inaccessible, pour continuer à distancer la concurrence (gpu haut de gamme , &lt;a href="https://cloud.google.com/tpu/"&gt;tpu&lt;/a&gt;) toujours basée sur des frameworks bien libres comme &lt;a href="https://www.tensorflow.org/"&gt;Tensorflow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La prochaine vague, déjà bien amorcée, est le serverless, un buzzword pour imposer un framework n&amp;#8217;exposant que du code métier, facilitant de fait l&amp;#8217;hébergement (et basé sur des conteneurs encore plus&amp;nbsp;densifiés).&lt;/p&gt;
&lt;p&gt;Une vague de métrologie, avec &lt;a href="https://opencensus.io/"&gt;OpenCensus&lt;/a&gt; qui va permettre d&amp;#8217;unifier les mesures privées (les services fournit par Google), avec les mesures publics, dans le code&amp;nbsp;déployé.&lt;/p&gt;
&lt;p&gt;La vague suivante va être la sécurité avec &lt;a href="https://cloudplatform.googleblog.com/2018/05/Open-sourcing-gVisor-a-sandboxed-container-runtime.html"&gt;gVisor&lt;/a&gt; pour les conteneurs, et &lt;a href="https://asylo.dev/"&gt;Asylo&lt;/a&gt; pour les&amp;nbsp;enclaves.&lt;/p&gt;
&lt;p&gt;La tactique est des plus classiques. On abuse de l&amp;#8217;exclusivité tant qu&amp;#8217;on peut, tout en démolissant la marge de la génération précédente de technologie, tout en maintenant un cout d&amp;#8217;entrée énorme, pour exclure les nouveaux entrants. Ça permet à la fois de réduire ses propres coûts tout en asséchant la concurrence. On a ainsi vu récemment passer la libération des frameworks de machine learning, en mode terre brulée (Google, Nokia, Microsoft…), pour finalement se concentrer sur la vente d&amp;#8217;hébergement avec du matériel spécifique (les fameux &lt;span class="caps"&gt;TPU&lt;/span&gt;), ou &amp;#8220;as a service&amp;#8221; pour les utilisateurs aux compétences limités : &lt;a href="https://cloud.google.com/automl/"&gt;AutoML&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;À l&amp;#8217;époque de la ruée vers l&amp;#8217;or, le meilleur moyen de devenir riche était de vendre des pelles (avec l&amp;#8217;exclusivité), pas de creuser. L&amp;#8217;offre Cloud de Google nous vends des&amp;nbsp;pelles.&lt;/p&gt;</content><category term="Ops"></category><category term="Google"></category><category term="GCE"></category><category term="Kubernetes"></category></entry><entry><title>La tokenisation et les index inversés au service de la lecture des logs</title><link href="http://blog.garambrogne.net/tokenisation-lecture-logs.html" rel="alternate"></link><published>2018-03-07T19:23:00+01:00</published><updated>2018-03-07T19:23:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2018-03-07:/tokenisation-lecture-logs.html</id><summary type="html">&lt;p&gt;Les logs ne servent pas qu&amp;#8217;à remplir les disques, mais dans tous les cas, ce n&amp;#8217;est pas aux humains de lire tout&amp;nbsp;ça.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Les logs servent à remplir les disques (souvent) et à déboguer (parfois), mais pas que.
Logstash a démontré que l&amp;#8217;on pouvait entasser des logs dans un moteur de recherche pour faciliter les analyses postmortem d&amp;#8217;incidents. On peut aussi compter des trucs et des machins pour faire de jolis graphes, voir même, en lâchant des sous, avoir des alertes avec &lt;a href="https://www.elastic.co/fr/products/x-pack/alerting"&gt;alerting&lt;/a&gt;. Elasticsearch propose aussi la notion de &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html"&gt;percolate&lt;/a&gt; avec ses recherches à l&amp;#8217;envers.
Les notions existent et sont même déjà implémentées (en&amp;nbsp;Java).&lt;/p&gt;
&lt;p&gt;Voici une présentation pédagogique, avec une implémentation toute légère en golang, pour chercher des motifs dans des flots de logs : &lt;a href="https://github.com/athoune/yangtze"&gt;Yangtze 长江&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="concept"&gt;Concept&lt;/h2&gt;
&lt;p&gt;L&amp;#8217;idée de base est d&amp;#8217;enregistrer des requêtes, et de leur soumettre des lignes de log. C&amp;#8217;est la percolation&amp;nbsp;d&amp;#8217;Elasticsearch.&lt;/p&gt;
&lt;p&gt;Pour ne pas tout bouffer la &lt;span class="caps"&gt;RAM&lt;/span&gt; et le &lt;span class="caps"&gt;CPU&lt;/span&gt;, les expressions régulières ne seront surtout pas utilisées de manière&amp;nbsp;systématique.&lt;/p&gt;
&lt;p&gt;Même si &lt;a href="https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html"&gt;Grok&lt;/a&gt; permet d&amp;#8217;écrire des regex concises et compréhensibles, même si &lt;a href="https://github.com/google/re2"&gt;&lt;span class="caps"&gt;RE2&lt;/span&gt;&lt;/a&gt; limite le bazar des très classiques &lt;span class="caps"&gt;PCRE&lt;/span&gt;, les regexps restent un marteau qui scalent mal. C&amp;#8217;est vrai, Piwik (enfin, &lt;a href="https://matomo.org/"&gt;Matomo&lt;/a&gt;) l&amp;#8217;a prouvé avec son &lt;a href="https://github.com/matomo-org/device-detector"&gt;parser de user-agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Indexer une expression régulière oblige à la déplier, et c&amp;#8217;est rapidement pénible à faire (du code ruby existe et fonctionne plutôt bien), pour un résultat peu&amp;nbsp;concluant.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;idée est de découper le motif en jetons (en tokens, en mots, quoi), et de les utiliser pour indexer les requêtes. En découpant les lignes de logs en jeton, on peut retrouver les requêtes correspondantes, et ne tester le motif que sur les lignes qui ont les jetons&amp;nbsp;nécessaires.&lt;/p&gt;
&lt;h2 id="implementation"&gt;Implémentation&lt;/h2&gt;
&lt;h3 id="motifs"&gt;Motifs&lt;/h3&gt;
&lt;p&gt;Plutôt que d&amp;#8217;aller se coincer dans du vrai regex, je pars sur un sous-ensemble minimaliste&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; un&amp;nbsp;jeton&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt; zéro ou un&amp;nbsp;jeton&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...&lt;/code&gt; plusieurs&amp;nbsp;jetons&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tokenisation"&gt;Tokenisation&lt;/h3&gt;
&lt;p&gt;La tokenization est tout aussi brutale, les jetons sont constitués de &lt;a href="https://golang.org/pkg/unicode/#IsLetter"&gt;lettres&lt;/a&gt;, de &lt;a href="https://golang.org/pkg/unicode/#IsDigit"&gt;chiffres&lt;/a&gt;,&amp;nbsp;de &lt;code&gt;_&lt;/code&gt; et&amp;nbsp;de &lt;code&gt;-&lt;/code&gt;. Tout le reste, ponctuation, parenthèses, espaces… est considéré comme des&amp;nbsp;séparateurs.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Je&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mange&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;carottes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Va donner&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Je&lt;/li&gt;
&lt;li&gt;mange&lt;/li&gt;
&lt;li&gt;des&lt;/li&gt;
&lt;li&gt;carottes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les tokens qui sont indexés (ceux des requêtes, pas ceux des lignes) sont rangés dans un tableau, et un identifiant (un simple compteur) leur est attribué. Par convention, les identifiants commencent à 1, 0 est réservé pour gérer le rien, un token qui n&amp;#8217;est pas&amp;nbsp;indexé.&lt;/p&gt;
&lt;p&gt;Avec la tokenisation et les identifiants, on peut transformer une ligne en une suite de&amp;nbsp;nombres.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;a ... c&amp;quot; =&amp;gt; [1, 0, 2] // un motif
&amp;quot;a a b c&amp;quot; =&amp;gt; [1, 1, 0, 2] // une ligne
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="bitset"&gt;Bitset&lt;/h3&gt;
&lt;p&gt;Les &lt;a href="https://godoc.org/github.com/willf/bitset"&gt;bitsets&lt;/a&gt; sont de longues suites de booléens, leur rang correspond à leur valeur. Par convention, le 0 est&amp;nbsp;toujours &lt;code&gt;false&lt;/code&gt;. Avec un bitset, on ne peut savoir que si un jeton est présent ou non, on perd sa position et son nombre d&amp;#8217;occurrences. Par contre, il est facile d&amp;#8217;effectuer des opérations booléennes, des opérations sur des &lt;a href="https://fr.wikipedia.org/wiki/Ensemble_(informatique)"&gt;ensembles&lt;/a&gt;, ou compter le nombre de bits allumés (on parle alors de&amp;nbsp;cardinalité).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;a ... c&amp;quot; =&amp;gt; ◻︎◼︎◼︎
&amp;quot;c . a&amp;quot; =&amp;gt; ◻︎◼︎◼︎
&amp;quot;a a&amp;quot; =&amp;gt; ◻︎◼︎◻︎
&amp;quot;a a b c&amp;quot; =&amp;gt; ◻︎◼︎◼︎
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une ligne avec une cardinalité nulle ne matchera jamais, première&amp;nbsp;simplification.&lt;/p&gt;
&lt;p&gt;Si le bitset d&amp;#8217;une requête n&amp;#8217;est pas inclus dans le bitstet de la ligne, on sait que ça ne matchera&amp;nbsp;pas.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set(&amp;quot;a ... c&amp;quot;) ⊂ set(&amp;quot;a a b c&amp;quot;)
set(&amp;quot;c . a&amp;quot;) ⊂ set(&amp;quot;a a b c&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si ces deux premières conditions sont passées, on peut tester le motif (en travaillant sur les suites de&amp;nbsp;nombres).&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pattern(&amp;quot;a ... c&amp;quot;).match(&amp;quot;a a b c&amp;quot;) == True
pattern(&amp;quot;c . a&amp;quot;).match(&amp;quot;a a b c&amp;quot;) == False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour limiter le travail, les requêtes sont indexées par jetons. Pour l&amp;#8217;instant, le jeton de la ligne utilisé pour tirer les requêtes potentielles est le premier. Pour bien faire, il faudrait trier les jetons par occurrence, et choisir le moins courant (et donc le plus&amp;nbsp;discriminant).&lt;/p&gt;
&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;
&lt;p&gt;Les performances de &lt;a href="https://golang.org/pkg/regexp/"&gt;regexp&lt;/a&gt; de golang, basé sur &lt;span class="caps"&gt;RE2&lt;/span&gt;, sont très bonnes. Tokenizer une ligne, vérifier si le motif correspond est plus que de tester une expression régulière. Mais ça tombe bien, ce n&amp;#8217;est pas le cas d&amp;#8217;usage, on tokenize une fois, exclue puis test le motif plein de fois.
Obtenir l&amp;#8217;identifiant d&amp;#8217;un jeton est super rapide, travailler avec les bitsets et vérifier les motifs sont aussi super rapide, bien plus qu&amp;#8217;une regexp. Le cout de la tokenization (qui gère de manière orthodoxe l&amp;#8217;&lt;span class="caps"&gt;UTF8&lt;/span&gt;) est très rapidement amorti quand il s&amp;#8217;agit de tester une brouette de&amp;nbsp;motifs.&lt;/p&gt;
&lt;p&gt;Préparer une ligne est 4 fois plus long que de lancer une regexp, mais tester un motif est presque 10 fois plus&amp;nbsp;rapide.&lt;/p&gt;
&lt;p&gt;Bon, un benchmark est toujours un peu raide et rien ne remplace un vrai&amp;nbsp;usage.&lt;/p&gt;
&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;p&gt;Avoir de jolis tests unitaires et des benchmarks, c&amp;#8217;est bien, avoir du code utilisable, c&amp;#8217;est mieux. Le premier usage sera de surveiller des flots de logs (tail, &lt;a href="https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html"&gt;journald&lt;/a&gt;, &lt;a href="https://www.fluentd.org/"&gt;fluentd&lt;/a&gt;…) pour soit lever des erreurs qualifiées (&lt;a href="https://sentry.io/"&gt;sentry&lt;/a&gt;, &lt;a href="https://prometheus.io/docs/alerting/alertmanager/"&gt;alertmanager&lt;/a&gt;, &lt;a href="http://alerta.io/"&gt;alerta&lt;/a&gt;), soit compter des trucs qui finiront dans du timeseries. Des actions à la &lt;a href="https://github.com/fail2ban/fail2ban"&gt;fail2ban&lt;/a&gt; sont potentielement&amp;nbsp;envisageable.&lt;/p&gt;</content><category term="Dev"></category><category term="token"></category><category term="log"></category><category term="alert"></category></entry><entry><title>RPC</title><link href="http://blog.garambrogne.net/rpc.html" rel="alternate"></link><published>2018-01-22T22:16:00+01:00</published><updated>2018-01-22T22:16:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2018-01-22:/rpc.html</id><summary type="html">&lt;p&gt;Les &lt;span class="caps"&gt;RPC&lt;/span&gt; sont les héraults de l&amp;#8217;informatique maintenant&amp;nbsp;distribué.&lt;/p&gt;</summary><content type="html">&lt;h1 id="rpc"&gt;&lt;span class="caps"&gt;RPC&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;RPC&lt;/span&gt; existent depuis longtemps, ils ont leurs heures de gloire (&lt;a href="https://fr.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture"&gt;&lt;span class="caps"&gt;CORBA&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://fr.wikipedia.org/wiki/SOAP"&gt;&lt;span class="caps"&gt;SOAP&lt;/span&gt;&lt;/a&gt;), et de déchéance (&lt;span class="caps"&gt;CORBA&lt;/span&gt;, &lt;span class="caps"&gt;SOAP&lt;/span&gt;). Ils reviennent sur le devant de la scène avec l&amp;#8217;invasion de Javascript au dépens des templates cotés serveurs, et surtout des micro&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Techniquement, un &lt;span class="caps"&gt;RPC&lt;/span&gt;, c&amp;#8217;est : une sérialisation, un protocole, une couche transport.
Certains &lt;span class="caps"&gt;RPC&lt;/span&gt; proposant différentes sérialisations (une binaire, une texte), et même différents&amp;nbsp;transports.&lt;/p&gt;
&lt;h2 id="serialisation"&gt;Sérialisation&lt;/h2&gt;
&lt;p&gt;La réponse magique est &lt;a href="https://fr.wikipedia.org/wiki/JavaScript_Object_Notation"&gt;&lt;span class="caps"&gt;JSON&lt;/span&gt;&lt;/a&gt;, ce qui est un progrès par rapport à la réponse magique &lt;a href="https://fr.wikipedia.org/wiki/Extensible_Markup_Language"&gt;&lt;span class="caps"&gt;XML&lt;/span&gt;&lt;/a&gt;, d&amp;#8217;il y a 20 ans. Mais &lt;a href="http://seriot.ch/parsing_json.php"&gt;le &lt;span class="caps"&gt;JSON&lt;/span&gt; est ambigu, chaque langage et même chaque bibliothèque va gérer de manière différente chaque point mal spécifié&lt;/a&gt;. Il ne gère ni différencie pas les entiers des flottants, ni les nombres en 64 bits, le binaire doit passer par du base64 et donc prendre plein de&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/BSON"&gt;&lt;span class="caps"&gt;BSON&lt;/span&gt;&lt;/a&gt; corrige le &lt;span class="caps"&gt;JSON&lt;/span&gt; en ajoutant de nouveaux types et un format binaire, mais il reste très lié à &lt;a href="https://www.mongodb.com/"&gt;Mongodb&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://msgpack.org/"&gt;MessagePack&lt;/a&gt; fait peu ou prou la même chose, mais de manière neutre, sans être lié à un&amp;nbsp;produit.&lt;/p&gt;
&lt;p&gt;On reste dans la famille des sérialisations auto descriptive, qui permet techniquement de faire un peu n&amp;#8217;importe quoi de chaque coté d&amp;#8217;un &lt;span class="caps"&gt;RPC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Cette approche prend beaucoup de place, avec Mongodb, il n&amp;#8217;est pas rare que les clefs prennent plus de place que les&amp;nbsp;contenus.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://avro.apache.org/docs/current/"&gt;Avro&lt;/a&gt; propose une approche intermédiaire. On envoie la grammaire, en &lt;span class="caps"&gt;JSON&lt;/span&gt;, puis le contenu en binaire, bien&amp;nbsp;compacte.&lt;/p&gt;
&lt;p&gt;La mode est maintenant à la sérialisation avec grammaire. Cette grammaire étant neutre, elle peut être utilisée par différents langages pour effectuer les sérialisations/deserialisation. Ces grammaires sont incrémentales, il est possible d&amp;#8217;ajouter des informations, sans casser la compatibilité avec&amp;nbsp;l&amp;#8217;existant.&lt;/p&gt;
&lt;p&gt;Petite collision de date, Google avait déjà son &lt;a href="https://developers.google.com/protocol-buffers/"&gt;protobuff&lt;/a&gt; (qu&amp;#8217;il utilisait bien au-delà des &lt;span class="caps"&gt;RPC&lt;/span&gt;) quand Facebook a publié spécification et implémentation de référence pour son &lt;a href="https://thrift.apache.org/"&gt;Thrift&lt;/a&gt;, fort similaire. Protobuff a été libéré après&amp;nbsp;Thrift.&lt;/p&gt;
&lt;p&gt;Pour aller un cran plus loin dans la recherche de performance, il existe des sérialisations qui ne sérialisent pas, et permettent le zéro-copie, ce qui est fort pratique pour les &lt;span class="caps"&gt;RPC&lt;/span&gt; locaux, directement en mémoire, sans réseau. &lt;a href="https://arrow.apache.org/"&gt;Apache Arraow&lt;/a&gt; et &lt;a href="https://capnproto.org/"&gt;Cap&amp;#8217;n Proto&lt;/a&gt; font ce genre de&amp;nbsp;chose.&lt;/p&gt;
&lt;h2 id="protocole"&gt;Protocole&lt;/h2&gt;
&lt;p&gt;La première étape est la notion de question du client au serveur, et la réponse correspondante, dans l&amp;#8217;autre&amp;nbsp;sens.&lt;/p&gt;
&lt;h3 id="rest"&gt;&lt;span class="caps"&gt;REST&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Representational_state_transfer"&gt;&lt;span class="caps"&gt;REST&lt;/span&gt;&lt;/a&gt; n&amp;#8217;est tout simplement pas un protocole &lt;span class="caps"&gt;RPC&lt;/span&gt;. C&amp;#8217;est un standard largement diffusé, le meilleur ami de &lt;a href="https://curl.haxx.se/"&gt;curl&lt;/a&gt;, mais ce n&amp;#8217;est pas un &lt;span class="caps"&gt;RPC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;REST&lt;/span&gt; permet d&amp;#8217;exposer rapidement des données hiérarchisées, pas plus, et &lt;a href="http://graphql.org/"&gt;GraphQL&lt;/a&gt; (quand il sera sec) est bien parti pour lui ravir cette&amp;nbsp;niche.&lt;/p&gt;
&lt;p&gt;Des formalisations comme &lt;a href="https://swagger.io/"&gt;Swagger&lt;/a&gt; (maintenant &lt;a href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt;) sont clairement les bienvenus,
et même indispensable, mais ne suffisent pas à définir de bout en bout les échanges.
Du code spécifique côté client et serveur reste indispensable, et fragilise&amp;nbsp;l&amp;#8217;ensemble.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.elastic.co/fr/products/elasticsearch"&gt;ElasticSearch&lt;/a&gt; reste le parfait exemple des limites de &lt;span class="caps"&gt;REST&lt;/span&gt; :
le protocole/sérialisation ne brime pas les performances, par contre,
les fonctions spécifiques comme les batchs, la pagination, la gestion du cluster,
rendent indispensable l&amp;#8217;utilisation des &lt;a href="https://www.elastic.co/guide/en/elasticsearch/client/index.html"&gt;drivers officiels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; a le même souci, avec son truandage pour gérer les flots &lt;span class="caps"&gt;TTY&lt;/span&gt;, et ils sont en train de &lt;a href="https://github.com/containerd/containerd/tree/master/api/"&gt;tout re-ranger en grpc&lt;/a&gt; avec &lt;a href="https://containerd.io/"&gt;Containerd&lt;/a&gt;, pour faciliter les échanges de serveur à&amp;nbsp;serveur.&lt;/p&gt;
&lt;h3 id="multiplexage"&gt;Multiplexage&lt;/h3&gt;
&lt;p&gt;Les connexions ne sont pas illimitées, que ce soit des clients web vers un serveur, ou même en interne, avec le nombre de connexions simultanées que peuvent ouvrir les technologies asynchrones. Le multiplexage s&amp;#8217;impose assez vite. Les requêtes ont des identifiants, et les réponses arrivent dans l&amp;#8217;ordre de leur&amp;nbsp;résolution.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://xmpp.org/extensions/"&gt;&lt;span class="caps"&gt;XMPP&lt;/span&gt;&lt;/a&gt; a formalisé cette approche avec ses stanzas, tout comme &lt;a href="http://www.jsonrpc.org/specification"&gt;&lt;span class="caps"&gt;JSON&lt;/span&gt;-&lt;span class="caps"&gt;RPC&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="flot"&gt;Flot&lt;/h3&gt;
&lt;p&gt;Les &lt;span class="caps"&gt;RPC&lt;/span&gt; sont confrontés aux mêmes problèmes que les Big Data, où il faut choisir entre le gros débit des traitements par lots (&lt;a href="https://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt;), et les faibles latences des flots (&lt;a href="https://spark.apache.org/"&gt;Spark&lt;/a&gt;, &lt;a href="http://storm.apache.org/"&gt;Storm&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;La gestion de flot facilite le multiplexage, limite la taille des buffers, et permet de commencer tôt le traitement du&amp;nbsp;message.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt;/1 propose une gestion de flot descendant, avec &lt;a href="https://fr.wikipedia.org/wiki/Server-sent_events"&gt;EventSource&lt;/a&gt;, mais rien en flot montant, il faut alors dégainer &lt;a href="https://fr.wikipedia.org/wiki/WebSocket"&gt;Websocket&lt;/a&gt;. Hum, est-ce bien raisonnable d&amp;#8217;utiliser WebSocket pour de la communication de serveur à&amp;nbsp;serveur?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol/2"&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt;/2&lt;/a&gt; gère le multiplexage, et permet la gestion de flot montant et&amp;nbsp;descendant.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.github.io/finagle/"&gt;Finagle&lt;/a&gt;, de Twitter, propose un &lt;span class="caps"&gt;RPC&lt;/span&gt; complet basé sur Thrift, mais surtout explique bien la notion de multiplexage, de streaming et de méta information. Finagle a publié beaucoup de lecture pédagogique indispensable, mais il est fortement lié à &lt;a href="http://www.scala-lang.org/"&gt;Scala&lt;/a&gt;, et même à Twitter, en&amp;nbsp;fait.&lt;/p&gt;
&lt;h2 id="meta-information"&gt;Méta&amp;nbsp;information&lt;/h2&gt;
&lt;p&gt;Les RPCs ont besoin d&amp;#8217;informations complètement différentes de ce qui sera utilisé par les fonctions exposées. La notion de format, de trace, d&amp;#8217;authentification, de chiffrage et de compression par exemple. Toutes les belles choses que l&amp;#8217;on est habitué à utiliser avec &lt;span class="caps"&gt;HTTP&lt;/span&gt;, en&amp;nbsp;fait.&lt;/p&gt;
&lt;h2 id="parralelisation"&gt;Parralélisation&lt;/h2&gt;
&lt;p&gt;On est habitué aux contraintes d&amp;#8217;&lt;span class="caps"&gt;HTTP&lt;/span&gt; qui a l&amp;#8217;habitude d&amp;#8217;imposer des temps de réponse courts, et de se faire couper en cas de &amp;#8220;timeout&amp;#8221;. Les &lt;span class="caps"&gt;RPC&lt;/span&gt; n&amp;#8217;ont pas les mêmes contraintes sur les temps de réponse. Les langages synchrones sont alors brimés par la difficulté de paralléliser des taches de durée très variables. L&amp;#8217;implémentation de &lt;span class="caps"&gt;GRPC&lt;/span&gt; en python, par exemple, travaille avec un pool de 10 connexions, ce qui n&amp;#8217;est pas farfelu si on garde la règle magique de 2 workers par coeur de processeurs. En augmentant le pool, on prend le risque d&amp;#8217;avoir des traitements avec peu d&amp;#8217;attentes &lt;span class="caps"&gt;IO&lt;/span&gt; et de faire exploser le&amp;nbsp;load.&lt;/p&gt;
&lt;p&gt;De toute façon, la notion de file d&amp;#8217;attente devra être géré, soit par le langage qui va accepter plein de connections, soit via un service spécifique, comme &lt;a href="https://redis.io"&gt;Redis&lt;/a&gt;, &lt;a href="https://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;, &lt;a href="https://nsq.io/"&gt;nsq&lt;/a&gt;, &lt;a href="https://kafka.apache.org/"&gt;kafka&lt;/a&gt;…. Les services de file d&amp;#8217;attente permettent de faire de jolies choses, souvent pénibles à reproduire dans son code, comme la répartition de charge ou la reprise de traitement en cas de vautrage. Google a une sainte horreur des serveurs de file d&amp;#8217;attentes, déjà qu&amp;#8217;il n&amp;#8217;aiment pas les proxy, car il se focalise sur la course à la latence. Twitter semble les suivre dans cette&amp;nbsp;approche.&lt;/p&gt;
&lt;h2 id="debogabilite"&gt;Débogabilité&lt;/h2&gt;
&lt;p&gt;Déboguer un &lt;span class="caps"&gt;RPC&lt;/span&gt; bancal peut rapidement devenir un&amp;nbsp;enfer.&lt;/p&gt;
&lt;h3 id="traces"&gt;Traces&lt;/h3&gt;
&lt;p&gt;Il est indispensable d&amp;#8217;utiliser une gestion d&amp;#8217;erreur centralisée (à la &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt;), des métriques (&lt;a href="https://github.com/etsy/statsd"&gt;statsd&lt;/a&gt; ou des compteurs &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;), et rapidement des logs unifiés, des traces en fait, comme le proposent &lt;a href="http://opentracing.io/"&gt;OpenTracing&lt;/a&gt; (&lt;a href="https://zipkin.io/"&gt;Zipkin&lt;/a&gt;, &lt;a href="https://github.com/jaegertracing/jaeger"&gt;Jaeger&lt;/a&gt;, &lt;a href="https://github.com/sourcegraph/appdash"&gt;Appdash&lt;/a&gt;…).&lt;/p&gt;
&lt;h3 id="ligne-de-commande"&gt;Ligne de&amp;nbsp;commande&lt;/h3&gt;
&lt;p&gt;Il est difficile de se passer du confort&amp;nbsp;de &lt;code&gt;curl&lt;/code&gt; + &lt;span class="caps"&gt;JSON&lt;/span&gt;, tous les &lt;span class="caps"&gt;RPC&lt;/span&gt; de la galaxie proposent un équivalent, avec plus ou moins de complétion et de retour&amp;nbsp;d&amp;#8217;erreurs.&lt;/p&gt;
&lt;h3 id="sniffer"&gt;Sniffer&lt;/h3&gt;
&lt;p&gt;Encore plus culte&amp;nbsp;que &lt;code&gt;curl&lt;/code&gt;, il existe &lt;a href="https://fr.wikipedia.org/wiki/Tcpdump"&gt;tcpdump&lt;/a&gt;, pour filtrer et voir passer un flot sur le réseau.
&lt;a href="https://github.com/elastic/beats/tree/master/packetbeat"&gt;PacketBeat&lt;/a&gt; propose de jolies bases pour construire un&amp;nbsp;sniffeur.&lt;/p&gt;
&lt;p&gt;La généralisation de &lt;span class="caps"&gt;TLS&lt;/span&gt;, même sur les réseaux privés rends ça rapidement&amp;nbsp;pénible.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;approche sniffing est du débogage en boite noir, il est tellement plus simple d&amp;#8217;être dans la place, et de profiter de l&amp;#8217;approche boite blanche, comme Google le préconise :
&lt;a href="https://github.com/google/protobuf/issues/3303#issuecomment-313236016"&gt;ticket grpc et tecpdump&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finagle confirme cette approche avec &lt;a href="https://twitter.github.io/twitter-server/index.html"&gt;Twitter-Server&lt;/a&gt;,
mais comme Finagle n&amp;#8217;impose pas &lt;span class="caps"&gt;TLS&lt;/span&gt;, il est possible de tricher : &lt;a href="https://github.com/pinterest/thrift-tools"&gt;thrift-tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rapidement, les &lt;span class="caps"&gt;RPC&lt;/span&gt; vont être distribués (sur plusieurs serveurs), il est assez illusoire de penser pouvoir attraper l&amp;#8217;ensemble des flots pour ensuite isoler celui qui nous intéresse.
Bricoler son Mysql/Postgres/Mongodb ou autres services tiers pour avoir du log spécifique n&amp;#8217;est pas la meilleur des idées, dans ce cas, le pcap est légitime, mais pour du code qui nous appartiens, avec la possibilité d&amp;#8217;utiliser des middlewares ou pires des proxys, ce serait dommage de ne pas en&amp;nbsp;profiter.&lt;/p&gt;
&lt;h2 id="transport"&gt;Transport&lt;/h2&gt;
&lt;p&gt;Les rpc ne sont pas forcément fascinés par la couche transport, mais la plupart s&amp;#8217;épanouissent dans &lt;span class="caps"&gt;HTTP&lt;/span&gt;, la garantie de pouvoir circuler sur internet sans drame, et sans faire crier les firewalls corporates. Il existe des transports plus spécialisés, comme &lt;a href="http://zeromq.org/"&gt;zeromq&lt;/a&gt;, &lt;a href="https://nats.io/"&gt;nats&lt;/a&gt; ou même plus exotique comme &lt;a href="https://fr.wikipedia.org/wiki/Streaming_text_oriented_message_protocol"&gt;&lt;span class="caps"&gt;STOMP&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hum, peut-on considérer un broker comme une couche transport?
Dans ce cas, Kafka et &lt;a href="https://fr.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol"&gt;&lt;span class="caps"&gt;AMQP&lt;/span&gt;&lt;/a&gt; en font&amp;nbsp;partis.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt;/2 permet de profiter de l&amp;#8217;héritage &lt;span class="caps"&gt;HTTP&lt;/span&gt;/1 (Internet, proxy…) mais le protocole est bien récent, et les bibliothèques pour le gérer ne sont pas bien répandues.
C&amp;#8217;est mature pour des serveurs webs (nginx, traefik…), pour des clients (Chrome, Firefox…), mais pour du code, golang écrase un peu&amp;nbsp;tout.&lt;/p&gt;
&lt;h2 id="ceinture-et-bretelle"&gt;Ceinture et&amp;nbsp;bretelle&lt;/h2&gt;
&lt;p&gt;Rien n&amp;#8217;interdit d&amp;#8217;exposer une fonction avec différentes&amp;nbsp;approches.&lt;/p&gt;
&lt;p&gt;Les usages sont trop divers pour qu&amp;#8217;une seule réponse universelle puisse être envisagée.
Les interfaces utilisateurs sont maintenant en &lt;span class="caps"&gt;HTML5&lt;/span&gt; avec beaucoup de Javascript, et proposent des interfaces utilisateurs réactives. Il est donc indispensable d&amp;#8217;utiliser &lt;span class="caps"&gt;HTTP&lt;/span&gt; pour les &lt;span class="caps"&gt;UI&lt;/span&gt; web. L&amp;#8217;outillage &lt;span class="caps"&gt;HTTP2&lt;/span&gt; en javascript est pour l&amp;#8217;instant introuvable, il faut donc basculer sur les plus classiques &lt;span class="caps"&gt;HTTP&lt;/span&gt;/1.1 et ses extensions, comme &lt;span class="caps"&gt;SSE&lt;/span&gt; et surtout&amp;nbsp;Websocket.&lt;/p&gt;
&lt;p&gt;Pour les appels internes, &lt;span class="caps"&gt;HTTP&lt;/span&gt;/1 est un peu court des pattes et va poser des problèmes avec les parallélisations massives que permettent les technologies&amp;nbsp;asynchrones.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;approche la plus saine me parait de surtout se concentrer sur l&amp;#8217;utilisation de grammaire neutre, qui permettent de générer (ou d&amp;#8217;introspecter pour les langages qui n&amp;#8217;aiment pas le code écrit par des robots), les clients et serveurs, et même de générer plusieurs protocoles pour une seule fonction&amp;nbsp;exposée.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.twitch.tv/twirp-a-sweet-new-rpc-framework-for-go-5f2febbf35f"&gt;Twirp&lt;/a&gt; propose cette approche après s&amp;#8217;être cassé les dents sur du grpc, qui reste pourtant la référence de ce genre&amp;nbsp;d&amp;#8217;approche.&lt;/p&gt;
&lt;p&gt;Grpc fait des choix plutôt autoritaires pour présenter ce que Google estime être l’état de l&amp;#8217;art.
Personnellement, aucun de ces choix ne me frustre, c&amp;#8217;est plutôt le manque de maturité des bibliothèques qui m&amp;#8217;inquiètent. Les fonctions avancées de la bibliothèque golang n&amp;#8217;a pas de numéro de release, avec une documentation légère.
La bibliothèque Python embarque un gros machin en C pour gérer toute la partie &lt;span class="caps"&gt;HTTP&lt;/span&gt;/2, avec un pool de workers. Je suppose que les autres langages sont dans le même état. Par contre, je n&amp;#8217;ai aucune inquiétude sur la pérennité de cette technologie et sur le fait que les bibliothèques vont évolués, et surtout que tous les langages vont avoir l&amp;#8217;outillage requis pour gérer comme il faut &lt;span class="caps"&gt;HTTP&lt;/span&gt;/2/&lt;/p&gt;
&lt;p&gt;Il existe une passerelle Grpc/&lt;span class="caps"&gt;REST&lt;/span&gt;,
&lt;a href="https://github.com/grpc-ecosystem/grpc-gateway"&gt;Grpc-gateway&lt;/a&gt;
qui permet d&amp;#8217;exposer une fonction, en utilisant la grammaire grpc, sous deux formes, du &lt;span class="caps"&gt;REST&lt;/span&gt; avec la documentation Swagger, et du&amp;nbsp;Grpc.&lt;/p&gt;
&lt;p&gt;La couche &lt;span class="caps"&gt;HTTP&lt;/span&gt;/2 n&amp;#8217;est pas forcément la réponse universelle, pour certains projets discrets, utilisant un grand nombre de connexions, le surcout en &lt;span class="caps"&gt;RAM&lt;/span&gt; peut être pénalisant. Le projet a fait le choix d&amp;#8217;utiliser un protocole différent (du &lt;span class="caps"&gt;TCP&lt;/span&gt; sans stream), et de générer le code avec les grammaires &lt;span class="caps"&gt;GRPC&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="bref"&gt;Bref&lt;/h2&gt;
&lt;p&gt;L&amp;#8217;informatique est maintenant distribué, que ce soit sur des coeurs de processeurs, ou des serveurs. Les &lt;span class="caps"&gt;RPC&lt;/span&gt; sont maintenant indispensable (ne serait-ce que par la traction des &lt;span class="caps"&gt;UI&lt;/span&gt; complexes en Javascript), mais le domaine n&amp;#8217;est pas encore&amp;nbsp;stabilisé.&lt;/p&gt;
&lt;p&gt;En l&amp;#8217;état, la seul recommendation sans risque est d&amp;#8217;utiliser des grammaires pour déclarer ses APIs, et d&amp;#8217;instrumenter son code (log, erreurs,&amp;nbsp;métriques).&lt;/p&gt;</content><category term="Dev"></category><category term="rpc"></category><category term="protobuf"></category><category term="thrift"></category><category term="grpc"></category><category term="jsonrpc"></category><category term="soap"></category><category term="rest"></category><category term="json"></category></entry><entry><title>Persister les conteneurs</title><link href="http://blog.garambrogne.net/persister-les-conteneurs.html" rel="alternate"></link><published>2016-11-28T09:34:00+01:00</published><updated>2016-11-28T09:34:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2016-11-28:/persister-les-conteneurs.html</id><summary type="html">&lt;p&gt;Les conteneurs permettent d&amp;#8217;isoler les ressources utilisées par des services, et avec un peu de discipline, offrent la promesse d&amp;#8217;utiliser au mieux un groupe de machines. Mais comment assurer de la persistence dans cet environnement&amp;nbsp;mobile?&lt;/p&gt;</summary><content type="html">&lt;h1 id="isoler"&gt;Isoler&lt;/h1&gt;
&lt;p&gt;Les conteneurs permettent d&amp;#8217;isoler les ressources utilisées par des services, et avec un peu de discipline, offrent la promesse d&amp;#8217;utiliser au mieux un groupe de&amp;nbsp;machines.&lt;/p&gt;
&lt;h1 id="distribuer"&gt;Distribuer&lt;/h1&gt;
&lt;p&gt;Distribuer des services est maintenant un classique, pour ne pas dire un prérequis.
Par contre, il faut ensuite assumer; surdimensionner pour assurer quelques pics devient rapidement un problème de place, mais surtout de puissance&amp;nbsp;électrique.&lt;/p&gt;
&lt;p&gt;La distribution permet d&amp;#8217;avoir plus de ressources à disposition, mais aussi d&amp;#8217;assurer plus de résilience, en proposant plusieurs instances de chaque service, capable de prendre le relai en cas d&amp;#8217;incident, ou tout simplement de démarrer un nouveau service sans états, pour remplacer un autre&amp;nbsp;défaillant.&lt;/p&gt;
&lt;h2 id="lelastique-dans-le-nuage"&gt;L&amp;#8217;élastique dans le&amp;nbsp;nuage&lt;/h2&gt;
&lt;p&gt;Le cloud nous promet cette élasticité, mais la taille efficace d&amp;#8217;une machine virtuelle est bien trop grosse pour un découpage propre, alors que le conteneur a lui, la taille et la mobilité&amp;nbsp;requise.&lt;/p&gt;
&lt;p&gt;Les ressources nécessaires fournies par la machine aux services sont le classique triplet : processeur, mémoire, stockage.
Triplet que sait bien border un conteneur. En faisant attention à avoir du code immuable et versionné, il est possible d&amp;#8217;avoir plusieurs instances d&amp;#8217;un même conteneur, sur une ou plusieurs machines.
Mobilité et ubiquité que sait bien gérer un&amp;nbsp;conteneur.&lt;/p&gt;
&lt;h2 id="orchestrer-lutilisation-des-ressources"&gt;Orchestrer l&amp;#8217;utilisation des&amp;nbsp;ressources&lt;/h2&gt;
&lt;p&gt;Il est possible de gérer un ensemble de conteneurs, avec un nombre d&amp;#8217;instances&amp;nbsp;fluctuant.&lt;/p&gt;
&lt;p&gt;Pouvoir optimiser cette fluctuation permet de dépasser le taux ridicule d&amp;#8217;utilisation de la plupart des datacenters : 15%.
Une gestion efficace des ressources est l&amp;#8217;arme secrète de Google, Borg. Enfin, l&amp;#8217;une des armes&amp;nbsp;secrètes.&lt;/p&gt;
&lt;p&gt;Le principe est simple : on met à disposition un ensemble de ressources, on commande ensuite des ressources, et un planning est établi. Des outils sont mis à disposition permettant la coordination ou la découverte de services, qui, couplés à des proxyies, permet la mise à disposition publique des&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Google a publié, sur le tard (une dizaine d&amp;#8217;année après sa mise en place), leur approche avec de la gestion de cluster avec &lt;a href="http://research.google.com/pubs/pub43438.html"&gt;Borg&lt;/a&gt;. Ils insistent beaucoup sur la différence entre les tâches longues (qui ne doivent pas s&amp;#8217;arrêter), et les taches éphémères (sensibles aux&amp;nbsp;latences).&lt;/p&gt;
&lt;p&gt;&lt;a href="http://mesos.apache.org/"&gt;Mesos&lt;/a&gt; a été une des premières implémentations libres de planification de ressources, mais il vise gros, et propose de généraliser ce que proposait le &lt;span class="caps"&gt;YARN&lt;/span&gt; de Hadoop. Si vous ne comptez pas vos ressources en rack ou en datacenter, il faut chercher autre&amp;nbsp;chose.&lt;/p&gt;
&lt;p&gt;Des choses comme &lt;a href="https://www.nomadproject.io/"&gt;Nomad&lt;/a&gt; (le sage), &lt;a href="https://www.docker.com/products/docker-swarm"&gt;Swarm&lt;/a&gt; (le tonitruant), ou &lt;a href="http://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; (le&amp;nbsp;conquérant).&lt;/p&gt;
&lt;p&gt;Ces outils sont utilisables sur une poignée de serveur, et en se basant sur des serveurs dédiés, ou des clouds frustres, il est possible d&amp;#8217;être bien moins cher que des clouds toutes&amp;nbsp;options.&lt;/p&gt;
&lt;h1 id="persister"&gt;Persister&lt;/h1&gt;
&lt;p&gt;La partie pénible dans l&amp;#8217;usage des clusters est la persistance, distribuer le travail n&amp;#8217;est finalement pas si complexe : le loadbalancing naïf est la première étape, avant de passer à de la parallélisation et au fameux&amp;nbsp;map-reduce.&lt;/p&gt;
&lt;p&gt;Le stockage dans un environnement distribué est un problème de cohérence, &lt;a href="https://gist.github.com/jboner/2841832"&gt;latence&lt;/a&gt;, et de&amp;nbsp;débit.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;existe pas de réponse absolue, mais un ensemble de choix et de tuning.
Le &lt;a href="https://en.wikipedia.org/wiki/CAP_theorem"&gt;théorème du &lt;span class="caps"&gt;CAP&lt;/span&gt;&lt;/a&gt; résume cet ensemble de contraintes, et l&amp;#8217;obligation d&amp;#8217;en choisir deux parmi les trois lettres &lt;em&gt;Consistency&lt;/em&gt;, &lt;em&gt;Availabiliy&lt;/em&gt; et &lt;em&gt;Partition tolerance&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="le-cas-du-conteneur"&gt;Le cas du&amp;nbsp;conteneur&lt;/h3&gt;
&lt;p&gt;Docker (et rkt) proposent gentiment d&amp;#8217;emballer une application, pour pouvoir la versionner, et la déployer simplement. Une généralisation du &lt;span class="caps"&gt;WAR&lt;/span&gt; de Java, quoi. Le prérequis est d&amp;#8217;avoir une application en lecture seule. Il est ensuite possible de monter des volumes au sein des conteneurs, pour proposer des dossiers en lecture/écriture. Sauf que ces volumes montés n&amp;#8217;apportent ni l&amp;#8217;ubiquité, ni la distribution, et seront liés à un serveur. Un conteneur permet d&amp;#8217;abstraire et de généraliser plein de choses, mais pas la persistance. Pour un système distribué, c&amp;#8217;est&amp;nbsp;ballot.&lt;/p&gt;
&lt;h2 id="qui-est-le-responsable-du-stockage"&gt;Qui est le responsable du&amp;nbsp;stockage?&lt;/h2&gt;
&lt;p&gt;Première décision, la persistance est-elle de la responsabilité du système d&amp;#8217;exploitation l&amp;#8217;application&amp;nbsp;?&lt;/p&gt;
&lt;h3 id="disques-distants"&gt;Disques&amp;nbsp;distants&lt;/h3&gt;
&lt;p&gt;Le moyen le plus simple pour déporter le stockage est d&amp;#8217;utiliser un disque distant.
Ce disque pourra être démonté puis remonté sur un autre serveur, il est mobile, mais n&amp;#8217;existera qu&amp;#8217;en un seul exemplaire. Pour peu que le serveur propose des instantanés, il sera possible d&amp;#8217;avoir du &lt;span class="caps"&gt;COW&lt;/span&gt;, et de monter des réplicats du disque sur d&amp;#8217;autres&amp;nbsp;noeuds.&lt;/p&gt;
&lt;p&gt;Tous les clouds proposent un service de ce genre, avec de l&amp;#8217;iSCSI par exemple.
Openstack fournit une &lt;a href="https://wiki.openstack.org/wiki/CinderSupportMatrix"&gt;liste indigeste des disques qu&amp;#8217;il peut utiliser en mode bloc&lt;/a&gt;.
Il est possible de redonder ce genre de disque, côté client, avec un simple &lt;span class="caps"&gt;RAID&lt;/span&gt;, ou côté serveur, avec de la magie, si c&amp;#8217;est un &lt;span class="caps"&gt;SAN&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;existe pas grand-chose en libre comme serveur iSCSI. Il existe le vénérable &lt;span class="caps"&gt;NBD&lt;/span&gt;, et le plus récent &lt;a href="https://bitbucket.org/hirofuchi/xnbd/wiki/Home"&gt;xNBD&lt;/a&gt;, prévu initialement pour du netboot. Coreos bosse sur &lt;a href="https://coreos.com/blog/torus-distributed-storage-by-coreos.html"&gt;Torus&lt;/a&gt;, un serveur &lt;span class="caps"&gt;NBD&lt;/span&gt; distribué, utilisable dans quelques&amp;nbsp;années?.&lt;/p&gt;
&lt;p&gt;Le Graal étant le &lt;a href="http://docs.ceph.com/docs/jewel/rbd/rbd/"&gt;rdb&lt;/a&gt; de Ceph (qui a rejoint le giron de RedHat), montable en &lt;span class="caps"&gt;NBD&lt;/span&gt; ou en Fuse. &lt;span class="caps"&gt;OVH&lt;/span&gt; propose maintenant du &lt;a href="https://www.ovh.com/fr/cloud/cloud-disk-array/"&gt;Ceph &lt;em&gt;as a service&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour les aventuriers, il existe des systèmes de fichiers distribués(&lt;a href="https://oss.oracle.com/projects/ocfs2/"&gt;&lt;span class="caps"&gt;OCFS2&lt;/span&gt;&lt;/a&gt; d&amp;#8217;Oracle, &lt;a href="https://en.wikipedia.org/wiki/GFS2"&gt;&lt;span class="caps"&gt;GFS2&lt;/span&gt;&lt;/a&gt; de Red Hat… ), permettant l&amp;#8217;ubiquité d&amp;#8217;un même disque monté sur plusieurs serveurs. En dehors du calcul scientifique, on trouve peu d&amp;#8217;exemples d&amp;#8217;usage de cette approche. Pour garantir la cohérence des données, un serveur de verrou est utilisé, garantissant des problèmes de performance pour les écritures&amp;nbsp;concourantes.&lt;/p&gt;
&lt;p&gt;Il existe des abstractions exposant une &lt;span class="caps"&gt;API&lt;/span&gt; unique vers diverses implémentations, comme &lt;a href="https://clusterhq.com/flocker/introduction/"&gt;Flocker&lt;/a&gt; ou &lt;a href="https://wiki.openstack.org/wiki/Cinder"&gt;Cinder&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="fichiers-distants"&gt;Fichiers&amp;nbsp;distants&lt;/h3&gt;
&lt;p&gt;Le partage en mode fichier permet un accès à distance, mais aussi multiple, à un ensemble de fichiers.
&lt;span class="caps"&gt;NFS&lt;/span&gt; et &lt;a href="https://docs.microsoft.com/fr-fr/azure/storage/storage-how-to-use-files-linux"&gt;Samba, que seul Azure préconise&lt;/a&gt; en sont les exemples les plus&amp;nbsp;classiques.&lt;/p&gt;
&lt;p&gt;Le partage de fichier est fort pratique, mais, pour assurer des performances correctes, certains raccourcis sont pris : une partie des engagements requis pour un système de fichier &lt;span class="caps"&gt;UNIX&lt;/span&gt; ne sont plus&amp;nbsp;assumés.&lt;/p&gt;
&lt;p&gt;Toutes les bases de données dédient une page de la documentation pour promettre d&amp;#8217;atroces souffrances à qui osera mélanger &lt;span class="caps"&gt;NFS&lt;/span&gt; et base de donnée : &lt;a href="https://www.postgresql.org/docs/9.4/static/creating-cluster.html"&gt;Postgres et &lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://docs.datastax.com/en/cassandra/1.2/cassandra/architecture/architecturePlanningAntiPatterns_c.html"&gt;Cassandra et &lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;L&amp;#8217;architecture de &lt;span class="caps"&gt;NFS&lt;/span&gt; commence à dater, et ce n&amp;#8217;est pas un, mais un ensemble de services qui sont utilisés, pas forcément prévu pour un environnement hostile comme l&amp;#8217;est le cloud.
&lt;span class="caps"&gt;NFS&lt;/span&gt; ne prévoit rien pour la distribution du&amp;nbsp;stockage.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="https://www.ovh.com/fr/nas/"&gt;serveurs &lt;span class="caps"&gt;NAS&lt;/span&gt; haute dispo, comme celui proposé par &lt;span class="caps"&gt;OVH&lt;/span&gt;&lt;/a&gt;, sont souvent des baies de disques connectés en optique avec deux serveurs, l&amp;#8217;un &lt;em&gt;ou&lt;/em&gt; l&amp;#8217;autre serveur pourra utiliser la baie, du &lt;span class="caps"&gt;RAID&lt;/span&gt; assurera la redondance des disques, une double alimentation, la redondance d&amp;#8217;énergie, mais en cas d&amp;#8217;incident sur la baie, bah, pas de bol, vous avez&amp;nbsp;perdu.&lt;/p&gt;
&lt;p&gt;Des implémentations propriétaires, comme l&amp;#8217;&lt;a href="https://aws.amazon.com/fr/efs/"&gt;&lt;span class="caps"&gt;EFS&lt;/span&gt;&lt;/a&gt; d&amp;#8217;Amazon permettent d&amp;#8217;avoir de la redondance et de la&amp;nbsp;distribution.&lt;/p&gt;
&lt;p&gt;Pour les acharnés du partage en mode fichier, il existe le tant redouté &lt;a href="https://www.gluster.org/"&gt;GlusterFS&lt;/a&gt;, récemment racheté par RedHat. La prochaine version majeur, &lt;a href="https://github.com/gluster/glusterd2"&gt;GlusterFS 4&lt;/a&gt; sera d&amp;#8217;ailleurs en Go et utilisera Etcd.
On voit son nom apparaitre de temps en temps, comme l&amp;#8217;&lt;a href="https://docs.acquia.com/cloud/arch"&gt;offre un poil daté d&amp;#8217;Acquia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;incontournable &lt;a href="http://ceph.com/ceph-storage/file-system/"&gt;Ceph propose aussi un partage en mode fichier&lt;/a&gt;, mais cette option semble un peu boudé, c&amp;#8217;est pour le mode bloc qu&amp;#8217;il est réputé. &lt;a href="https://about.gitlab.com/2016/11/10/why-choose-bare-metal/"&gt;Gitlab l&amp;#8217;utilise pour son offre &lt;span class="caps"&gt;SAAS&lt;/span&gt;&lt;/a&gt; mais ils ont souffert de la non-homogénéité des &lt;span class="caps"&gt;IO&lt;/span&gt; des clouds publiques, et sont passé à du &lt;em&gt;bare metal&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="objets-distants"&gt;Objets&amp;nbsp;distants&lt;/h3&gt;
&lt;p&gt;Assurer un stockage distribué, aussi à l&amp;#8217;aise en lecture qu&amp;#8217;en écriture,
sans corrompre les données, fut une des premières questions que Google dut résoudre, et surtout la première révélation des secrets de Google.
Le &lt;a href="http://static.googleusercontent.com/media/research.google.com/fr//archive/gfs-sosp2003.pdf"&gt;white paper sur le &lt;em&gt;Google File System&lt;/em&gt;&lt;/a&gt;
fut publié en 2003. Il servit de base à la création du &lt;a href="https://en.wikipedia.org/wiki/Apache_Hadoop#HDFS"&gt;&lt;span class="caps"&gt;HDFS&lt;/span&gt;&lt;/a&gt; du projet&amp;nbsp;Hadoop.&lt;/p&gt;
&lt;p&gt;Le principe est simple, les fichiers sont immuables, mais versionnés. Il n&amp;#8217;est pas possible de modifier un fichier existant, on va en créer une nouvelle version, et par défaut, on lira la dernière version&amp;nbsp;disponible.&lt;/p&gt;
&lt;p&gt;Le premier service d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;, au-delà de la simple &lt;span class="caps"&gt;VM&lt;/span&gt;, fut un service de persistance distribué, S3. Le &lt;a href="http://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf"&gt;Dynamo paper&lt;/a&gt; expliquant la notion d&amp;#8217;anneau virtuelle pour une architecture sans maître avec de la redondance, fut publié en&amp;nbsp;2007.&lt;/p&gt;
&lt;p&gt;Pour S3, les fichiers sont immuables, versionnés, redondés, accessibles directement en &lt;span class="caps"&gt;HTTP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Aucun cloud digne de ce nom n&amp;#8217;ose sortir sans proposer un clone plus ou moins compatible de&amp;nbsp;S3.&lt;/p&gt;
&lt;p&gt;Tous les frameworks webs décents sont maintenant capables de gérer les uploads sur des stockage objets, avec des bibliothèques comme &lt;a href="https://github.com/carrierwaveuploader/carrierwave"&gt;CarrierWave&lt;/a&gt; ou &lt;a href="https://github.com/thoughtbot/paperclip"&gt;paperclip&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Toutes les bases de données savent (ou devraient savoir) maintenant faire des snapshots dans des stockages à la&amp;nbsp;S3.&lt;/p&gt;
&lt;h4 id="swift"&gt;Swift&lt;/h4&gt;
&lt;p&gt;Le clone de référence est &lt;a href="http://docs.openstack.org/developer/swift/"&gt;Swift&lt;/a&gt; d&amp;#8217;OpenStack.
Son architecture est extrêmement simple, à la limite du naïf, en python très classique.
Ils ont une excellente réputation sur la qualité de leur &lt;span class="caps"&gt;API&lt;/span&gt;, et la fidélité avec l&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt; du S3 d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="riak-s2"&gt;Riak-S2&lt;/h4&gt;
&lt;p&gt;Il existe aussi le moins connu &lt;a href="http://fr.basho.com/products/riak-s2/"&gt;Riak-S2&lt;/a&gt;, une surcouche au Riak de Basho, libéré il y a quelque temps.
C&amp;#8217;est une architecture Erlang contemporaine, au code particulièrement pédagogique et étonnamment lisible, compte tenu de la complexité du&amp;nbsp;bestiau.&lt;/p&gt;
&lt;h4 id="leofs"&gt;LeoFS&lt;/h4&gt;
&lt;p&gt;Pour les fans d&amp;#8217;Erlang, il y a aussi &lt;a href="http://leo-project.net/"&gt;LeoFS&lt;/a&gt;. Par contre, je ne sais pas trop qui l&amp;#8217;utilise en dehors de Rakuten, leur&amp;nbsp;sponsor.&lt;/p&gt;
&lt;h4 id="minio"&gt;Minio&lt;/h4&gt;
&lt;p&gt;En ce moment est en train d&amp;#8217;émerger &lt;a href="https://www.minio.io/"&gt;Minio&lt;/a&gt;, il propose une belle &lt;span class="caps"&gt;UI&lt;/span&gt; Web, mais surtout un serveur, en Go, ce qui change du Java et de&amp;nbsp;l&amp;#8217;Erlang.&lt;/p&gt;
&lt;p&gt;Son accueil enthousiaste vient du fait qu&amp;#8217;il scale bien à un. Ce serveur est utilisable sur un seul serveur, et son image Docker juste&amp;nbsp;marche.&lt;/p&gt;
&lt;p&gt;Il gère tellement bien un seul serveur que la version distribuée n&amp;#8217;est pas encore&amp;nbsp;finie.&lt;/p&gt;
&lt;p&gt;Par contre, il est capable de gérer une grappe de disque, et d&amp;#8217;assurer de la redondance avec le &lt;a href="https://fr.wikipedia.org/wiki/Code_de_Reed-Solomon"&gt;code de Reed-Solomon&lt;/a&gt;, utilisé par le système &lt;span class="caps"&gt;RAID&lt;/span&gt; pour les disques, et &lt;a href="https://www.backblaze.com/blog/reed-solomon/"&gt;vulgarisé dans un article par Backblaze&lt;/a&gt;, le plus grandiose des entasseurs de disques durs. En gérant eux même la parité, plutôt que la délégué à du &lt;span class="caps"&gt;RAID&lt;/span&gt; comme tout le monde, ils peuvent réparer objet par objet, et pas par&amp;nbsp;volume.&lt;/p&gt;
&lt;h4 id="rados-gateway-de-ceph"&gt;&lt;span class="caps"&gt;RADOS&lt;/span&gt; Gateway, de&amp;nbsp;Ceph&lt;/h4&gt;
&lt;p&gt;Le dernier, et pas des moindres, est &lt;a href="http://ceph.com/ceph-storage/object-storage/"&gt;&lt;span class="caps"&gt;RADOS&lt;/span&gt; Gateway&lt;/a&gt; de Ceph.
Ceph met à disposition sa solution de stockage &lt;span class="caps"&gt;RADOS&lt;/span&gt;, sous les trois approches :&amp;nbsp;blocs/fichiers/objets.&lt;/p&gt;
&lt;h3 id="base-de-donnees"&gt;Base de&amp;nbsp;données&lt;/h3&gt;
&lt;p&gt;Le stockage objet met un pied dans le plat. Il casse la compatibilité du système de fichier &lt;span class="caps"&gt;UNIX&lt;/span&gt;, pour proposer quelque chose de plus adapté aux environnements distribués.
Du coup, l&amp;#8217;application continue d&amp;#8217;écrire sur un disque local (les essais de stockage sans &lt;span class="caps"&gt;FS&lt;/span&gt; d&amp;#8217;Oracle ou Mysql ont fait un flop), mais vont se charger de la distribution avec réplication, répartition, cohérence,&amp;nbsp;réparation…&lt;/p&gt;
&lt;p&gt;Avoir une application distribuée, correctement implémentée est difficile.
Le projet &lt;a href="http://jepsen.io/analyses.html"&gt;Jepsen&lt;/a&gt; propose un framework d&amp;#8217;analyse et publie ses analyses de  différentes versions d&amp;#8217;applications.
Le résultat des analyses est souvent &amp;#8220;call me maybe&amp;#8221;, et il y a ensuite des corrections de l&amp;#8217;application&amp;nbsp;autopsiée.&lt;/p&gt;
&lt;h3 id="base-de-donnees-relationnelle"&gt;Base de données&amp;nbsp;relationnelle&lt;/h3&gt;
&lt;p&gt;Les bases de données relationnelles sont toutes capables de gérer de la réplication, en utilisant le très classique modèle maître-esclave. Postgresql a quand même attendu la version 9 pour proposer ça en natif, sans passer par un produit&amp;nbsp;tiers.&lt;/p&gt;
&lt;p&gt;Pour répartir les données sur plusieurs machines, il faut passer par du sharding, ce qui va poser des problèmes pour faire des jointures sur des données dispersées. Le sharding est traditionnellement applicatif, et fonctionne mieux sur des données bien isolées, comme des utilisateurs, par&amp;nbsp;exemple.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.citusdata.com/product/community"&gt;Citusdata&lt;/a&gt; propose d&amp;#8217;améliorer Postgresql en lui mettant à disposition&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un &lt;a href="https://github.com/citusdata/cstore_fdw"&gt;stockage orienté&amp;nbsp;colonne&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;du sharding avec &lt;a href="https://github.com/citusdata/citus"&gt;Citus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;un &lt;a href="https://docs.citusdata.com/en/v6.0/performance/query_processing.html"&gt;moteur de requêtes&amp;nbsp;distribuées&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;des approximations statistiques pour des calculs&amp;nbsp;massifs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les conteneurs adorent les applications sans états, indifférenciées. Ce qui est l&amp;#8217;opposé des bases de données&amp;nbsp;relationnelles.&lt;/p&gt;
&lt;h4 id="multi-maitre"&gt;Multi-maître&lt;/h4&gt;
&lt;p&gt;Pour ne pas avoir à gérer la différence maitre/esclave, et la complexité de la promotion de maître à esclave, puis de créer un nouvel esclave, il est possible d&amp;#8217;utiliser de la réplication multi&amp;nbsp;maître.&lt;/p&gt;
&lt;p&gt;Postgres sait maintenant faire ça avec &lt;a href="https://2ndquadrant.com/fr/resources/bdr/"&gt;&lt;span class="caps"&gt;BDR&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mysql propose des outils similaires, comme &lt;a href="http://galeracluster.com/"&gt;Galera&lt;/a&gt; et &lt;a href="http://www.proxysql.com/"&gt;proxysql&lt;/a&gt;, ou des trucs plus emballé comme &lt;a href="https://www.percona.com/software/mysql-database/percona-xtradb-cluster"&gt;XtraDB Cluster&lt;/a&gt; de&amp;nbsp;Percona.&lt;/p&gt;
&lt;p&gt;Ces outils sont sympathiques, mais rien de prévu pour pour pouvoir redimensionner&amp;nbsp;simplement.&lt;/p&gt;
&lt;p&gt;Pourtant, des solutions propriétaires existent, comme &lt;a href="https://en.wikipedia.org/wiki/Aurora_database_analytics_engine"&gt;Aurora&lt;/a&gt; d&amp;#8217;Amazon, basé sur Mysql, avec de gros efforts pour conserver la compatibilité et disponible dans leur offre &lt;a href="https://aws.amazon.com/fr/rds/aurora/details/"&gt;&lt;span class="caps"&gt;RDS&lt;/span&gt;&lt;/a&gt;, ou le mystérieux &lt;a href="http://research.google.com/pubs/pub41344.html"&gt;F1&lt;/a&gt; de Google, reservé à&amp;nbsp;AdWords.&lt;/p&gt;
&lt;p&gt;Le modèle relationnel n&amp;#8217;est juste pas prévu pour fonctionner de manière distribuée, mais de gros efforts sont faits pour, par contre, les applications vont devoir faire des compromis pour s&amp;#8217;adapter. En posant un Wordpress sur un Cloud, on ne pourra pas s&amp;#8217;attendre à de&amp;nbsp;miracle.&lt;/p&gt;
&lt;p&gt;Je fait bien la différence entre le modèle relationnel et le &lt;span class="caps"&gt;SQL&lt;/span&gt;, le langage, qui lui peut parfaitement être distribué.
Des outils comme &lt;a href="https://drill.apache.org/"&gt;Drill&lt;/a&gt; de la fondation Apache, &lt;a href="https://prestodb.io/"&gt;Presto&lt;/a&gt; de Facebook ou même &lt;a href="http://spark.apache.org/sql/"&gt;Spark &lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="base-de-donnees-nosql"&gt;Base de données&amp;nbsp;NoSQL&lt;/h3&gt;
&lt;p&gt;Les bases NoSQL ont été inventées pour remettre en cause le dogme&amp;nbsp;relationnel.&lt;/p&gt;
&lt;p&gt;Bon, elles ont ensuite fournis baucoup d&amp;#8217;efforts pour s&amp;#8217;en rapprocher,
le &lt;span class="caps"&gt;SQL&lt;/span&gt; restant une &lt;em&gt;lingua franca&lt;/em&gt; pour explorer des données, et pouvoir se brancher dessus en &lt;span class="caps"&gt;JDBC&lt;/span&gt;/&lt;span class="caps"&gt;ODBC&lt;/span&gt; est toujours&amp;nbsp;appréciable.&lt;/p&gt;
&lt;p&gt;Le NoSQL a aussi été conçu pour pouvoir profiter d&amp;#8217;un hébergement&amp;nbsp;distribué.&lt;/p&gt;
&lt;p&gt;Le monde du NoSQL est tout récent, Darwin n&amp;#8217;a pas eu le temps de faire le tri pour trouver ses champions. Voici une sélection arbitraire de bases de données, qui fonctionnent à des tailles raisonnables. La partie élection, pour avoir une archi sans maître, peut être plus ou moins&amp;nbsp;laborieuse.&lt;/p&gt;
&lt;p&gt;La première chose que va sacrifier une base NoSQL est la notion de&amp;nbsp;transaction.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.mongodb.com/community"&gt;Mongodb&lt;/a&gt; est la plus visible des bases NoSQL, et avant la version 3 et le stockage Tiger, est plutôt pénible à administrer. Le pattern de distribution est classique, un système d&amp;#8217;élection pour avoir un master, et un proxy qui va assurer le routage vers le bon serveur. La distribution du travail se fait avec du classique&amp;nbsp;map-reduce.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://cassandra.apache.org/"&gt;Cassandra&lt;/a&gt; est une base de données orientée colonnes, avec un presque &lt;span class="caps"&gt;SQL&lt;/span&gt;, le &lt;a href="http://cassandra.apache.org/doc/latest/cql/index.html"&gt;&lt;span class="caps"&gt;CQL&lt;/span&gt;&lt;/a&gt;, sans jointures ni sous-requêtes, mais avec du map-reduce. Créé par &lt;a href="https://code.facebook.com/posts/160967930761450/cassandra-a-structured-storage-system-on-a-p2p-network/"&gt;Facebook pour manger du log&lt;/a&gt;, il fut ensuite confié à la fondation Apache, pour passer à autre chose : Presto et Hbase, et &lt;a href="https://www.quora.com/Why-did-Facebook-develop-Puma-pTail-instead-of-using-existing-ones-like-Flume"&gt;une autre stack pour avaler leurs logs&lt;/a&gt;. Cassandra, même s’il est civilisé, reste une base conçue et dimensionnée pour du big&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.scylladb.com/"&gt;Scylladb&lt;/a&gt; est un clone C++,  agressif et ambitieux de Cassandra, mettant un point d&amp;#8217;honneur à saturer des réseaux 10Go, le kernel, les CPUs et des disques &lt;a href="https://en.wikipedia.org/wiki/NVM_Express"&gt;NVMe&lt;/a&gt; tout en conservant la compatibilité avec&amp;nbsp;Cassandra.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.elastic.co/fr/products/elasticsearch"&gt;Elasticsearch&lt;/a&gt; est une base orientée documents, distribués, avec des beaux index et une interface &lt;span class="caps"&gt;REST&lt;/span&gt;.
Initialement conçu pour la recherche full text, il a évolué vers les calculs d&amp;#8217;agrégations, et poursuit vers le machage de logs. Elasticsearch est parfaitement à l&amp;#8217;aise avec les principes de distribution et de réplication, tout en restant crédible à partir de 2&amp;nbsp;noeuds.&lt;/p&gt;
&lt;h3 id="le-stockage-se-distribue-bien-se-replique-bien-mais-se-bouge-mal"&gt;Le stockage se distribue bien, se réplique bien, mais se bouge&amp;nbsp;mal&lt;/h3&gt;
&lt;p&gt;Les containers sont tout à fait capables d&amp;#8217;assurer la persistance, locale ou sur du partage en mode blocs.
Par contre, les orchestrateurs ont bien envie de rééquilibrer le cluster en déplaçant les containers dans tous les sens. C&amp;#8217;est tout à fait légitime pour des containers sans états, un contre sens pour du&amp;nbsp;stockage.&lt;/p&gt;
&lt;p&gt;Il faut alors revenir aux vieux principes décrits par Borg et Hadoop : les données sont fixes, les process peuvent&amp;nbsp;bouger.&lt;/p&gt;
&lt;p&gt;Les orchestrateurs sont maintenant capables de faire attention à ne pas déplacer les données, pour pouvoir profiter pleinement des performances&amp;nbsp;locales.&lt;/p&gt;
&lt;p&gt;Le stockage local est le plus performant, le mode bloc offre une simplification et un confort appréciable, le mode objet permet une pérennité incomparable et un scaling sans trop de limites, le mode fichier n&amp;#8217;a plus sa place dans un environnement&amp;nbsp;web.&lt;/p&gt;</content><category term="Ops"></category><category term="docker"></category><category term="conteneur"></category></entry><entry><title>Le phénomène Docker expliqué aux adorateurs de la Terre plate</title><link href="http://blog.garambrogne.net/le-phenomene-docker-explique-aux-adorateurs-de-la-terre-plate.html" rel="alternate"></link><published>2016-10-17T09:20:00+02:00</published><updated>2016-10-17T09:20:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2016-10-17:/le-phenomene-docker-explique-aux-adorateurs-de-la-terre-plate.html</id><summary type="html">&lt;p&gt;Si vous aimez jouer au buzzword bingo, vous avez bien remarqué que Docker est un mot compte triple. Vous allez l&amp;#8217;adorer, le détester, ou adorer le détester. Comment un machin si récent peu déclencher autant d&amp;#8217;engouement ? Pourquoi les gros du cloud (abrégé en &lt;span class="caps"&gt;GDC&lt;/span&gt;) se prosternent devant lui, ou monte une équipe pour lui casser les genoux et lui rappeler sa&amp;nbsp;place?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Si vous aimez jouer au buzzword bingo, vous avez bien remarqué que Docker est un mot compte triple. Vous allez l&amp;#8217;adorer, le détester, ou adorer le détester. Comment un machin si récent peu déclencher autant d&amp;#8217;engouement ? Pourquoi les gros du cloud (abrégé en &lt;span class="caps"&gt;GDC&lt;/span&gt;) se prosternent devant lui, ou monte une équipe pour lui casser les genoux et lui rappeler sa&amp;nbsp;place?&lt;/p&gt;
&lt;h1 id="tldr"&gt;&lt;span class="caps"&gt;TL&lt;/span&gt;;&lt;span class="caps"&gt;DR&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;Une architecture web est composée d&amp;#8217;un ensemble de services, ensemble que l&amp;#8217;on doit pouvoir utiliser à différents endroits, du poste de dev, à l&amp;#8217;intégration continue, et finalement les serveurs de prod, en un ou plusieurs&amp;nbsp;exemplaires.&lt;/p&gt;
&lt;h1 id="sabstraire-du-materiel"&gt;S&amp;#8217;abstraire du&amp;nbsp;matériel&lt;/h1&gt;
&lt;p&gt;Avant de râler que s&amp;#8217;était mieux avant, mieux vaut le définir, ce avant, histoire de parler de la même&amp;nbsp;chose.&lt;/p&gt;
&lt;h2 id="les-machines-virtuelles"&gt;Les machines&amp;nbsp;virtuelles&lt;/h2&gt;
&lt;p&gt;Les machines virtuelles n&amp;#8217;ont jamais été qu&amp;#8217;un mensonge. Qemu propose vraiment de la virtualisation, lui. Il isole complètement l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; invité de la machine, et ce, à la vitesse d&amp;#8217;une tortue qui a trop mangé de&amp;nbsp;poutine.&lt;/p&gt;
&lt;p&gt;Tous les outils utilisables de virtualisation (&lt;a href="http://www.linux-kvm.org"&gt;&lt;span class="caps"&gt;KVM&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://www.xenproject.org"&gt;Xen&lt;/a&gt;, &lt;a href="https://www.virtualbox.org"&gt;Virtualbox&lt;/a&gt;, &lt;a href="https://developer.apple.com/reference/hypervisor"&gt;hypervisor&lt;/a&gt;… ) sont en fait de la paravirtualisation, le kernel invité est adapté à ce que fournit son hôte, et s&amp;#8217;appuie sur des fonctions spécifiques du processeur. Il est d&amp;#8217;ailleurs impossible de virtualiser de la virtualisation en mélangeant des&amp;nbsp;technos.&lt;/p&gt;
&lt;p&gt;Le vrai mensonge n&amp;#8217;est pas là, mais dans le &amp;#8220;Rien n&amp;#8217;a changé, tout est comme avant, un serveur virtuel s&amp;#8217;utilise comme un serveur physique, c&amp;#8217;est&amp;nbsp;kifkif&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Même si l&amp;#8217;accès à la &lt;span class="caps"&gt;RAM&lt;/span&gt; sera dédié, le processeur sera partagé ou alors gâché avec de l&amp;#8217;épinglage (du pinning, quoi). Dans tous les cas, les cartes réseau et disques durs seront partagées. La tragique conséquence est le classique phénomène du voisin&amp;nbsp;bruyant.&lt;/p&gt;
&lt;p&gt;De faux drivers permettent d&amp;#8217;accéder de manière isolée à des fonctions du matériel, comme l&amp;#8217;horloge ou le générateur d&amp;#8217;aléatoire. Mais les vrais paranoïaques n&amp;#8217;ont pas de voisins (allez lire la doc de &lt;a href="https://securedrop.org/"&gt;SecureDrop&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Autre partage pénible, les IPs, que l&amp;#8217;on doit soit router (niveau 4 ou 7) caché derrière un &lt;span class="caps"&gt;LAN&lt;/span&gt;, soit gaspiller de l&amp;#8217;IPv4, bientôt plus rare qu&amp;#8217;un panda en&amp;nbsp;liberté.&lt;/p&gt;
&lt;p&gt;Mais au fait, pourquoi virtualiser&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;La réponse est simple, on virtualise pour&amp;nbsp;isoler.&lt;/p&gt;
&lt;p&gt;Isolation qui permet&amp;nbsp;de&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;limiter la portée des bêtises (erreurs ou&amp;nbsp;agressions)&lt;/li&gt;
&lt;li&gt;mélanger des versions d&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; et d&amp;#8217;avoir des cycles de mises à jour indépendants, voir même utiliser différents &lt;span class="caps"&gt;OS&lt;/span&gt;, pour les plus&amp;nbsp;créatifs&lt;/li&gt;
&lt;li&gt;installer des logiciels ou des bibliothèques dans des versions non&amp;nbsp;compatibles&lt;/li&gt;
&lt;li&gt;ne pas mélanger les pommes et les oranges, aka&amp;nbsp;co-tenant&lt;/li&gt;
&lt;li&gt;garantir une répartition plutôt équitable des&amp;nbsp;ressources&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tout ça permet donc d&amp;#8217;entasser plein de choses sur un même serveur, on parle alors de&amp;nbsp;densification.&lt;/p&gt;
&lt;p&gt;Ces entités isolées, abstraites de la couche matérielle peuvent être manipulées&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;redimensionnement (&lt;span class="caps"&gt;RAM&lt;/span&gt;, &lt;span class="caps"&gt;CPU&lt;/span&gt;,&amp;nbsp;Disques)&lt;/li&gt;
&lt;li&gt;déplacement d&amp;#8217;une machine à l&amp;#8217;autre, pour rééquilibrer, ou mettre à jour le&amp;nbsp;matériel&lt;/li&gt;
&lt;li&gt;snapshot et&amp;nbsp;restauration&lt;/li&gt;
&lt;li&gt;Jouer à jour/nuit avec des cycles de vie courts, pour faire des tests, des benchmarks, des&amp;nbsp;essais&lt;/li&gt;
&lt;li&gt;Ajouter de la puissance pour tenir la charge d&amp;#8217;un évènement, comme un passage à la&amp;nbsp;télévision&lt;/li&gt;
&lt;li&gt;Lancer des lots violents de calculs, de manière&amp;nbsp;ponctuelle&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="les-limites-de-la-virtualisation-des-machines"&gt;Les limites de la virtualisation des&amp;nbsp;machines&lt;/h2&gt;
&lt;h3 id="les-machines-des-developpeurs"&gt;Les machines des&amp;nbsp;développeurs&lt;/h3&gt;
&lt;p&gt;Il est complexe d&amp;#8217;amener les machines virtuelles sur les postes des développeurs, et super pénible de gérer une grappe de serveur. Ça reste lent, ça bouffe des tonnes de &lt;span class="caps"&gt;RAM&lt;/span&gt;, le partage de fichier pour une édition local est mou des genoux, le watch de fichier ne marchera pas forcément.
C&amp;#8217;est ballot, tous les &lt;span class="caps"&gt;OS&lt;/span&gt; proposent maintenant de la virtualisation de série, bhyve dépote sur Mac, mais, étonnement je n&amp;#8217;ai jamais vu de dév Linux utiliser &lt;span class="caps"&gt;KVM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Rebelote pour faire des tests fonctionnels ou même unitaires utilisant un service (comme une base de données), avec en prime des surprises comme
ah tiens, Xen ne veut pas de&amp;nbsp;Virtualbox.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.vagrantup.com"&gt;Vagrant&lt;/a&gt; est un excellent outil pour systématiser la mise en place de machines virtuelles. De toute façon, il est ridicule de travailler directement avec Virtualbox (ou l&amp;#8217;un de ses concurrents), et même criminel si l&amp;#8217;on travaille à plusieurs sur un même projet. Entendons-nous bien, je suis un grand fan de Vagrant, mais il est au top, il n&amp;#8217;y aura rien après, Otto, son successeur a été&amp;nbsp;sabordé.&lt;/p&gt;
&lt;p&gt;Vouloir simplifier la stack du développeur en ne travaillant qu&amp;#8217;avec des services directement installés en local a rapidement montré ses limites. Le temps de setup est énorme, la bascule d&amp;#8217;un projet à l&amp;#8217;autre est souvent la garantie d&amp;#8217;écraser des données, et la flemme du commun va empêcher d&amp;#8217;utiliser des outils qui sauvent le monde comme Redis ou Elasticsearch. De toute façon, qui utilise le même &lt;span class="caps"&gt;OS&lt;/span&gt; que le serveur cible sur sa machine locale? Une Debian stable en poste&amp;nbsp;client?&lt;/p&gt;
&lt;h3 id="la-taille-des-tranches-de-virtualisation"&gt;La taille des tranches de&amp;nbsp;virtualisation&lt;/h3&gt;
&lt;p&gt;La virtualisation fonctionne bien, et est très largement utilisée, mais elle a un ticket d&amp;#8217;entrée, avec moins de 2 coeurs et 2 Go de &lt;span class="caps"&gt;RAM&lt;/span&gt;, ce n&amp;#8217;est plus une &lt;span class="caps"&gt;VM&lt;/span&gt;, mais un jouet (&lt;a href="https://aws.amazon.com/fr/ec2/instance-types/"&gt;&lt;span class="caps"&gt;AWS&lt;/span&gt; appelle ça T2, pour flatter&lt;/a&gt;). Dédier une machine pour des services peu gourmands, reviens vite hors de prix, sans compter la place disque occupé par l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; (et l&amp;#8217;application) sur la machine physique si l&amp;#8217;on n&amp;#8217;a pas de système de &lt;span class="caps"&gt;COW&lt;/span&gt;, qui reste complexe à mettre en&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;Du coup, on se rabat sur le classique entassage sur une même machine, mais avec quelle garantie d&amp;#8217;isolation (sécurité et&amp;nbsp;ressource)?&lt;/p&gt;
&lt;p&gt;Même si la haute dispo est heureusement réservée à des applications très spécifiques (brrrr, des gros mots comme &lt;a href="http://linux-ha.org/"&gt;HAlinux&lt;/a&gt;, drdb, heartbeat, pacemaker…), il est maintenant classique d&amp;#8217;avoir des applications distribuées. Une base de données répliquée en master/slave est courante, un Elasticsearch est malheureux tout seul. Pour pouvoir espérer tenir un pic de charge, il faut tout simplement que l&amp;#8217;application métier puisse être déployée sur plusieurs&amp;nbsp;serveurs.&lt;/p&gt;
&lt;h1 id="la-notion-de-services"&gt;La notion de&amp;nbsp;services&lt;/h1&gt;
&lt;p&gt;Les architectures actuelles sont de plus en plus riches. L&amp;#8217;indétrônable &lt;span class="caps"&gt;LAMP&lt;/span&gt;, qui a permis à Wordpress de conquérir le monde est maintenant&amp;nbsp;détrôné.&lt;/p&gt;
&lt;p&gt;On garde le classique service de persistance (la base de données, quoi), le service applicatif, puis un service pour stocker des fichiers (&lt;span class="caps"&gt;FS&lt;/span&gt; local, ~~GlusterFS~~, un S3 like), mais aussi un service de mémoire partagé (Memcache, Redis…) pour gérer le cache, les sessions, les évènements. Pour travailler avec des timeout décents, on va utiliser un service de taches asynchrones (~~cron avec wget~~, Sidekiq/Celery/Beanstalk…). De la recherche full text avec ~~Solr~~ Elasticsearch. Un machin pour gérer les websockets par ce que son langage chéri est bien incapable de gérer de l&amp;#8217;asynchrone, Un petit Logstash pour manger les logs, un Sentry pour les erreurs, un Statsd pour les compteurs de performances, un Cuttlefish pour les mails transactionnels, un Pootle pour les traductions… Et là, je ne parle que de services Open Source que l&amp;#8217;on peut déployer, et pas des services en ligne, les fameux &lt;span class="caps"&gt;SAAS&lt;/span&gt;.&lt;/p&gt;
&lt;h1 id="la-decrepitude-du-systeme"&gt;La décrépitude du&amp;nbsp;système&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Server&amp;nbsp;rots&lt;/p&gt;
&lt;p&gt;—&amp;nbsp;Ori&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;L&amp;#8217;entropie mange le monde, c&amp;#8217;est comme ça, et c&amp;#8217;est prouvé par &lt;a href="https://fr.wikipedia.org/wiki/Ian_Malcolm_(Jurassic_Park)"&gt;Pr Ian Malcom&lt;/a&gt; dans Jurassic Park : ce n&amp;#8217;est qu&amp;#8217;une question de temps, mais ça va partir en&amp;nbsp;sucette.&lt;/p&gt;
&lt;p&gt;Au fil du temps, son petit serveur d&amp;#8217;amour, nommé en suivant un champ lexical spécifique, installé avec amour et inspiration, va commencer à sortir du champ de rationalité, il va continuer de fonctionner, bien sûr, mais il y aura des changements subtils, des innovations, des corrections sauvages, mais, promis, temporaires. Et au bout de suffisamment de temps, il va devenir un &lt;em&gt;snow flake&lt;/em&gt;, unique et inreproductible comme un flocon de neige. Plus on entasse de choses, plus on bataille pour prolonger une version d&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; dépassé, plus le décollage sera&amp;nbsp;rapide.&lt;/p&gt;
&lt;p&gt;La seule solution est d&amp;#8217;avoir un système immuable, construit de manière reproductible avec une recette, en précisant les quelques dossiers capables d&amp;#8217;écrire, pour assurer la&amp;nbsp;persistance.&lt;/p&gt;
&lt;h1 id="et-docker-dans-tout-ca"&gt;Et Docker dans tout ça&amp;nbsp;?&lt;/h1&gt;
&lt;p&gt;L&amp;#8217;isolation par la virtualisation est couteuse, et il n&amp;#8217;y a aucun intérêt à avoir un système complet pour accueillir un&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Il faut revenir à quelque chose de simple, de très &lt;span class="caps"&gt;UNIX&lt;/span&gt; en fait : demander au kernel d&amp;#8217;isoler des&amp;nbsp;process.&lt;/p&gt;
&lt;p&gt;Chroot existe depuis toujours, et cgroups a été initialement conçu par Google il y a maintenant 10&amp;nbsp;ans.&lt;/p&gt;
&lt;p&gt;Rajoutez à ça un système de fichiers en oignon, comme &lt;span class="caps"&gt;AUFS&lt;/span&gt;, pour mutualiser la place occupé sur le disque dur et avoir des mises à jour tranche par&amp;nbsp;tranche.&lt;/p&gt;
&lt;p&gt;Pour répondre à la ribambelle de questions que je viens d&amp;#8217;énumérer, la conteneurisation apporte une réponse différente (et potentiellement complémentaire) à la&amp;nbsp;virtualisation.&lt;/p&gt;
&lt;h2 id="lxc"&gt;&lt;span class="caps"&gt;LXC&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Petit incident de parcours, ces éléments ont été utilisés pour construire &lt;a href="https://linuxcontainers.org"&gt;&lt;span class="caps"&gt;LXC&lt;/span&gt;&lt;/a&gt; (wouaaais) le chantre du &lt;em&gt;Fat Container&lt;/em&gt; (Oooooohhhh). Plutôt que de travailler avec des process, comme tout le monde, &lt;span class="caps"&gt;LXC&lt;/span&gt; a trouvé très malin de singer un système complet, avec un init et une grappe de process, vous savez, &amp;#8220;comme avant&amp;#8221;. Pour parfaire le tout, &lt;span class="caps"&gt;LXC&lt;/span&gt; est livré avec une stack réseau en &lt;span class="caps"&gt;DIY&lt;/span&gt; (démerde-toi, en &lt;span class="caps"&gt;VF&lt;/span&gt;). Ubuntu a bien essayé de sauver les meubles avec son &lt;a href="https://www.ubuntu.com/cloud/lxd"&gt;&lt;span class="caps"&gt;LXD&lt;/span&gt;&lt;/a&gt;, mais non. La simulation d&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; que propose &lt;span class="caps"&gt;LXC&lt;/span&gt; est très différente d&amp;#8217;un &lt;span class="caps"&gt;OS&lt;/span&gt; même virtualisé. Faire des tests fonctionnels dans &lt;span class="caps"&gt;LXC&lt;/span&gt; en espérant que ça ressemblera à la cible, non &lt;span class="caps"&gt;LXC&lt;/span&gt;, est la garantie d&amp;#8217;un drame. De toute façon, &lt;span class="caps"&gt;LXC&lt;/span&gt;, avant &lt;span class="caps"&gt;LXD&lt;/span&gt; est trop pénible à mettre en place pour du dev/test. Et &lt;span class="caps"&gt;LXD&lt;/span&gt;, ça veut dire Ubuntu, et du coup, grosse flemme de lui laisser une seconde chance. Surtout que &lt;span class="caps"&gt;LXD&lt;/span&gt; ne remet pas en cause le fat container, et la concurrence, elle,&amp;nbsp;fonctionne.&lt;/p&gt;
&lt;p&gt;Docker, qui avant d&amp;#8217;être un projet libre, a utilisé &lt;span class="caps"&gt;TOUTES&lt;/span&gt; les technos de contenurisaton disponible sous Linux, une à une. Au moment de sa libération, Docker était une surcouche à &lt;span class="caps"&gt;LXC&lt;/span&gt;. En utilisant dans un vrai cadre ces outils (avec des tonnes de clients et des brouettes de devs dédiés), Docker a vraiment utilisé &lt;span class="caps"&gt;LXC&lt;/span&gt;, qui n&amp;#8217;était qu&amp;#8217;un gadget un peu reloud. Ca a permis de trouver plein d&amp;#8217;horreurs dans le code du kernel, et les corrections qui ont suivis ont abouti au mythique 3.11, la première version offrant de la conteneurisation décente. &lt;span class="caps"&gt;LXC&lt;/span&gt; a été jeté au profit de bibliothèques maisons, en Go, qui utilise des fonctions avancées du&amp;nbsp;kernel.&lt;/p&gt;
&lt;h2 id="docker"&gt;Docker&lt;/h2&gt;
&lt;p&gt;Donc, première révélation aux haters : Docker n&amp;#8217;existe pas. Docker se contente d&amp;#8217;utiliser des fonctions du kernel, sans le patcher, et d&amp;#8217;autres outils bas niveau pour proposer une solution cohérente de conteneur.
D&amp;#8217;autres outils proposent leurs propres solutions de conteneurisation en utilisant les mêmes briques de&amp;nbsp;bases.&lt;/p&gt;
&lt;p&gt;De toute façon, Docker n&amp;#8217;existe pas, ce n&amp;#8217;est qu&amp;#8217;une &lt;span class="caps"&gt;API&lt;/span&gt; basée sur &lt;em&gt;runc&lt;/em&gt;, l&amp;#8217;implémentation officielle de l&amp;#8217;Open Container Initiative (crée grâce à la saine polémique lancée par &lt;a href="https://coreos.com/rkt/"&gt;Rkt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Donc, pour ceux qui ne suivent pas, Docker, c&amp;#8217;est un process lancé dans un &lt;em&gt;chroot&lt;/em&gt; avec le kernel isole ou partage un maximum de&amp;nbsp;choses.&lt;/p&gt;
&lt;h3 id="la-conquete"&gt;La&amp;nbsp;conquête&lt;/h3&gt;
&lt;p&gt;Docker a choisi une stratégie bottom top. Sa première cible a été les développeurs, les postes clients. Je ne sais pas si c&amp;#8217;était leur stratégie, mais clairement, leurs premières victimes ont été des&amp;nbsp;développeurs.&lt;/p&gt;
&lt;p&gt;Docker propose une api &lt;span class="caps"&gt;CLI&lt;/span&gt; élégante, une &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt; logique, et surtout une très bonne documentation, des évangélistes de prestiges, comme &lt;a href="https://jpetazzo.github.io"&gt;Petazzoni&lt;/a&gt; ou &lt;a href="https://blog.jessfraz.com"&gt;Jess Frazelle&lt;/a&gt; (qui bosse maintenant chez&amp;nbsp;Mesos).&lt;/p&gt;
&lt;h3 id="immuable"&gt;Immuable&lt;/h3&gt;
&lt;p&gt;Une image n&amp;#8217;a pas vocation à conserver ses données modifiées. Pour ça, il y a les &lt;em&gt;volumes&lt;/em&gt;, des dossiers en &lt;span class="caps"&gt;RW&lt;/span&gt; qui existe hors du&amp;nbsp;container.&lt;/p&gt;
&lt;p&gt;Les images sont définies par couches, et si l&amp;#8217;on suit la tendance actuelle, la première couche sera une debian stable, commune à toutes les&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Il est possible d&amp;#8217;avoir plusieurs instances d&amp;#8217;une même image dans sa grappe de&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;Les réglages spécifiques à une instance doivent passer par des variables d&amp;#8217;environnements, et non les classiques fichiers de&amp;nbsp;conf.&lt;/p&gt;
&lt;p&gt;Tous ces points (une image immuable, configurée depuis l&amp;#8217;extérieur, avec quelques dossiers explicites qui assureront un stockage non volatile), permettent de conserver une même image pour les tests d&amp;#8217;intégration, préprod, et enfin la production, limitant au maximum le drame des tests qui passent dans un environnement, mais pas dans un&amp;nbsp;autre.&lt;/p&gt;
&lt;h3 id="la-recette-dockerfile"&gt;La recette&amp;nbsp;Dockerfile&lt;/h3&gt;
&lt;p&gt;Docker permet un système simple de recette, Dockerfile, pour construire ses propres images, et le Hub pour aller piocher dans un catalogue. Bon, on y trouve tout et n&amp;#8217;importe quoi dans ce hub et il est sage de se contenter des repos officiels. Ces images publiques sont fort pratiques pour du dev, plus polémique pour de la&amp;nbsp;prod.&lt;/p&gt;
&lt;p&gt;On ne met pas à jour une image, on la reconstruit. Voilà, contre l&amp;#8217;entropie, la&amp;nbsp;reconstruction.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;approche en pelures d&amp;#8217;oignons du filesystem permet de profiter d&amp;#8217;un cache tacite. Pratique, mais pas suffisant, il y a actuellement du travail qui est fait pour améliorer le cache, sans affaiblir la&amp;nbsp;sécurité.&lt;/p&gt;
&lt;p&gt;Dockerfile est la solution par défaut pour construire une image, mais il est possible de se débrouiller&amp;nbsp;autrement.&lt;/p&gt;
&lt;h3 id="le-multi-os"&gt;Le multi &lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Pour achever les dernières poches de résistance, Docker, qui fonctionnait déjà très bien sur Mac dans un Virtualbox (merci l&amp;#8217;archi client/serveur) a développé la première appli connue utilisant la toute fraiche &lt;span class="caps"&gt;API&lt;/span&gt; de virtualisation de MacOS (basé sur un produit FreeBSD). Même effort coté Windows, mais c&amp;#8217;est un monde que je ne fréquente&amp;nbsp;pas.&lt;/p&gt;
&lt;h3 id="la-composition-de-docker-compose"&gt;La composition de&amp;nbsp;Docker-Compose&lt;/h3&gt;
&lt;p&gt;Docker a clairement dit que le &lt;em&gt;fat container&lt;/em&gt; était une mauvaise idée (voir le troll de &lt;a href="http://phusion.github.io/baseimage-docker/"&gt;Phusion&lt;/a&gt;), et vante l&amp;#8217;approche &amp;#8220;service par conteneur&amp;#8221;. Pour une application complète, il faut donc plusieurs conteneurs, configurés, décrits simplement dans une recette. C&amp;#8217;est ce que propose&amp;nbsp;Docker-Compose.&lt;/p&gt;
&lt;h3 id="docker-a-distance"&gt;Docker à&amp;nbsp;distance&lt;/h3&gt;
&lt;p&gt;Docker est basé sur une architecture client/serveur, qui utilise une socket &lt;span class="caps"&gt;UNIX&lt;/span&gt;, ou une authentification par certificats. Il est donc possible de laisser son docker client causait avec un docker daemon,&amp;nbsp;distant.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est ce que propose &lt;a href="https://www.docker.com/products/docker-machine"&gt;Docker-machine&lt;/a&gt;, qui permet d&amp;#8217;utiliser de la virtualisation ou du&amp;nbsp;Cloud.&lt;/p&gt;
&lt;p&gt;Tous les gros du Cloud proposent une offre spécifique à Docker, qui, en proposant une abstraction pour distribuer des services, met à mal le côté captif des différents&amp;nbsp;Cloud.&lt;/p&gt;
&lt;h3 id="docker-en-cluster"&gt;Docker en&amp;nbsp;cluster&lt;/h3&gt;
&lt;p&gt;Une fois son application bien rangée en petit conteneur, il est tentant de la distribué sur plusieurs serveurs, voir même d&amp;#8217;avoir quelque chose de mouvant, que ce soit pour accompagner une montée en charge, ou pour survivre à une panne d&amp;#8217;un des&amp;nbsp;serveurs.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est ici que ce positionne &lt;a href="https://www.docker.com/products/docker-swarm"&gt;Docker-Swarm&lt;/a&gt;, mais aussi &lt;a href="http://kubernetes.io"&gt;Kubernetes&lt;/a&gt;, &lt;a href="https://www.nomadproject.io"&gt;Nomad&lt;/a&gt;, &lt;a href="http://mesos.apache.org"&gt;Mesos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker-swarm propose d&amp;#8217;utiliser la même &lt;span class="caps"&gt;API&lt;/span&gt; pour gérer un ou plusieurs serveurs, et de laisser un algo se débrouiller pour répartir les services et élire des &lt;em&gt;masters&lt;/em&gt; des services, si&amp;nbsp;besoin.&lt;/p&gt;
&lt;p&gt;Avec sa dernière release, Docker a mis le bazar en allant s&amp;#8217;attaquer aux offres d&amp;#8217;hébergements distribués. Par ce que bon, humilier &lt;span class="caps"&gt;LXC&lt;/span&gt; ou Vagrant, tout le monde s&amp;#8217;en fout un peu, par contre, s&amp;#8217;attaquer à de vrais produits qui génèrent de vrais sous, comme Kubernetes, c&amp;#8217;est une autre histoire. Surtout que cette version 1.11 semble avoir été sorti un peu vite, sans trop se soucier de réutiliser des choses&amp;nbsp;existantes.&lt;/p&gt;
&lt;h1 id="ce-qui-marche-avec-docker"&gt;Ce qui marche avec&amp;nbsp;Docker&lt;/h1&gt;
&lt;p&gt;Docker, sur un poste de dev, c&amp;#8217;est un nouveau dans une équipe qui peut commencer à bosser dans 1h, avec Vagrant, il fallait compter 1&amp;nbsp;jour.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;intégration continue, Docker est pour l&amp;#8217;instant sans concurrence, surtout que l&amp;#8217;on n&amp;#8217;a pas à simuler ce que va utiliser le dev, mais on va prendre la même&amp;nbsp;configuration.&lt;/p&gt;
&lt;p&gt;Docker est un fan de &lt;a href="https://12factor.net/"&gt;12 Factor&lt;/a&gt;, il permet de déclarer simplement comment lancer un service, et se chargera des logs et de son cycle de&amp;nbsp;vie.&lt;/p&gt;
&lt;p&gt;Pour compiler, et encore plus avec des outils trop récents (Golang, je pense à toi), ou des trucs qui veulent installer des tonnes de  paquets en *-dev, Docker est magique, on monte le dossier courant comme volume de travail, et paf, ce qui se fait là bas, atterrit&amp;nbsp;ici.&lt;/p&gt;
&lt;p&gt;Pour les instances à la demande, comme ce que permettent les &lt;a href="http://jupyter.org/"&gt;notebook de Jupyter&lt;/a&gt;, il est tentant d&amp;#8217;utiliser des images contenant tout le bazar (les scientifiques n&amp;#8217;ont pas la même notion de packaging que les&amp;nbsp;développeurs).&lt;/p&gt;
&lt;p&gt;Pour installer une application composée de plusieurs services (tout ce qui est à peine plus complexe que phpmyadmin, quoi), la mettre à jour, puis la bazarder, Docker a peu de&amp;nbsp;concurrents.&lt;/p&gt;
&lt;h1 id="ce-qui-nest-pas-sec"&gt;Ce qui n&amp;#8217;est pas&amp;nbsp;sec&lt;/h1&gt;
&lt;p&gt;Docker a décidé de se friter avec Systemd, c&amp;#8217;est une querelle de personnes, mais aussi une concurrence. Mais bon, j&amp;#8217;en cause dans un autre billet: &lt;a href="https://blog.garambrogne.net/quis-custodiet-ipsos-custodes.html"&gt;Quis custodiet ipsos custodes?&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Par grosse flemme, Docker a décidé de ne pas gérer de compatibilité ascendante entre son client et son serveur. Quand on est sur la machine où se trouve le daemon, ce n&amp;#8217;est pas grave, c&amp;#8217;est le même paquet, à distance, ou pour les APIs tiers, c&amp;#8217;est une autre&amp;nbsp;histoire.&lt;/p&gt;
&lt;p&gt;Pour la gestion de plusieurs machines, c&amp;#8217;est actuellement la bataille, avec Google qui a envie de mettre en valeur son propre Cloud qui permet d&amp;#8217;avoir le Kubernetes le plus beau du monde. Nomad et Mesos visent les très gros trucs, et Docker ne veut rien lâcher de sa conquête de l&amp;#8217;Univers, et pour cela, il est prêt à aller trop&amp;nbsp;vite.&lt;/p&gt;
&lt;p&gt;Les APIs peuvent changer brutalement et potentiellement tout défoncer les produits&amp;nbsp;tiers.&lt;/p&gt;
&lt;h1 id="ce-qui-est-de-la-mauvaise-foi"&gt;Ce qui est de la mauvaise&amp;nbsp;foi&lt;/h1&gt;
&lt;h2 id="docker-cest-pourri-par-ce-quon-ne-peut-pas-mettre-a-jour-openssl"&gt;Docker c&amp;#8217;est pourri par ce qu&amp;#8217;on ne peut pas mettre à jour&amp;nbsp;openssl&lt;/h2&gt;
&lt;p&gt;Une image est immuable, et ne dois pas être mise à jour sur place. Il faut donc une nouvelle image de l&amp;#8217;application. Avec le système de couches, il suffit de repartir d&amp;#8217;une image système à jour, et de&amp;nbsp;redéployer.&lt;/p&gt;
&lt;p&gt;Le souci est le même pour tout ce qui est compilation (hors bibliothèque partagé), et donc Java, Scala, Golang… Même drame pour les&amp;nbsp;frameworks.&lt;/p&gt;
&lt;p&gt;La réponse à ça est le&amp;nbsp;baking.&lt;/p&gt;
&lt;p&gt;De manière générale, il est assez criminel de demander à un dev de pousser une image depuis son poste de travail. Il est nettement plus sage d&amp;#8217;utiliser de l&amp;#8217;intégration continue, et donc d&amp;#8217;avoir un combo build+test automatisé, pour redéclencher la création d&amp;#8217;une nouvelle&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;De toute façon, un apt-get upgrade ne marche qu&amp;#8217;un temps (sur deux versions d&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt;, grosso modo), et même les langages (&lt;span class="caps"&gt;PHP&lt;/span&gt;, je te vois!) ne sont pas maintenus jusqu&amp;#8217;à la fin des&amp;nbsp;temps.&lt;/p&gt;
&lt;p&gt;Avec les architectures distribuées, vous allez vous retrouver avec un service frontal qui va assurer le proxy/routage des services. Le classique Nginx/HAproxy/&lt;a href="https://traefik.io"&gt;Traefik&lt;/a&gt;/&lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt;/… qui va bien, et qui va affronter Internet, faisant rempart de son corps devant l&amp;#8217;application.
Ce proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; pourra être mis à jour, indépendamment du cycle de vie de&amp;nbsp;l&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;La mise à jour d&amp;#8217;openSSL sera nécessaire pour dans la grande majorité des cas à une utilisation client, pas serveur. Mais en parlant de ça, savez-vous si l&amp;#8217;application va accepter des réglages débiles (comme le fit Ruby), ne pas vérifier la concordance entre le nom du serveur, et le nom du certificat (comme le fit Python), si le pinning et la révocation sont&amp;nbsp;gérés?&lt;/p&gt;
&lt;h2 id="je-ne-veux-pas-sur-mon-infra-une-image-qui-sort-du-cul-dinternet"&gt;Je ne veux pas sur mon infra une image qui sort du cul&amp;nbsp;d&amp;#8217;Internet&lt;/h2&gt;
&lt;p&gt;Clairement, c&amp;#8217;est une bête idée de lancer un binaire sans un minimum de crédibilité. Coup de bol, une image polie doit être fournie avec son Dockerfile, et donc, de fait, ses sources permettant de la&amp;nbsp;construire.&lt;/p&gt;
&lt;p&gt;Ce problème est de plus en plus générique. Qui a envie de recompiler Elasticsearch après avoir relu le code, ou même&amp;nbsp;MongoDB?&lt;/p&gt;
&lt;p&gt;De toute façon, c&amp;#8217;est celui qui construit qui répare. Un dev peut très bien bosser avec une image &amp;#8220;postgres:9.4&amp;#8221; sur son poste de travail, mais rien ne vous oblige à la déployer. Son service a besoin d&amp;#8217;un service Postgres dans la version 9.4, bah, coïncidence, c&amp;#8217;est la version packagé Debian, et vous avez tuné avec amour sa conf et sa réplication. Si vous êtes de bonne humeur, vous pouvez même construire votre image Pg, basée sur &amp;#8220;debian:stable&amp;#8221;, avec les réglages que verront l&amp;#8217;utilisateur. Image qui servira en dev, et pour le &lt;span class="caps"&gt;CI&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="docker-ce-nest-pas-secure"&gt;Docker, ce n&amp;#8217;est pas&amp;nbsp;secure&lt;/h2&gt;
&lt;p&gt;Oui, clairement, la surface d&amp;#8217;attaque entre la virtualisation et la conteneurisation n&amp;#8217;est pas comparable, ni leur&amp;nbsp;maturité.&lt;/p&gt;
&lt;p&gt;Par contre, il y a un souci pour la virtualisation qui va protéger l&amp;#8217;hôte, et les autres &lt;span class="caps"&gt;VM&lt;/span&gt;, laissant libre ce qui se passe au sein de la &lt;span class="caps"&gt;VM&lt;/span&gt;. Visualisez un alien qui se faufile dans le véhicule blindé, ok, il aura du mal à sortir, mais pour les passagers, ce sera une autre&amp;nbsp;affaire.&lt;/p&gt;
&lt;p&gt;La taille minimale des VMs oblige à y déployer plusieurs services, la contamination de l&amp;#8217;un d&amp;#8217;entre eux pourra mettre en danger les&amp;nbsp;autres.&lt;/p&gt;
&lt;p&gt;Il y a deux réponses à&amp;nbsp;ça.&lt;/p&gt;
&lt;p&gt;En plus de l&amp;#8217;isolation permise par Cgroups et les Namespaces, Docker permet d&amp;#8217;utiliser simplement l&amp;#8217;arsenal classique de Linux : Apparmor/SELinux, les capabilities et &lt;a href="https://github.com/seccomp"&gt;seccomp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apparmor est déployé par défaut sur Ubuntu (et est plaisant à utiliser), mais Seccomp est peu utilisé en dehors de Chromium. Docker est un évangéliste de ces technos soit ignorés, soit&amp;nbsp;méprisés.&lt;/p&gt;
&lt;p&gt;Seconde approche, mise en évidence par &lt;a href="http://rancher.com/"&gt;Rancher&lt;/a&gt; et CoreOS, utiliser la tactique de ceinture et bretelles, en mettant des containers dans des machines virtuelles (grâce à la délégation de droit que permet &lt;span class="caps"&gt;KVM&lt;/span&gt;). L&amp;#8217;idée n&amp;#8217;est pas d&amp;#8217;avoir du un pour un, mais d&amp;#8217;isoler les services critiques ou au moins de les&amp;nbsp;séparer.&lt;/p&gt;
&lt;p&gt;De toute façon, vous n&amp;#8217;êtes pas tenu de faire du co-tenant sur vos&amp;nbsp;serveurs.&lt;/p&gt;
&lt;h2 id="docker-ca-tourne-en-root"&gt;Docker, ça tourne en&amp;nbsp;root&lt;/h2&gt;
&lt;p&gt;Oui, c&amp;#8217;est un poil reloud. Par contre, le root dans les images, c&amp;#8217;est juste une histoire de flemme, et d&amp;#8217;ailleurs Kubernetes refuse maintenant les images sans utilisateur&amp;nbsp;dédié.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;hôte, c&amp;#8217;est une limitation de Linux, qui bosse sur ce sujet, et une partie des outils sont déjà possibles sans privilège. Docker piaffe d&amp;#8217;impatience d&amp;#8217;avoir son outil qui puisse fonctionner sans&amp;nbsp;root.&lt;/p&gt;
&lt;p&gt;Juste pour le plaisir de dire du mal : ping a besoin du compte root, lui&amp;nbsp;aussi.&lt;/p&gt;
&lt;h2 id="docker-cest-pourri-je-ne-peux-pas-me-connecter-en-ssh-pour-deboguer-mon-service"&gt;Docker c&amp;#8217;est pourri, je ne peux pas me connecter en &lt;span class="caps"&gt;SSH&lt;/span&gt; pour déboguer mon&amp;nbsp;service&lt;/h2&gt;
&lt;p&gt;Ni pour y faire un apt-get upgrade, on a&amp;nbsp;compris.&lt;/p&gt;
&lt;p&gt;Un conteneur utilise les namespaces pour isoler son process hôte, mais il est tout à fait possible, lors de la création d&amp;#8217;un conteneur d&amp;#8217;utiliser les mêmes namespaces qu&amp;#8217;un conteneur existant. On parle de side kick container (Robin qui vient déboguer Batman, quoi). Un conteneur peut ainsi stracer un&amp;nbsp;autre.&lt;/p&gt;
&lt;p&gt;Je vous invite à &lt;span class="caps"&gt;RTFM&lt;/span&gt; &lt;a href="http://man7.org/linux/man-pages/man1/nsenter.1.html"&gt;nsenter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je parle bien de conteneur en général, rien de tout ça est spécifique à&amp;nbsp;Docker.&lt;/p&gt;
&lt;h2 id="docker-cest-pourri-ca-veut-scaler-mes-services-en-les-clonants-faisant-fi-des-threads"&gt;Docker, c&amp;#8217;est pourri, ça veut scaler mes services en les clonants, faisant fi des&amp;nbsp;threads&lt;/h2&gt;
&lt;p&gt;Non, ça, c&amp;#8217;est une connerie des 12 factor. Heroku a la flemme de gérer les possibilités de scaling des différents serveurs webs, du coup, hop, la vérité c&amp;#8217;est eux, ne vous occupez de rien, on n&amp;#8217;a qu&amp;#8217;à prendre le plus petit commun.
Donc, dans la vraie vie, Docker sait passer les messages &lt;span class="caps"&gt;UNIX&lt;/span&gt; au process, et il se fiche de savoir si votre application fait du fork en &lt;span class="caps"&gt;COW&lt;/span&gt; (comme peut le faire Gunicorn), ou des threads. Rien de vous empeche d&amp;#8217;avoir une grappe de process/thread web dans votre container, et quand vous aurez besoin de scaler, d&amp;#8217;instancier une (ou plusieurs) grappe, un peu plus&amp;nbsp;loin.&lt;/p&gt;
&lt;p&gt;12 factor, qui ont le bon gout d&amp;#8217;être pratique et lisible, reste quand même de la propagande post mortem&amp;nbsp;d&amp;#8217;Heroku.&lt;/p&gt;
&lt;h2 id="docker-cest-pourri-je-ne-peux-pas-securiser-lacces-a-mes-services-en-utilisant-des-sockets-unix"&gt;Docker, c&amp;#8217;est pourri, je ne peux pas sécuriser l&amp;#8217;accés à mes services en utilisant des sockets &lt;span class="caps"&gt;UNIX&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Bah, déjà, quand je mets mon Mysql d&amp;#8217;un côté, et mon Wordpress de l&amp;#8217;autre, je ne peux plus utiliser de sockets &lt;span class="caps"&gt;UNIX&lt;/span&gt;, pour gérer les droits. Pour ceux qui ne sont pas attentifs, mais les solutions de types Heroku sont encore pire, il n&amp;#8217;y a pas de notion de &lt;span class="caps"&gt;VLAN&lt;/span&gt; privé, et du coup, tous les services non moisis proposent de l&amp;#8217;authentification, même Memcached sait le faire, c&amp;#8217;est&amp;nbsp;dire.&lt;/p&gt;
&lt;p&gt;Dans les gros boulets ouverts aux quatre vents, personnellement, je vois Statsd (avec son protocole &lt;span class="caps"&gt;UDP&lt;/span&gt;) et Elasticsearch, pour son gossip de cluster.
Mais Docker ne vous a pas oublié, il propose un &lt;a href="https://docs.docker.com/engine/userguide/networking/#/user-defined-networks"&gt;système de réseau extensible&lt;/a&gt;, pour créer des sous réseaux permettant de regrouper vos différents services. Pour le reste du monde, ça s&amp;#8217;appelle &lt;a href="https://en.wikipedia.org/wiki/Software-defined_networking"&gt;Software Defined Network&lt;/a&gt;, et il existe plein d&amp;#8217;implémentation libre. Le réseau virtuel est d&amp;#8217;ailleurs actuellement ce qui fait la qualité d&amp;#8217;un service Cloud (&lt;span class="caps"&gt;OVH&lt;/span&gt;, je te&amp;nbsp;regarde).&lt;/p&gt;
&lt;h2 id="docker-cest-pourri-ca-utilise-alpine-qui-utilise-musl-que-cest-compatible-avec-rien"&gt;Docker, c&amp;#8217;est pourri, ça utilise Alpine qui utilise musl que c&amp;#8217;est compatible avec&amp;nbsp;rien&lt;/h2&gt;
&lt;p&gt;Une application à besoin d&amp;#8217;un contexte : une arborescence Linux, et un kernel pour accéder au&amp;nbsp;matériel.&lt;/p&gt;
&lt;p&gt;Une Debian nue doit faire dans les 125Mo, pourquoi ne pas utiliser quelque chose de plus minimaliste, surtout avec les technos qui ont des traditions d&amp;#8217;autarcisme (&lt;span class="caps"&gt;JVM&lt;/span&gt;, Erlang, Golang…)?
Alpine Linux est une distribution qui a une approche &amp;#8220;embarqué&amp;#8221;, basée sur busybox et musl comme libC. 5Mo tout&amp;nbsp;mouillé.&lt;/p&gt;
&lt;p&gt;C&amp;#8217;est rigolo à utiliser, jusqu&amp;#8217;à ce qu&amp;#8217;on essaye de compiler un truc non packagé, et que l&amp;#8217;on tombe sur du code spécifique glibc.
Une image Debian embarque clairement beaucoup trop de bazars pour lancer un seul service bien bordé. Mais bon, ça à le bon gout de marcher, on a la garantie qualité Debian™, et avec le système de couches, l&amp;#8217;image de base sera commune à la plupart des&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Pour l&amp;#8217;instant, les images de base sont basées sur Debian, et c&amp;#8217;est un choix excellent, ça limite au maximum les surprises de la couche basse, et permet de se concentrer sur le côté trop à jour de&amp;nbsp;l&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;Docker aime explorer des voies, mais bon, si Alpine vous chagrine, vous pouvez toujours tester Slackware, comme&amp;nbsp;avant.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, rien ne vous oblige à utiliser une image que ne vous sentez pas, les Dockerfile sont tellement simples à hacker, ne vous privez&amp;nbsp;pas.&lt;/p&gt;
&lt;h2 id="docker-cest-pourri-pour-de-la-prod"&gt;Docker, c&amp;#8217;est pourri pour de la&amp;nbsp;prod&lt;/h2&gt;
&lt;p&gt;Il y a beaucoup de concurrents pour le concours de &amp;#8220;pourri pour la prod&amp;#8221;, hein.
Docker avance couche par couche.
L&amp;#8217;étape prod distribué, par ce que prod sur un serveur, ce n&amp;#8217;est pas non plus super complexe, est son chantier actuel.
Docker Swarm est arrivé avec La release 1.12, et a fait hurler pas mal de personnes. Mais bon, on change d&amp;#8217;échelle niveau complexité, et Raft, c&amp;#8217;est quand même un gros morceaux. Allez demander aux devs de Etcd ou&amp;nbsp;Consul.&lt;/p&gt;
&lt;p&gt;Swarm a quand même l&amp;#8217;audace de s&amp;#8217;attaquer à Kubernetes, la version libre de l&amp;#8217;outil interne de Saint Google, quand&amp;nbsp;même.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kubernetes builds upon 15 years of experience of running production workloads at Google, combined with best-of-breed ideas and practices from the&amp;nbsp;community.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Voici l&amp;#8217;argumentaire &amp;#8220;take that, Batman&amp;#8221; de Google pour ses&amp;nbsp;conccurents.&lt;/p&gt;
&lt;p&gt;Enfin bon, avoir un avis définitif sur un tel outil, sorti fin juillet, c&amp;#8217;est un poil hatif. Il est possible d&amp;#8217;utiliser Docker sans Swarm,&amp;nbsp;promis!&lt;/p&gt;
&lt;h2 id="docker-cest-pourri"&gt;Docker, c&amp;#8217;est&amp;nbsp;pourri&lt;/h2&gt;
&lt;p&gt;Imprimez des teeshirts et des stickers, c&amp;#8217;est efficace avec les&amp;nbsp;slogans.&lt;/p&gt;
&lt;p&gt;Docker est opiniated, avance vite, et il fait beaucoup de&amp;nbsp;bruit.&lt;/p&gt;
&lt;p&gt;On peut lui reprocher plein de choses (avec des arguments), mais s’il n&amp;#8217;y a qu&amp;#8217;un seul argument à retenir pour le défendre :
Docker est un produit opensource qui explore, code, explique et corrige. Et ça, que vous soyez utilisateur ou non de leur produit, c&amp;#8217;est déjà super&amp;nbsp;important.&lt;/p&gt;
&lt;p&gt;La conteneurisation fait déjà partie de l&amp;#8217;informatique moderne, il va falloir s&amp;#8217;y&amp;nbsp;faire.&lt;/p&gt;</content><category term="Ops"></category><category term="Docker"></category><category term="Conteneur"></category></entry><entry><title>GraphQL, repenser le modèle de données</title><link href="http://blog.garambrogne.net/graphql-repenser-le-modele-de-donnees.html" rel="alternate"></link><published>2016-09-14T19:07:00+02:00</published><updated>2016-09-14T19:07:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2016-09-14:/graphql-repenser-le-modele-de-donnees.html</id><summary type="html">&lt;p&gt;GraphQL est une rafraichissante invention de Facebook permettant de manipuler un modèle de donnée en passant par un petit tuyau. Ciblant clairement les applications smartphones (webs ou natives), GraphQL remet à plat tout un tas d&amp;#8217;habitudes et de cargo&amp;nbsp;cult.&lt;/p&gt;</summary><content type="html">&lt;h1 id="lecosysteme-de-react"&gt;L&amp;#8217;écosystème de&amp;nbsp;React&lt;/h1&gt;
&lt;p&gt;Caché derrière son blockbuster &lt;a href="https://facebook.github.io/react/"&gt;React&lt;/a&gt; (accompagné de &lt;a href="https://facebook.github.io/relay/"&gt;Relay&lt;/a&gt;), &lt;a href="http://graphql.org/"&gt;GraphQL&lt;/a&gt; est une rafraichissante invention de Facebook permettant de manipuler un modèle de donnée en passant par un petit tuyau. Ciblant clairement les applications smartphones (webs ou natives), GraphQL remet à plat tout un tas d&amp;#8217;habitudes et de cargo cult. D&amp;#8217;abord utilisé comme arme secrète, il est maintenant libéré, spécifié avec une implémentation de référence (en nodejs) et d&amp;#8217;autres implémentations (se basant sur des frameworks largement&amp;nbsp;utilisés).&lt;/p&gt;
&lt;p&gt;React fait le choix de franchement basculer un maximum d&amp;#8217;intelligence du serveur vers le client, donnant sciemment le pouvoir à JavaScript, le vrai, celui dans le navigateur&amp;nbsp;web.&lt;/p&gt;
&lt;h1 id="les-echanges-clientsserveurs"&gt;Les échanges&amp;nbsp;clients/serveurs&lt;/h1&gt;
&lt;p&gt;Malgré ses velléités d&amp;#8217;autonomies, le &lt;span class="caps"&gt;HTML5&lt;/span&gt; reste massivement utilisé comme une plaisante interface utilisateur connectée en &lt;span class="caps"&gt;HTTP&lt;/span&gt; à un serveur, qui va garder la main sur les données, et coordonner les interactions des multiples&amp;nbsp;clients.&lt;/p&gt;
&lt;h2 id="rest"&gt;&lt;span class="caps"&gt;REST&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Exposer une &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt; est l&amp;#8217;approche la plus neutre et la plus ouverte pour mettre à disposition des données et des fonctions via le protocole &lt;span class="caps"&gt;HTTP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Par contre, implémenter avec ses petites mains un client à partir d&amp;#8217;une &lt;span class="caps"&gt;API&lt;/span&gt; &lt;span class="caps"&gt;REST&lt;/span&gt; est chronophage, fragile, pénible à faire évoluer, douloureux à&amp;nbsp;tester.&lt;/p&gt;
&lt;h2 id="modele"&gt;Modèle&lt;/h2&gt;
&lt;p&gt;Les données, côté client sont manipulées sous forme d&amp;#8217;objets, mais il faut voir comment faire correspondre le modèle local avec le modèle&amp;nbsp;distant.&lt;/p&gt;
&lt;p&gt;Le choix d&amp;#8217;utiliser le même langage, entre le client et le serveur, permet de partager le modèle. Il est possible de choisir JavaScript, comme le fait &lt;a href="https://www.meteor.com/"&gt;Meteor&lt;/a&gt;, ou pire, de choisir un langage tiers, qui sera compilé en JavaScript, client et serveur, comme &lt;a href="http://opalang.org/"&gt;&lt;span class="caps"&gt;OPA&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Une variante de cette approche est de garder son langage de prédilection pour le serveur et de ne générer que la partie cliente, comme le presque oublié &lt;a href="http://www.gwtproject.org/"&gt;&lt;span class="caps"&gt;GWT&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Plutôt que de partir d&amp;#8217;un langage spécifique pour définir son modèle, il est plus sage de passer par une grammaire agnostique pour définir modèles et fonctions qui sera moulinée dans différents langages.
C&amp;#8217;est que propose &lt;a href="http://swagger.io/"&gt;Swagger&lt;/a&gt; (renommé il y a peu &lt;a href="https://openapis.org/"&gt;OpenAPI&lt;/a&gt;, surcouche à &lt;span class="caps"&gt;REST&lt;/span&gt;. Il existe aussi &lt;a href="http://raml.org/"&gt;&lt;span class="caps"&gt;RAML&lt;/span&gt;&lt;/a&gt;, qui semble avoir moins de&amp;nbsp;soutiens.&lt;/p&gt;
&lt;p&gt;Il existe aussi des grammaires neutres, non &lt;span class="caps"&gt;REST&lt;/span&gt; mais pouvant utiliser &lt;span class="caps"&gt;HTTP&lt;/span&gt; comme couche transport, comme Thrift et Protobuff avec son &lt;a href="http://www.grpc.io/"&gt;grpc&lt;/a&gt; qui vient de sortir en version 1.0, mais lié à la toute nouvelle version 2 d&amp;#8217;&lt;span class="caps"&gt;HTTP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Toutes ces technologies permettent d&amp;#8217;accéder à des données en clef/valeur, ou à des fonctions. Il faut donc implémenter une fonction côté serveur, qui sera utilisé côté client, avec du coup un aller-retour demandant des compétences&amp;nbsp;distinctes.&lt;/p&gt;
&lt;h2 id="reseau-et-latences"&gt;Réseau et&amp;nbsp;latences&lt;/h2&gt;
&lt;p&gt;Le premier ennemi des réseaux mobiles est la latence, bien avant le débit. Pour limiter les temps de latence, il faut limiter le nombre de requêtes. Pour pallier aux problèmes de débit, il ne faut rapatrier que le nécessaire pour afficher la réponse. Google, avec &lt;span class="caps"&gt;HTTP&lt;/span&gt;/2 (version normalisée de son &lt;span class="caps"&gt;SPDY&lt;/span&gt;), propose de multiplexer la connexion, sur un canal bidirectionnel qui reste ouvert, permettant ainsi d&amp;#8217;envoyer les données sous une forme permettant de les utiliser au fur et à&amp;nbsp;mesure.&lt;/p&gt;
&lt;h1 id="graphql"&gt;GraphQL&lt;/h1&gt;
&lt;p&gt;Pour optimiser la communication réseau, Facebook se contente de reprendre la tactique du &lt;span class="caps"&gt;SQL&lt;/span&gt; : utiliser un langage concis permettant de sélectionner ce que l&amp;#8217;on souhaite, et de préciser les objets et attributs attendus en&amp;nbsp;réponse.&lt;/p&gt;
&lt;p&gt;Ce langage de requête est le &lt;em&gt;GraphQL&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;La requête est en GraphQL, un nouveau format texte, mais la réponse est classiquement en &lt;span class="caps"&gt;JSON&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;La ressemblance de GraphQL avec &lt;span class="caps"&gt;SQL&lt;/span&gt; est faible, et c&amp;#8217;est une bonne nouvelle : il est clairement impensable de confier la rédaction de &lt;span class="caps"&gt;SQL&lt;/span&gt; au client web, de plus, le modèle relationnel, intimement lié à &lt;span class="caps"&gt;SQL&lt;/span&gt; est loin d&amp;#8217;être un modèle universel, déjà mis à mal par les &lt;span class="caps"&gt;ORM&lt;/span&gt; et le&amp;nbsp;cache.&lt;/p&gt;
&lt;p&gt;Le GraphQL permet de rapatrier une grappe d&amp;#8217;objets, avec un peu de dynamisme, surtout pas d&amp;#8217;effectuer des calculs sur des regroupements ou tout autres action&amp;nbsp;complexes.&lt;/p&gt;
&lt;h2 id="modele-abstrait"&gt;Modèle&amp;nbsp;abstrait&lt;/h2&gt;
&lt;p&gt;Le GraphQL ne sera pas traité directement par une base de données. Il travaille sur un modèle abstrait et orienté graphe. Facebook est fasciné par les graphes, presque autant que Linkedin, et ça tombe bien, c&amp;#8217;est un modèle que notre cerveau arrive bien à appréhender, mais qu&amp;#8217;il est difficile d&amp;#8217;implémenter autrement que tout en &lt;span class="caps"&gt;RAM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;abstraction du modèle laisse toute liberté aux choix d&amp;#8217;implémentations. Des outils sont fournis pour exposer les objets d&amp;#8217;un &lt;span class="caps"&gt;ORM&lt;/span&gt; s&amp;#8217;appuyant sur le classique modèle relationnel, mais rien n&amp;#8217;empêche d&amp;#8217;utiliser d&amp;#8217;autres modèles, fichier par exemple (MongoDB, Couchbase, Elasticsearch…), voir même les modèles hybrides que nous propose l&amp;#8217;intégration de &lt;span class="caps"&gt;JSON&lt;/span&gt; dans Postgresql et plus récemment Mysql. Des &lt;a href="https://github.com/chentsulin/awesome-graphql"&gt;outils tiers&lt;/a&gt; permettent de faire des serveurs GraphQL dans autre chose que du Javascript, et il faut reconnaitre que l&amp;#8217;implémentation pour Python, &lt;a href="https://github.com/graphql-python/graphene"&gt;Graphene&lt;/a&gt;, a une bonne&amp;nbsp;tête.&lt;/p&gt;
&lt;h2 id="typage"&gt;Typage&lt;/h2&gt;
&lt;p&gt;GraphQL utilise du typage, qui permet de systématiser la validation, mais aussi de&amp;nbsp;l&amp;#8217;introspection.&lt;/p&gt;
&lt;p&gt;Le typage permet de garantir la réponse attendue, mais il est suffisement souple pour permettre une évolution du serveur sans imposer la mise à jour du client. Fort pratique pour faire évoluer un service pour une nouvelle application sans pour autant casser une ancienne application utilisant le même&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Il existe un éditeur en ligne, avec une interface sympathique, &lt;a href="https://github.com/graphql/graphiql"&gt;GraphiQL&lt;/a&gt; (notez le i, comme&amp;nbsp;interactif).&lt;/p&gt;
&lt;h2 id="parametres"&gt;Paramètres&lt;/h2&gt;
&lt;p&gt;Une simple astuce permet de révolutionner ce classique usage de graphes : les attributs acceptent des arguments nommés, il est donc possible d&amp;#8217;ajouter du dynamisme (comme une recherche) ou plus simplement du paramétrage (comme une taille&amp;nbsp;d&amp;#8217;image).&lt;/p&gt;
&lt;p&gt;GraphQL ne va pas utiliser le principe de &lt;span class="caps"&gt;REST&lt;/span&gt;, mais se contenter d&amp;#8217;un seul point d&amp;#8217;entrée &lt;span class="caps"&gt;HTTP&lt;/span&gt;, &lt;em&gt;/graphql&lt;/em&gt;, mais va profiter de la session, avec donc une connaissance de&amp;nbsp;l&amp;#8217;utilisateur.&lt;/p&gt;
&lt;h2 id="mutations"&gt;Mutations&lt;/h2&gt;
&lt;p&gt;Initialement conçu pour un accès lecture seul, GraphQL permet maintenant des&amp;nbsp;mutations.&lt;/p&gt;
&lt;h2 id="requeter-simplement"&gt;Requêter&amp;nbsp;simplement&lt;/h2&gt;
&lt;p&gt;Avec GraphQL, le serveur se contente de gérer le modèle (avec la garantie de cohérence, les droits, la persistance), avec du code métier, des middlewares, tout ce genre de bonnes&amp;nbsp;choses.&lt;/p&gt;
&lt;p&gt;Le client pourra librement composer à partir de ces&amp;nbsp;données.&lt;/p&gt;
&lt;p&gt;Par dessus, vous pouvez ajouter Relay et React, pour profiter de l&amp;#8217;écosystème officiel. Mais vous pouvez utiliser d&amp;#8217;autres choses, sans même vous limiter à JavaScript (il suffit de savoir lire du &lt;span class="caps"&gt;JSON&lt;/span&gt; sur de l&amp;#8217;&lt;span class="caps"&gt;HTTP&lt;/span&gt;).&lt;/p&gt;</content><category term="Dev"></category><category term="React"></category><category term="Facebook"></category><category term="GraphQL"></category><category term="database"></category><category term="langage de requête"></category></entry><entry><title>Quis custodiet ipsos custodes?</title><link href="http://blog.garambrogne.net/quis-custodiet-ipsos-custodes.html" rel="alternate"></link><published>2016-09-01T20:00:00+02:00</published><updated>2016-09-01T20:00:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2016-09-01:/quis-custodiet-ipsos-custodes.html</id><summary type="html">&lt;p&gt;La grande bataille de la gestion de process entre Systemd et&amp;nbsp;Docker&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://fr.wikipedia.org/wiki/Quis_custodiet_ipsos_custodes%3F"&gt;Mais qui gardera ces gardiens&amp;nbsp;?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Où il sera question de Linux, de processus, de conteneurs et de choses gravitant&amp;nbsp;autour.&lt;/p&gt;
&lt;h2 id="processus-et-daemon"&gt;Processus et&amp;nbsp;daemon&lt;/h2&gt;
&lt;p&gt;Un serveur regroupe des process, et le premier d&amp;#8217;entre eux, celui avec le pid 1 a la responsabilité de lancer tous les autres lors du&amp;nbsp;démarrage.&lt;/p&gt;
&lt;p&gt;Certains sont des &lt;a href="https://fr.wikipedia.org/wiki/Daemon_(informatique)"&gt;daemons&lt;/a&gt;, des programmes qui tournent en tache de fond, sans interaction directe. Un daemon a la responsabilité d&amp;#8217;abandonner ses privilèges, de changer d&amp;#8217;utilisateur et de racine (chroot), de causer dans un journal, de noter son &lt;span class="caps"&gt;PID&lt;/span&gt;, et de se détacher, de que décrit si bien &lt;a href="https://github.com/zedshaw/python-lust"&gt;Zed Shaw dans Lust&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="init"&gt;Init&lt;/h2&gt;
&lt;p&gt;Historiquement, le rôle d&amp;#8217;init est dévolu à init.d, qui accuse méchamment le poids de son&amp;nbsp;âge.&lt;/p&gt;
&lt;p&gt;Son principe, finalement simpliste alors qu&amp;#8217;il se prétendait simple, est que chaque service est lancé par un script shell, en répondant à quelques commandes&amp;nbsp;: &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, &lt;code&gt;reload&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dans les faits, les efforts de l&amp;#8217;application sont laborieux, et la qualité de ces scripts est variable pour des applications packagés par le système. Pour des applications métiers, la qualité est tout simplement atroce. Le shell n&amp;#8217;a tout simplement pas les commandes nécessaires, et le nébuleux &lt;a href="http://manpages.ubuntu.com/manpages/xenial/fr/man8/start-stop-daemon.8.html"&gt;start-stop-daemon&lt;/a&gt; est une punition à&amp;nbsp;débuguer.&lt;/p&gt;
&lt;p&gt;Il y a eut un peu de tuning, comme le passage de &lt;a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)"&gt;bash&lt;/a&gt; à &lt;a href="https://en.wikipedia.org/wiki/Almquist_shell"&gt;dash&lt;/a&gt; pour grappiller du temps, et des efforts de parallélisation. Rien qui n&amp;#8217;a pu sauver un init.d finalement en phase&amp;nbsp;terminale.&lt;/p&gt;
&lt;h2 id="superviseur"&gt;Superviseur&lt;/h2&gt;
&lt;p&gt;Des tentatives laborieuses ont été explorées, côté utilisateur, comme &lt;a href="https://cr.yp.to/daemontools.html"&gt;daemontools&lt;/a&gt; ou &lt;a href="http://supervisord.org/"&gt;supervisor&lt;/a&gt;. Ces bricolages permettent de se passer de la danse du daemon, et de se contenter de lancer directement l&amp;#8217;application, l&amp;#8217;emballage se chargeant du reste. Deux drames &lt;span class="caps"&gt;UNIX&lt;/span&gt; ont perturbé cette volonté de simplification&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pour utiliser des ports réseaux &amp;lt; 1024, il faut être root, par ce&amp;nbsp;que.&lt;/li&gt;
&lt;li&gt;Un utilisateur ne peut pas changer d&amp;#8217;utilisateur, ça, c&amp;#8217;est du plus facile à justifier. Mais si vous pouvez utiliser un superviseur, qui tourne avec l&amp;#8217;utilisateur root, paf, vous pouvez lancer des commandes en son&amp;nbsp;nom.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;#8217;idée du superviseur n&amp;#8217;a jamais été traitée complètement, et ces outils inaboutis finiront par vous mordre, tôt ou tard. Supervisor, le meilleur des produits disponibles, dépend de bibliothèques mortes depuis des années, impose des rechargements par grappe, et utilise des actions bien&amp;nbsp;séquentielles.&lt;/p&gt;
&lt;p&gt;Pourtant, le &lt;a href="https://devcenter.heroku.com/articles/procfile"&gt;Procfile&lt;/a&gt;, un simple format déclaratif, est devenus un standard de fait, et est maintenant sanctifié par &lt;a href="https://12factor.net/fr/"&gt;les 12 facteurs&lt;/a&gt; , par le &lt;a href="https://12factor.net/fr/processes"&gt;chapitre &lt;span class="caps"&gt;VI&lt;/span&gt;&lt;/a&gt;&amp;nbsp;précisément. &lt;/p&gt;
&lt;h2 id="au-dela-de-chroot"&gt;Au-delà de&amp;nbsp;chroot&lt;/h2&gt;
&lt;p&gt;Pour limiter la possibilité de faire des bêtises, un serveur doit avoir un utilisateur, un &lt;a href="https://fr.wikipedia.org/wiki/Chroot"&gt;chroot&lt;/a&gt; si on est poli, et … c&amp;#8217;est à peu près tout, une limitation sur le nombre de fichiers ouvert?
Depuis peu, d&amp;#8217;autres possibilités de restrictions sont apparues, comme &lt;a href="http://wiki.apparmor.net/"&gt;Apparmor&lt;/a&gt;/&lt;a href="https://www.nsa.gov/what-we-do/research/selinux/"&gt;SELinux&lt;/a&gt;, les &lt;a href="https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/"&gt;cgroups&lt;/a&gt;, les &lt;a href="http://man7.org/linux/man-pages/man7/namespaces.7.html"&gt;namespaces&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Seccomp"&gt;seccomp&lt;/a&gt;, les &lt;a href="http://man7.org/linux/man-pages/man7/capabilities.7.html"&gt;capabilities&lt;/a&gt; … Plein de très belles choses permettant de cloisonner proprement les applications. Techniquement, c&amp;#8217;est à l&amp;#8217;application de gérer tout ça, ou presque, seules les cgroups sont faciles à manipuler depuis&amp;nbsp;l&amp;#8217;extérieur.&lt;/p&gt;
&lt;p&gt;Plutôt que d&amp;#8217;espérer vainement que les applications gèrent proprement toutes ces fonctionnalités, il est nettement plus efficace de reprendre l&amp;#8217;idée simpliste des superviseurs, et d&amp;#8217;emballer chaque&amp;nbsp;process.&lt;/p&gt;
&lt;p&gt;Cette approche, qui sépare l&amp;#8217;application de l&amp;#8217;isolation de son contexte, permet d&amp;#8217;auditer simplement la sécurité, et d&amp;#8217;avoir des politiques systématiques. &lt;a href="https://github.com/google/lmctfy/blob/master/README.md"&gt;&lt;span class="caps"&gt;LMCTFY&lt;/span&gt;&lt;/a&gt;, &amp;#8220;Laisse-moi contenariser ça pour toi&amp;#8221;, comme on disait chez Google, pionnier de cette&amp;nbsp;approche.&lt;/p&gt;
&lt;p&gt;Il suffit de rajouter un &lt;a href="https://en.wikipedia.org/wiki/Union_mount"&gt;système de fichier par couche&lt;/a&gt; pour avoir un chroot propre qui prends peu de place, et voilà, vous avez un système de&amp;nbsp;conteneur.&lt;/p&gt;
&lt;h2 id="la-collision-avec-systemd"&gt;La collision avec&amp;nbsp;Systemd&lt;/h2&gt;
&lt;p&gt;Parallèlement avec l&amp;#8217;apparition des outils systèmes permettant la création des containers est arrivé &lt;a href="https://www.freedesktop.org/wiki/Software/systemd/"&gt;Systemd&lt;/a&gt;, le nouvel init standard pour&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Après quelques concurrents timides à init.d, Systemd a débarqué avec son gros bulldozeur, en choisissant de remettre à plat la fonction d&amp;#8217;init, en incluant la supervision et moult fonctions liées. Avec une diplomatie brutale, peu de respect pour les dogmes, et une grande ambition, Systemd est arrivé à se faire haïr comme peu de logiciels avant. En plus, il est promu (et développé) par Red&amp;nbsp;Hat.&lt;/p&gt;
&lt;p&gt;Mais bon, les réécritures timides d&amp;#8217;init.d n&amp;#8217;avait aucune chance, &lt;a href="http://upstart.ubuntu.com/"&gt;Upstart&lt;/a&gt; s&amp;#8217;étant vautré, il ne restait plus beaucoup d&amp;#8217;alternatives. L&amp;#8217;apparition du Cloud a forcé le destin, par ce qu&amp;#8217;il consomme énormément d&amp;#8217;init, et annihile le culte incompréhensible du dieu uptime. Il est indispensable d&amp;#8217;avoir des cycles de start/stop avec les dépendances qui vont bien, rapides et&amp;nbsp;prévisibles.&lt;/p&gt;
&lt;p&gt;Debian a sifflé la fin de la récré en incluant systemd malgré le peu d&amp;#8217;enthousiasme de ses mainteneurs, suivi par Ubuntu qui a ainsi acté la mort du pénible&amp;nbsp;Upstart. &lt;/p&gt;
&lt;h2 id="le-choc-avec-docker"&gt;Le choc avec&amp;nbsp;Docker&lt;/h2&gt;
&lt;p&gt;Systemd a débloqué plein de choses, mais il a renâclé sur la notion de conteneur, son &lt;a href="https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html"&gt;nspawn&lt;/a&gt; étant officiellement un jouet &amp;#8220;for debugging, testing and building&amp;#8221;. C&amp;#8217;est&amp;nbsp;ballot.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; a démontré que les outils systèmes étaient valide pour créer des conteneurs, sans suivre la fausse bonne idée qu&amp;#8217;est &lt;a href="https://linuxcontainers.org/fr/"&gt;&lt;span class="caps"&gt;LXC&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker, grisé par son succès pense qu&amp;#8217;il va être le prochain init, et qu&amp;#8217;il faut une guerre ouverte avec Systemd. &amp;#8220;&lt;a href="https://static.lwn.net/images/2016/devconf-badge.jpg"&gt;Je dis non à tout patch spécifique à systemd&lt;/a&gt;&amp;#8221; voilà la signature de &lt;a href="https://blog.jessfraz.com/"&gt;Jess Frazelle&lt;/a&gt;, core dev charismatique et influente, sur son étiquette pour une conférence Dockercon. D&amp;#8217;ailleurs, &lt;a href="https://chrome.google.com/webstore/detail/systemd-butts/cdlammifaiaedopfjihjnlidjjkcngja"&gt;elle qualifie Systemd de&amp;nbsp;🍑&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Forcé à faire des concessions par les applications s&amp;#8217;utilisant (ou son concurrent, &lt;a href="https://coreos.com/rkt/docs/latest/app-container.html"&gt;Rkt&lt;/a&gt;), Docker a été contraint d&amp;#8217;écrire des specs, &lt;a href="https://www.opencontainers.org/"&gt;Open Containers&lt;/a&gt;,plutôt que de tout défoncer à chaque release majeur. Fair-play, ils ont créé une implémentation de référence, &lt;a href="https://runc.io/"&gt;runc&lt;/a&gt;, qu&amp;#8217;ils utilisent maintenant comme couche basse pour leur outil (depuis la&amp;nbsp;1.11).&lt;/p&gt;
&lt;p&gt;Systemd, surtout dans sa version Debian stable (Jessie, donc) n&amp;#8217;implémente pas toutes les possibilités d&amp;#8217;isolation du kernel Linux, mais collabore très bien avec runc, qui est par concept peu intrusif et plus facile à mettre à&amp;nbsp;jour.&lt;/p&gt;
&lt;h2 id="le-deplacement-du-conflit"&gt;Le déplacement du&amp;nbsp;conflit&lt;/h2&gt;
&lt;p&gt;Systemd et Docker sont en fait très proche : ils permettent de manipuler des process sur un&amp;nbsp;serveur.&lt;/p&gt;
&lt;p&gt;La notion d&amp;#8217;application, qui n&amp;#8217;a jamais vraiment été autonome est maintenant remplacé par une composition de services. Il est primordial que cette composition soit la même en dev, en test, en préprod et en&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;Autre grande évolution conceptuelle, la notion de serveurs, qu&amp;#8217;ils soient virtuels ou &amp;#8220;bare metal&amp;#8221;, est en train de fondre. Pour pouvoir prolonger la densification, utiliser toujours plus de ressources, et profiter de la fluidité du Cloud, il ne faut plus lier les process à un serveur. Une application est composée d&amp;#8217;une arborescence de services. Il y a maintenant un pool de ressources qui seront alloués par un orchestrateur, responsable de palier au crash des serveurs, et de recâbler les services entre eux, de manière&amp;nbsp;dynamique.&lt;/p&gt;
&lt;p&gt;Docker est le standard de fait pour la conteneurisation. &lt;a href="https://docs.docker.com/compose/overview/"&gt;Docker-Compose&lt;/a&gt; est bien placé pour la description de compositions, et Docker, dans sa version 1.12 a décidé de s&amp;#8217;attaquer à la couche suivante, avec &lt;a href="https://docs.docker.com/engine/swarm/"&gt;Docker-Swarm&lt;/a&gt;, ce qui a fait hurler tous les produits positionnés sur cette couche-là, la seule permettant de proposer des produits financièrement viables. Docker s&amp;#8217;est attaqué ainsi à &lt;a href="http://kubernetes.io/"&gt;Kubernetes&lt;/a&gt;, du dieu Google, avec un produit mal&amp;nbsp;fini.&lt;/p&gt;
&lt;p&gt;Entre cet affront, le clash avec Systemd, le refus de se brimer en suivant des specs, de stabiliser son cycle de vie, des drames de harcèlement moral en interne et surtout l&amp;#8217;importance que représente cette nouvelle étape dans le développement informatique, il va y avoir un&amp;nbsp;drame.&lt;/p&gt;
&lt;p&gt;Comme &lt;a href="http://thenewstack.io/docker-fork-talk-split-now-table/"&gt;un fork&lt;/a&gt;.&lt;/p&gt;</content><category term="Ops"></category><category term="Supervisor"></category><category term="Systemd"></category><category term="Daemon"></category><category term="12 factors"></category><category term="Docker"></category></entry><entry><title>Golang le bouleverseur</title><link href="http://blog.garambrogne.net/golang-le-boulverseur.html" rel="alternate"></link><published>2016-01-25T22:25:00+01:00</published><updated>2016-01-25T22:25:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2016-01-25:/golang-le-boulverseur.html</id><summary type="html">&lt;p&gt;C&amp;#8217;est dur de traduire le mot anglais &amp;#8220;disruption&amp;#8221;. Boulversement semble être le terme le plus approprié. Golang fait partie des éléments &amp;#8220;bouleverseurs&amp;#8221; de&amp;nbsp;l&amp;#8217;informatique.&lt;/p&gt;</summary><content type="html">&lt;h2 id="evolution-et-disruption"&gt;Évolution et&amp;nbsp;disruption&lt;/h2&gt;
&lt;p&gt;C&amp;#8217;est dur de traduire le mot anglais &amp;#8220;disruption&amp;#8221;. Boulversement semble être le terme le plus&amp;nbsp;approprié.&lt;/p&gt;
&lt;p&gt;Le progrès, l&amp;#8217;évolution, n&amp;#8217;est absolument pas linéaire, de temps en temps, plutôt que la douce pente des améliorations, on monte une marche, d&amp;#8217;un&amp;nbsp;coup.&lt;/p&gt;
&lt;p&gt;Golang fait partie des éléments &amp;#8220;bouleverseurs&amp;#8221; de&amp;nbsp;l&amp;#8217;informatique.&lt;/p&gt;
&lt;h2 id="les-trois-couches"&gt;Les trois&amp;nbsp;couches&lt;/h2&gt;
&lt;p&gt;En informatique, sur la table basse du hardware, on pose un gros gâteau multicouche : le logiciel. Tout en bas, la couche du noyau, tout en haut, la couche applicative. Au milieu, quelque chose de plus indistinct, la couche de &lt;a href="https://fr.wikipedia.org/wiki/Middleware"&gt;middleware&lt;/a&gt; (intergiciel selon l&amp;#8217;Office québécois de la langue française), les bases de données et les services&amp;nbsp;non-métier.&lt;/p&gt;
&lt;p&gt;Dans cette couche, on peut par exemple ranger les proxy, les brokers, les compteurs de métriques, les serveurs de cache/noms/temps… Tout ce qu&amp;#8217;il faut pour relier les différentes parties applicatives qui, ensemble, forment une&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;On trouve dans cette catégorie des applications vénérables, éprouvées, indispensables, mais aussi très rigides. Déjà, corriger un bug, puis le maintenir en attendant qu&amp;#8217;il passe upstream, peu de personnes peuvent se le permettre, alors écrire un plugin pour mettre du code métier en C&amp;nbsp;…&lt;/p&gt;
&lt;h2 id="au-dela-du-c"&gt;Au-delà du&amp;nbsp;C&lt;/h2&gt;
&lt;p&gt;Le &lt;a href="http://shop.oreilly.com/product/9781565923065.do"&gt;C est sacré&lt;/a&gt;. Il a été créé pour inventer &lt;span class="caps"&gt;UNIX&lt;/span&gt;, et c&amp;#8217;est la plus fine couche possible au-dessus de l&amp;#8217;assembleur. Les seules limites sont donc le matériel (via le kernel), et des détails, comme la responsabilité de gérer la mémoire qui est à la charge du&amp;nbsp;développeur.&lt;/p&gt;
&lt;p&gt;On se retrouve donc avec les pleins pouvoirs, mais les temps de développement explosent. La vélocité (le ratio fonctionnalité/temps de développement) est un des gros problèmes du&amp;nbsp;C.&lt;/p&gt;
&lt;p&gt;Avec des gros processeurs (qui ne coute pas cher) et des petits temps de dev (qui coute cher), le C est hors de&amp;nbsp;prix.&lt;/p&gt;
&lt;p&gt;Il faut donc aller voir ailleurs : les surcouches, les langages spécialisés (mais compilés), ou les langages de scripts (mais&amp;nbsp;génériques).&lt;/p&gt;
&lt;h2 id="le-scripting"&gt;Le&amp;nbsp;scripting&lt;/h2&gt;
&lt;p&gt;Les langages de scripting ont une vélocité inégalable, mais le surcout en &lt;span class="caps"&gt;RAM&lt;/span&gt; et &lt;span class="caps"&gt;CPU&lt;/span&gt; est loin d&amp;#8217;être négligeable, tout comme sa tradition pénible de ne pas savoir utiliser correctement les threads. La réponse officielle pour les soucis de performances est un mensonge : si c&amp;#8217;est mou, tu n&amp;#8217;as qu&amp;#8217;à recodé la partie qui grippe en C. Double&amp;nbsp;mensonge.&lt;/p&gt;
&lt;p&gt;La plupart des blocages sont dus à des problèmes d&amp;#8217;&lt;span class="caps"&gt;IO&lt;/span&gt; que seul l&amp;#8217;asynchrone peut sauver. &lt;span class="caps"&gt;OK&lt;/span&gt;, le &lt;span class="caps"&gt;SSD&lt;/span&gt; peut aider. Viennent ensuite les problèmes de &lt;span class="caps"&gt;CPU&lt;/span&gt; : le surcout de l&amp;#8217;interprétation (qui peut être compensé par de la compilation Just In Time) et la parallélisation (faire bosser plusieurs coeurs de son&amp;nbsp;processeur).&lt;/p&gt;
&lt;h3 id="optimiser-en-c"&gt;Optimiser en&amp;nbsp;C&lt;/h3&gt;
&lt;p&gt;Le C permet d&amp;#8217;utiliser très efficacement son processeur, mais la parallélisation et l&amp;#8217;asynchrone ne seront pas offertes en cadeau.
Pour appeler simplement du C depuis un autre langage, il existe depuis longtemps des outils génériques : &lt;a href="http://www.swig.org"&gt;&lt;span class="caps"&gt;SWIG&lt;/span&gt;&lt;/a&gt; ou &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;&lt;span class="caps"&gt;FFI&lt;/span&gt;&lt;/a&gt;, une &lt;span class="caps"&gt;API&lt;/span&gt; C, et de la &lt;a href="https://en.wikipedia.org/wiki/Source-to-source_compiler"&gt;transcompilation&lt;/a&gt;, mais le mélange reste douloureux et ne facilitera pas la&amp;nbsp;maintenance.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, imaginer qu&amp;#8217;un bon scripteur fera du bon C est complètement&amp;nbsp;farfelu.&lt;/p&gt;
&lt;p&gt;Intégrer du scripting dans du code C (le contraire, donc), est faisable, mais obtenir de bonnes performances peut être complexe, il n&amp;#8217;y a qu&amp;#8217;à voir les performances de Postgresql avec ses procédures stockées&amp;nbsp;exotiques.&lt;/p&gt;
&lt;p&gt;Il existe des solutions hybrides, basées sur Numpy par exemple, qui émergent pour gérer efficacement des aller-retour entre le scripting et le C : &lt;a href="http://blog.cloudera.com/blog/2015/07/ibis-on-impala-python-at-scale-for-data-science/"&gt;Ibis&lt;/a&gt; et &lt;a href="https://www.monetdb.org/blog/embedded-pythonnumpy-monetdb"&gt;MonetDB&lt;/a&gt;. Mais ce sont des réponses super&amp;nbsp;spécialisées.&lt;/p&gt;
&lt;h3 id="lua"&gt;Lua&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://www.lua.org"&gt;Lua&lt;/a&gt; a été le premier a chambouler la frontière entre C est le reste du monde. Conçu pour gérer des configurations complexes d&amp;#8217;une application en C en utilisant un modèle de mémoire similaire pour pouvoir partager des variables, il est aussi un langage de scripting plus décent que bien d&amp;#8217;autres.
Interprèté, simple et minimaliste, avec un &lt;span class="caps"&gt;JIT&lt;/span&gt; efficace, Lua est le chouchou des dév C pour embarquer du code&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Il y a en ce moment une vague de logiciels intégrant Lua pour définir le comportement métier&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://torch.ch"&gt;Torch&lt;/a&gt; l&amp;#8217;utilise pour faire du calcul scientifique (avec la possibilité d&amp;#8217;utiliser le &lt;span class="caps"&gt;GPU&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Un patch &lt;a href="https://github.com/openresty/lua-nginx-module#readme"&gt;Nginx&lt;/a&gt; (packagé Debian, c&amp;#8217;est dire sa popularité)&amp;nbsp;existe&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.haproxy.com/2015/03/12/haproxy-1-6-dev1-and-lua/"&gt;Haproxy&lt;/a&gt; l&amp;#8217;aura dans sa prochaine version&amp;nbsp;stable&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.haka-security.org/"&gt;Haka&lt;/a&gt; sniff son réseau, &lt;a href="https://nmap.org/book/nse-language.html"&gt;Nmap&lt;/a&gt; le réseau des&amp;nbsp;autres&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sysdig.org"&gt;Sysdig&lt;/a&gt; surveille le système d&amp;#8217;exploitation avec ses&amp;nbsp;chisels&lt;/li&gt;
&lt;li&gt;&lt;a href="http://redis.io/commands/eval"&gt;Redis&lt;/a&gt; et ses procédures stockées. Notons la présence d&amp;#8217;un &lt;a href="http://redis.io/topics/ldb"&gt;dévermineur&lt;/a&gt;&amp;nbsp;spécifique&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref ça fonctionne, et&amp;nbsp;bien.&lt;/p&gt;
&lt;p&gt;Mais, il y a un mais. Débuguer et tester du lua embarqué peut rapidement devenir apocalyptique. Comprendre le workflow du code en C pour y intercaler ses bouts de scripts n&amp;#8217;est pas évident, et il ne sera pas possible d&amp;#8217;aller au-delà de ce qu&amp;#8217;expose l&amp;#8217;application&amp;nbsp;hôte.&lt;/p&gt;
&lt;h3 id="javascript"&gt;Javascript&lt;/h3&gt;
&lt;p&gt;Javascript a un indéniable côté universel et il propose maintenant de bonnes performances grâce au &lt;span class="caps"&gt;JIT&lt;/span&gt; et ses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays"&gt;Arrays spécialisés&lt;/a&gt;. Nodejs l&amp;#8217;a définitivement rendu crédible hors du browser, mais il est dur de trouver des applications embarquant du&amp;nbsp;Javascript.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://electron.atom.io"&gt;Electron&lt;/a&gt; est un framework javascript pour créer des applications pour le &amp;#8220;desktop&amp;#8221;, Javascript y est l&amp;#8217;hôte, et non&amp;nbsp;l&amp;#8217;invité.&lt;/li&gt;
&lt;li&gt;Nginx a du créer son propre interpréteur Javascript : &lt;a href="https://www.nginx.com/blog/nginscript-why-our-own-javascript-implementation/"&gt;nginScript&lt;/a&gt; car les usages prévus par V8 ou Gecko sont trop&amp;nbsp;spécialisés.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comme langage autonome, Node a surpris tout le monde en démontrant l&amp;#8217;efficacité de l&amp;#8217;approche asynchrone et la maturité des interpréteurs Javascripts modernes. Par contre, il n&amp;#8217;a rien réglé sur la confusion du code et la fiabilité en production. Node a effectué un débroussaillage salutaire, mais sans arriver à atteindre un niveau de sérieux suffisant. Il faut voir ce que va apporter &lt;a href="https://nodejs.org/en/docs/es6/"&gt;&lt;span class="caps"&gt;ES6&lt;/span&gt;&lt;/a&gt;, mais il reste encore beaucoup de&amp;nbsp;travail.&lt;/p&gt;
&lt;h3 id="golang"&gt;Golang&lt;/h3&gt;
&lt;p&gt;Conçu pour remplacer le C++ moche, Golang a, contre toutes attentes, été massivement adopté par les scripteurs (python, ruby…) lassés du code linéaire, mou et imprévisible que permet leurs langages pourtant si&amp;nbsp;véloces.&lt;/p&gt;
&lt;p&gt;Golang a des partis pris un peu abrupts&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Golang n&amp;#8217;invente rien, n&amp;#8217;amène aucun nouveau concept. Pas de modèle objet complexe ni même d&amp;#8217;exceptions. Quelqu&amp;#8217;un sachant déjà coder ne sera pas perdu en découvrant&amp;nbsp;Golang.&lt;/li&gt;
&lt;li&gt;Golang est compilé. Le typage est fort, et même le style de syntaxe est&amp;nbsp;imposé.&lt;/li&gt;
&lt;li&gt;Golang ressemble à du scripting, la syntaxe est expressive, la compilation rapide, il n&amp;#8217;y a pas de gestion de mémoire grâce au ramasse-miette, les bibliothèques systèmes sont&amp;nbsp;généreuses.&lt;/li&gt;
&lt;li&gt;La parallélisation et l&amp;#8217;asynchrone sont prévus dés le&amp;nbsp;départ.&lt;/li&gt;
&lt;li&gt;Golang utilise les types et les structs du C, appeler du code C est triviale avec &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avec ces choix et ces contraintes, Golang permet de développer efficacement des applications à tous les niveaux du gateau logiciel :
 des couches basses (comme le fameux &lt;a href="https://www.docker.com"&gt;Docker&lt;/a&gt;), des applications distribuées, des bases de données, du middleware, des applications&amp;nbsp;métiers.&lt;/p&gt;
&lt;p&gt;Golang fait sauter la distinction entre le code système et le code&amp;nbsp;métier.&lt;/p&gt;
&lt;p&gt;Malgré l&amp;#8217;absence de modules dynamiques et de &lt;a href="https://en.wikipedia.org/wiki/Generic_programming"&gt;generics&lt;/a&gt;, Golang fournit des bibliothèques de grande qualitées :
beaucoup d&amp;#8217;applications Golang ne sont en fait que des gros frameworks permettant de composer une application, et pas simplement la&amp;nbsp;configurer.&lt;/p&gt;
&lt;p&gt;Ces deux derniers points sont tout simplement une&amp;nbsp;révolution.&lt;/p&gt;
&lt;p&gt;Avant, les gens se contentaient de tricoter avec des outils existants, où s&amp;#8217;arrêtait en route, faute&amp;nbsp;d&amp;#8217;outils.&lt;/p&gt;
&lt;p&gt;La fin de la loi de Moore, la maturité du cloud et de la virtualisation, les exigences de résiliance, tout pousse les applications à travailler en environement distribué.
Ce type d&amp;#8217;environnement amène avec lui son lot de complexité, qui nécessite des bibliothèques robustes, et plus seulement des services&amp;nbsp;fiables.&lt;/p&gt;
&lt;p&gt;Java a bien prouvé qu&amp;#8217;il était possible de cloner les outils révélés par Google (MapReduce, BigTable, Chubby, Borg, Dremel…), mais voilà, Java, quoi.
Le ticket d&amp;#8217;entrée est élevé, que ce soit en compétence technique et en nombre de serveurs.
C&amp;#8217;est rentable pour du big data, qui nécessite déjà des machines par palettes entières, mais c&amp;#8217;est rapidement impressionnant pour des applications distribuées plus classiques. Les gros fournisseurs de clouds fournissent certains des ses outils, mais comme service, en mode boite noire, la vengeance du logiciel propriétaire contre l&amp;#8217;hégémonie de&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Golang a montré que l&amp;#8217;on pouvait créer ces outils de coordinations, simplement, et de manière composable, pour ensuite créer des applications distribuées.
Cette zone floue entre bibliothèque et service semble impossible en C/C++, un problème de packaging, de divergence d&amp;#8217;approche.
C&amp;#8217;est dommage, des applications distribuées existent, comme les bases de données
&lt;a href="http://www.scylladb.com"&gt;ScyllaDB&lt;/a&gt;,
&lt;a href="http://rethinkdb.com"&gt;RethinkDB&lt;/a&gt;,
&lt;a href="http://www.aerospike.com"&gt;Aerospike&lt;/a&gt;&amp;nbsp;…&lt;/p&gt;
&lt;p&gt;Ces applications sont prometteuses, mais sont développés en autarcie, pas moyen d&amp;#8217;en recomposer simplement de nouvelles à partir du travail&amp;nbsp;existant.&lt;/p&gt;
&lt;p&gt;De son côté Golang propose des briques de base permettant d&amp;#8217;agir sur l&amp;#8217;ensemble de la&amp;nbsp;stack.&lt;/p&gt;
&lt;p&gt;Les bibliothèques de Golang permettent un accès complet aux couches basses, mais le réseau est sa grande force,
il gère différents protocoles réseau, au-delà de l&amp;#8217;inévitable &lt;span class="caps"&gt;HTTP&lt;/span&gt; :
&lt;a href="https://godoc.org/golang.org/x/net/http2"&gt;http2&lt;/a&gt;,
&lt;a href="https://godoc.org/golang.org/x/crypto/ssh"&gt;ssh&lt;/a&gt;,
&lt;a href="https://golang.org/pkg/crypto/tls/"&gt;tls&lt;/a&gt;,
&lt;a href="https://github.com/miekg/dns"&gt;dns&lt;/a&gt;… ce qui permet d&amp;#8217;agir à chacune des étapes du protocole.
Des outils complémentaires permettent de simplifier l&amp;#8217;utilisation de protocoles classiques, comme
&lt;a href="https://github.com/vulcand/oxy"&gt;oxy&lt;/a&gt; pour créer des proxy http, &lt;a href="https://github.com/hashicorp/yamux"&gt;yamux&lt;/a&gt; pour multiplexer des connexions &lt;span class="caps"&gt;TCP&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;D&amp;#8217;autres bibliothèques réseaux, elles, fournissent des outils de haut niveau, comme la coordination sans maitre : &lt;a href="https://github.com/hashicorp/raft"&gt;raft&lt;/a&gt; (Paxos étant jugé trop complexe) ou la communication par potin (gossip) : &lt;a href="https://github.com/hashicorp/serf"&gt;serf&lt;/a&gt;, ou le déverrouillage par double clef : &lt;a href="https://github.com/cloudflare/redoctober"&gt;red october&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Par dessus sont construit des outils réseaux de haut niveau : &lt;a href="http://vulcand.github.io"&gt;Vulcand&lt;/a&gt;, &lt;a href="https://coreos.com/etcd/"&gt;etcd&lt;/a&gt;, &lt;a href="https://www.consul.io"&gt;Consul&lt;/a&gt;…&lt;/p&gt;
&lt;p&gt;Il faut être honnête et reconnaitre que l&amp;#8217;écosystème Java fournit des outils similaires avec &lt;a href="https://zookeeper.apache.org"&gt;Zoo keeper&lt;/a&gt;, &lt;a href="http://akka.io"&gt;Akka&lt;/a&gt;, &lt;a href="http://netty.io"&gt;Netty&lt;/a&gt;, &lt;a href="https://github.com/Netflix/Hystrix"&gt;Hystrix&lt;/a&gt;, &lt;a href="https://github.com/Netflix/zuul"&gt;Zuul&lt;/a&gt;…, mais on est clairement à une autre échelle, et Java est une famille&amp;nbsp;envahissante.&lt;/p&gt;
&lt;p&gt;Golang profite d&amp;#8217;une bienveillante neutralité, faisant fi des guerres des langages&amp;nbsp;historiques.&lt;/p&gt;
&lt;p&gt;Jusqu&amp;#8217;à présent, il y avait une séparation claire entre langages système et langage métier.
Cette frontière est fortement remise en cause avec Golang, et je suis curieux de voir ce que va pouvoir apporter &lt;a href="https://www.rust-lang.org"&gt;Rust&lt;/a&gt; dans cette zone&amp;nbsp;intermédiaire.&lt;/p&gt;</content><category term="Dev"></category><category term="golang"></category></entry><entry><title>Fighting with EventMachine</title><link href="http://blog.garambrogne.net/fighting-with-eventmachine.html" rel="alternate"></link><published>2015-06-16T21:41:00+02:00</published><updated>2015-06-16T21:41:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-06-16:/fighting-with-eventmachine.html</id><summary type="html">&lt;p&gt;Refurbished post about async Ruby : event&amp;nbsp;machine.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here is one of my old post (late 2011) from &lt;a href="http://dev.af83.com/2011/09/20/fighting-with-eventmachine.html"&gt;dev.af83.com&lt;/a&gt; about EventMachine, when I worked with ruby. &lt;span class="caps"&gt;TL&lt;/span&gt;;&lt;span class="caps"&gt;DR&lt;/span&gt; without &lt;a href="https://en.wikipedia.org/wiki/Futures_and_promises"&gt;futures/promises&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Fiber_(computer_science)"&gt;fibers&lt;/a&gt; async languages are&amp;nbsp;doomed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it&amp;#8217;s your first night with EventMachine, you have to&amp;nbsp;fight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="eventmachine-in-a-nutshell"&gt;EventMachine in a&amp;nbsp;nutshell&lt;/h2&gt;
&lt;p&gt;EventMachine is an implementation of the &lt;a href="http://en.wikipedia.org/wiki/Reactor_pattern"&gt;reactor pattern&lt;/a&gt;. It&amp;#8217;s not Twisted, nor Node, nor Erlang. It follows the Ruby way, hence you have to follow the EventMachine&amp;nbsp;way.&lt;/p&gt;
&lt;h2 id="the-event-loop-pattern-is-now-mainstream"&gt;The event-loop pattern is now&amp;nbsp;mainstream&lt;/h2&gt;
&lt;p&gt;The event-loop pattern is quite simple. The actions are no more systematically sequential. It&amp;#8217;s not thread-based: there is only one thread for the event-loop, therefore execution is still sequential. This enforce a unique context, accessing variables is still atomic (no&amp;nbsp;concurrency).&lt;/p&gt;
&lt;p&gt;Classical asynchronous actions are waiting actions, such as IOs (network or disk access). This makes long-lived actions (&lt;span class="caps"&gt;CPU&lt;/span&gt; intensive) to jam the flow. Async actions are detached from the flow, and a callback is called when it finished.
If you don&amp;#8217;t take care, you will have stairs of block (one for each callback) and troubles for handling errors, the now infamous &lt;em&gt;callback spaghetti&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="the-eventmachine-way"&gt;The EventMachine&amp;nbsp;way&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://google.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;got a response&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;too early&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first block handles the reactor, the second one is the&amp;nbsp;callback.&lt;/p&gt;
&lt;p&gt;What if I want to fetch more than one&amp;nbsp;url?&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://google.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;yahoo&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://yahoo.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;yahoo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yahoo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As the event-loop pattern forces you to nest callbacks, you&amp;#8217;re building an infinite staircase. It also makes actions sequential, but you may don&amp;#8217;t want google to answer before yahoo. You also had to add one level of indentation per&amp;nbsp;url.&lt;/p&gt;
&lt;p&gt;A workaround is to handle it by&amp;nbsp;hand:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%w{ google.com yahoo.com}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don&amp;#8217;t you love flashbacks in movies? This way, url fetches are parallel, and you&amp;#8217;ve even get a callback when &lt;em&gt;all&lt;/em&gt; actions are&amp;nbsp;finished.&lt;/p&gt;
&lt;p&gt;EventMachine provides a helper for such common&amp;nbsp;patterns:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%w{ google.com yahoo.com}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It is the official way, but &lt;span class="caps"&gt;IMO&lt;/span&gt; it turned out not as elegant as it could have been. The&amp;nbsp;first &lt;code&gt;Proc&lt;/code&gt; handles the iterations&amp;nbsp;and &lt;code&gt;return&lt;/code&gt; the response, the&amp;nbsp;second &lt;code&gt;Proc&lt;/code&gt; is the &lt;em&gt;finished&lt;/em&gt;&amp;nbsp;callback.&lt;/p&gt;
&lt;p&gt;Em-http-request provides a specific object for doing batch jobs in a simpler&amp;nbsp;form:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MultiRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:google&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://google.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:yahoo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://yahoo.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:callback&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:errback&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Short, elegant, and specific, isn&amp;#8217;t it? As nobody cares about the specific requests&amp;#8217; callbacks, they are out. The response is &lt;em&gt;deferrable&lt;/em&gt; and MultiRequest collects the spreaded responses. You don&amp;#8217;t have to handle parallel or sequential multi-actions in your own &lt;span class="caps"&gt;API&lt;/span&gt;. It&amp;#8217;s really a job for EventMachine (or&amp;nbsp;Synchrony)!&lt;/p&gt;
&lt;h3 id="the-mysterious-deferrable-behavior"&gt;The mysterious Deferrable&amp;nbsp;behavior&lt;/h3&gt;
&lt;p&gt;As seen above, an async action returns a deferrable response, which is a mysterious object bound a callback. Why not a&amp;nbsp;classical &lt;code&gt;Proc&lt;/code&gt;? Why not a quick and&amp;nbsp;dirty &lt;code&gt;DefaultDeferrable&lt;/code&gt; as you may have seen in &lt;span class="caps"&gt;EM&lt;/span&gt;&amp;#8217;s&amp;nbsp;doc?&lt;/p&gt;
&lt;p&gt;The callback is just a trigger, it is not responsible for giving the answer back: the deferrable object &lt;strong&gt;is&lt;/strong&gt; the&amp;nbsp;answer.&lt;/p&gt;
&lt;p&gt;But Deferrable is a complex answer, providing a state, two (actual) triggers, a success callback and a error callback; and an optional&amp;nbsp;timeout.&lt;/p&gt;
&lt;p&gt;In this pattern, an async function returns a response immediately (a Deferrable one), but the actual &lt;em&gt;value&lt;/em&gt; of the response will be available later. Think of it as a closed box that will open itself, later&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Using &lt;span class="caps"&gt;API&lt;/span&gt; functions with a block is just a syntactic sugar, but don&amp;#8217;t forget to provide both a callback and an errorback, and more importantly, you must return a&amp;nbsp;Deferrable.&lt;/p&gt;
&lt;h3 id="example-of-an-api-based-on-eventmachine"&gt;Example of an &lt;span class="caps"&gt;API&lt;/span&gt; based on&amp;nbsp;EventMachine&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s take a simple example: airports are required to freely give weather information (no authentication layer to handle). So here is the first weather webservice I found in Google. It&amp;#8217;s &lt;span class="caps"&gt;PHP&lt;/span&gt;-based on the server side, response serialization is done the oldschool way, but it&amp;#8217;s free indeed and &lt;em&gt;it just works&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Weather&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Deferrable&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;attr_reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:humidity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:wind_speed&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rough&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@humidity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@wind_speed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rough&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;Weather: code=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; temp=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@temp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; humidity=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@humidity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; wind_speed=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@wind_speed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;meteo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://stationmeteo.meteorologic.net/metar/your-metar.php?type=mes&amp;amp;icao=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;icao&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_feed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;icao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;succeed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#giving itself as first argument give the choice to how handle answer&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And now an example with EventMachine&amp;#8217;s&amp;nbsp;iterator:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Airport of Lyon, Paris and Marseille; US airports don&amp;#39;t seems to work.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;%w{LFRS LFLL LFML}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;airport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;meteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;airport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="fiber-or-the-hidden-threaded-way"&gt;Fiber, or the hidden-threaded&amp;nbsp;way&lt;/h3&gt;
&lt;p&gt;It can be funny to fight with callback in daemon project, at least for some time. But it may come a time when you just yearn for sequential programming. Still you want to use this cool framework wich uses EventMachine to handle lots of parallels connections. How would you do&amp;nbsp;then?&lt;/p&gt;
&lt;p&gt;You could strive to parrallelize a few requests, but most of the time, you just want to be able to describe your needs in a sequential fashion, &lt;em&gt;everywhere&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Fiber, shipped with Ruby 1.9, is the answer. A Fiber waits for the response, for you, pausing the execution in the middle of a flat code chunk using &lt;em&gt;#yield&lt;/em&gt; and resuming execution at any time with &lt;em&gt;#resume&lt;/em&gt;. We are using Synchrony&amp;nbsp;here:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ameteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:meteo&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;meteo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ameteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;icao&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Usage is&amp;nbsp;straightforward:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synchrony&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LFRS&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LFML&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sequential actions are now, well, sequential.&amp;nbsp;Revolutionary.&lt;/p&gt;
&lt;p&gt;In order to transform you previously async methods into a Fiber-aware one, you may prefix the method with &lt;em&gt;a&lt;/em&gt; (as in &lt;em&gt;a&lt;/em&gt;sync), then wraps it within a fiber which will be resumed in the callbacks. It can be made systematic, have a look of how Synchrony monkey patches common&amp;nbsp;libraries.&lt;/p&gt;
&lt;p&gt;You could also explicitly ask for parallel actions, using an async variant of your&amp;nbsp;code.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synchrony&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Synchrony&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;%w{LFRS LFLL LFML}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;airport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ameteo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;airport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;synchrony&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Synchrony&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:satolas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ameteo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LFRS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:paris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ameteo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LFLL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:callback&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;EventMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Dev"></category><category term="ruby"></category><category term="EventMachine"></category><category term="async"></category><category term="fiber"></category></entry><entry><title>Microservices : orchestrer, chorégraphier, visualiser et isoler</title><link href="http://blog.garambrogne.net/microservices-orchestrer-choregraphier-visualiser-isoler.html" rel="alternate"></link><published>2015-05-27T00:15:00+02:00</published><updated>2015-05-27T00:15:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-05-27:/microservices-orchestrer-choregraphier-visualiser-isoler.html</id><summary type="html">&lt;p&gt;Les microservices : un nouveau buzzword, de la formalisation de choses existantes, de nouveaux concepts, de nouvelles responsabilités et de nouveaux drames à&amp;nbsp;découvrir.&lt;/p&gt;</summary><content type="html">&lt;h1 id="microservices"&gt;Microservices&lt;/h1&gt;
&lt;p&gt;Le principe du microservce est simple : une application est composé de différents petits services, fortement découplés, et orientés métier. Ces services communiquent entre eux pour composer une application. L&amp;#8217;application, naturellement distribuée, gagne en souplesse et en robustesse, l&amp;#8217;utilisation des ressources est optimisée, les évolutions (changements et croissance) sont&amp;nbsp;simplifiées.&lt;/p&gt;
&lt;p&gt;Les microservices sont une nouvelle étape dans les architectures serveurs. Créées en opposition aux applications monolithiques, les microservices utilisent pleinement les possibilités du Cloud computing et des conteneurs.
Théorisés et mis en place par de très gros sites (Netflix, Twitter, Google …), les nouvelles abstractions, et les outils adéquats, qu&amp;#8217;apportent les microservices sont maintenant utilisables par tout le&amp;nbsp;monde.&lt;/p&gt;
&lt;p&gt;Tout cela a bien sûr un coût : l&amp;#8217;application est plus complexe, plus dure à tester et la gestion des latences devient une priorité. La robustesse n&amp;#8217;existe que si les clients font l&amp;#8217;effort d&amp;#8217;implémenter une stratégie de tolérance aux pannes (refaire un appel échoué sur un nouveau noeud, gèrer les&amp;nbsp;timeouts…).&lt;/p&gt;
&lt;h1 id="routage"&gt;Routage&lt;/h1&gt;
&lt;p&gt;Le point central de ce type d&amp;#8217;application reste le routage, comme pour les classiques sites&amp;nbsp;web.&lt;/p&gt;
&lt;p&gt;Par contre, cette notion de routage est généralisée. Le traitement peut-être synchrone ou asynchrone, séquentiel ou parallèle, unitaire ou par lot, avec un ou plusieurs essais, ou un mix de tout&amp;nbsp;ça.&lt;/p&gt;
&lt;p&gt;Ces différentes approches existent actuellement de manière dispersée, avec le routage des applications webs, les services web, et les workers asynchrones avec ses files&amp;nbsp;d&amp;#8217;attente.&lt;/p&gt;
&lt;p&gt;Une notion formelle de &lt;span class="caps"&gt;RPC&lt;/span&gt; peut être utilisé (&lt;a href="https://thrift.apache.org/"&gt;thrift&lt;/a&gt;, &lt;a href="http://twitter.github.io/finagle/"&gt;finagle&lt;/a&gt;…) mais une simple sérialisation (&lt;span class="caps"&gt;JSON&lt;/span&gt;, &lt;a href="http://msgpack.org/"&gt;msgpack&lt;/a&gt;, &lt;a href="https://github.com/google/protobuf/"&gt;protobuff&lt;/a&gt;…) avec du classique &lt;span class="caps"&gt;REST&lt;/span&gt;, ou un serveur de message (&lt;a href="https://www.amqp.org/"&gt;&lt;span class="caps"&gt;AMQP&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://github.com/bitly/nsq"&gt;&lt;span class="caps"&gt;NSQ&lt;/span&gt;&lt;/a&gt;, Redis…) fonctionne tout aussi&amp;nbsp;bien.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;abstraction de message, bien connu dans le protocole &lt;span class="caps"&gt;HTTP&lt;/span&gt;, permet d&amp;#8217;enrichir, de restreindre, de sécuriser, d&amp;#8217;organiser, de compresser, de surveiller les échanges de messages indépendamment de leur contenu et de manière transparente pour l&amp;#8217;application. Des outils classiques et éprouvés comme HAproxy ou Nginx ont tout à fait leur place dans le nouveau monde des&amp;nbsp;microservices.&lt;/p&gt;
&lt;h1 id="dynamisme"&gt;Dynamisme&lt;/h1&gt;
&lt;p&gt;Le découplage et la communication par message permettent de distribuer simplement les services (en faisant varier le nombre d&amp;#8217;instances d&amp;#8217;un même service). De manière similaire un service crashé peut être remplacé sans heurt. Les mises à jour peuvent être roulantes, ou même atomiques en refusant les nouvelles requêtes, attendant la fin de requêtes en cours, avant de basculer le routage sur la nouvelle version du service. Des choses plus complexes, comme du A/B testing sont réalisable&amp;nbsp;simplement. &lt;/p&gt;
&lt;p&gt;Le routage peut facilement devenir dynamique et même réactif, utilisant le concept de découvertes de&amp;nbsp;service.&lt;/p&gt;
&lt;p&gt;Pour garantir un tant soit peu de cohérence, il est possible d&amp;#8217;utiliser un coordinateur comme le tant redouté &lt;a href="https://zookeeper.apache.org"&gt;ZooKeeper&lt;/a&gt;, ou une de ses alternatives comme &lt;a href="https://github.com/coreos/etcd"&gt;Etcd&lt;/a&gt; ou &lt;a href="https://www.serfdom.io"&gt;Serf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Des outils de plus haut niveau se basent sur ces outils, comme &lt;a href="https://consul.io/"&gt;consul&lt;/a&gt;, &lt;a href="https://github.com/coreos/fleet"&gt;fleet&lt;/a&gt;, &lt;a href="https://vulcand.io/"&gt;vulcan&lt;/a&gt; ou &lt;a href="https://github.com/Netflix/eureka"&gt;Eureka&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Crée à l&amp;#8217;origine comme base clef/valeur distribuée pour mettre à disposition une configuration pour un cluster, les produits ont évolué pour proposer du pub/sub (les clients intéressés sont prévenus d&amp;#8217;un changement de clef et déclenche une action), puis du &lt;span class="caps"&gt;DNS&lt;/span&gt; (en exposant les valeurs de la base) et même du monitoring (la disparition d&amp;#8217;un service devient un évènement, qui sera&amp;nbsp;traité).&lt;/p&gt;
&lt;p&gt;Un nouvel antagonisme apparait avec le routage dynamique : le choix entre l&amp;#8217;orchestration et la chorégraphie. L&amp;#8217;orchestration déclare les différentes routes de manière explicite, la chorégraphie donne des instructions pour ensuite laisser les différents éléments se&amp;nbsp;débrouiller.&lt;/p&gt;
&lt;p&gt;Pour valider la résilience de l&amp;#8217;approche &amp;#8220;chorégraphie&amp;#8221;, il existe le mythique &lt;a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey"&gt;Chaos Monkey&lt;/a&gt;, que seuls les plus braves osent l&amp;#8217;utiliser en&amp;nbsp;production.&lt;/p&gt;
&lt;h1 id="inspecter"&gt;Inspecter&lt;/h1&gt;
&lt;p&gt;Un service peut être composé d’un ou plusieurs services, eux-mêmes composés de services. Le client discutant avec une façade, il ne connait (ni se soucis) de l&amp;#8217;implémentation du service. Cette approche aide au découplage des différents services, mais rend plus complexes le débug et l&amp;#8217;optimisation des différentes&amp;nbsp;latences.&lt;/p&gt;
&lt;p&gt;Comme à chaque fois, la réponse à été donnée par Google il y a quelque temps (en 2010), sous la forme d&amp;#8217;un white paper : &lt;a href="http://research.google.com/pubs/pub36356.html"&gt;Dapper&lt;/a&gt;. Comme souvent, une implémentation en Java en est faite, par Twitter, cette fois-ci : &lt;a href="http://twitter.github.io/zipkin/"&gt;Zipkin&lt;/a&gt;. Zipkin vise gros et il prévoit de travailler avec le reste de l&amp;#8217;écosystème Twitter. Il existe heureusement un clone plus léger, &lt;a href="https://github.com/sourcegraph/appdash"&gt;Appdash&lt;/a&gt;, avec un protocole simple et bien&amp;nbsp;spécifié.&lt;/p&gt;
&lt;p&gt;Prévu initialement pour mesurer les latences sans perturber le service (avec de l&amp;#8217;échantillonnage léger, 1 sur 1024), ces outils de tracing permettent aussi de voir simplement ce qu&amp;#8217;il se passe, de repérer ce qu&amp;#8217;il est pertinent de paralléliser, d&amp;#8217;aider à optimiser et&amp;nbsp;dimensionner.&lt;/p&gt;
&lt;p&gt;Google insiste sur la notion d&amp;#8217;ubiquité dans Dapper et instrumente tout son code à partir de quelques bibliothèques clefs (gestion de thread, du&amp;nbsp;réseau…).&lt;/p&gt;
&lt;p&gt;Avec du monkeypatch ou des hooks, il n&amp;#8217;est pas compliqué d&amp;#8217;instrumenter des frameworks &lt;span class="caps"&gt;RPC&lt;/span&gt; comme &lt;a href="https://github.com/onefinestay/nameko"&gt;Nameko&lt;/a&gt; : j&amp;#8217;avais fait un &lt;a href="https://gist.github.com/athoune/2542c2042d676764556f"&gt;prototype en monkeypatch&lt;/a&gt;, j&amp;#8217;ai eu droit à un commentaire proposant une meilleure approche avec un hook&amp;nbsp;élégant.&lt;/p&gt;
&lt;p&gt;Il est aussi possible de faire de l&amp;#8217;instrumentation en boite noire avec des outils comme &lt;a href="http://packetbeat.com/"&gt;Packet beat&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="visualiser"&gt;Visualiser&lt;/h1&gt;
&lt;p&gt;L&amp;#8217;ensemble des services forme des réseaux analysables et visualisables par des outils classiques comme &lt;a href="https://networkx.github.io/"&gt;Networkx&lt;/a&gt;. Ces réseaux peuvent être décrits de manière explicite, par introspection, ou en surveillant (en échantillonant) la communication engendrée par l&amp;#8217;application ou des&amp;nbsp;tests.&lt;/p&gt;
&lt;p&gt;Plus sportif, il est possible de simuler et de tester le coté dynamique des routes avec &lt;a href="https://github.com/adrianco/spigo"&gt;Spigo&lt;/a&gt;, prometteur, mais pour l&amp;#8217;instant très lié à l&amp;#8217;écosystème de&amp;nbsp;Netflix.&lt;/p&gt;
&lt;h1 id="isoler"&gt;Isoler&lt;/h1&gt;
&lt;p&gt;Le découpage permet aussi d&amp;#8217;isoler les services, pour simplifier la prise en main par différentes équipes, pour déployer chacun à son&amp;nbsp;rythme.&lt;/p&gt;
&lt;p&gt;Mais le découpage permet aussi de compartimenter l&amp;#8217;accés aux ressources, pour éviter qu&amp;#8217;un incident sur un service entraine l&amp;#8217;ensemble et finisse par faire tomber&amp;nbsp;l&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;Le terme technique est disjoncteur. En cas d&amp;#8217;abus, on coupe tout de suite, avant que ça casse&amp;nbsp;tout. &lt;/p&gt;
&lt;p&gt;Concrètement, une utilisation fine des timeouts permet d&amp;#8217;écourter pas mal de drames.
Une allocation fixe des ressources permet de garantir que chaque service n&amp;#8217;ira pas perturber son voisin. Les cgroups de Linux ont été conçues pour ça, et sont un des piliers de la conteneurisation.
Contraindre les accès aux disques durs et au réseau sera plus un poil plus complexe que le &lt;span class="caps"&gt;CPU&lt;/span&gt; et la &lt;span class="caps"&gt;RAM&lt;/span&gt;. Pour limiter la portée des bêtises (volontaire ou non), &lt;a href="http://wiki.apparmor.net/index.php/Main_Page"&gt;apparmor&lt;/a&gt; est simple à mettre en&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;Il est possible d&amp;#8217;automatiser entièrement la mise à disposition de ressources matérielles avec &lt;a href="http://mesos.apache.org/"&gt;Mesos&lt;/a&gt;, mais le ticket d&amp;#8217;entrée est conséquent, &lt;a href="https://docs.docker.com/swarm/"&gt;Swarm&lt;/a&gt; semble plus économe, mais reste encore très&amp;nbsp;jeune.&lt;/p&gt;
&lt;p&gt;Les outils de containerisation sont conçus pour restreindre l&amp;#8217;utilisation des services, mais aussi pour mesurer leurs usages. Les outils de mesures sont en pleine effervescence, dispersés, mais &lt;a href="https://github.com/google/cadvisor"&gt;cAdvisor&lt;/a&gt; propose un produit complet, se connectant sur &lt;a href="http://influxdb.com/"&gt;Influxdb&lt;/a&gt; ou le très prometteur &lt;a href="http://prometheus.io/"&gt;Prometheus&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="la-suite"&gt;La&amp;nbsp;suite&lt;/h1&gt;
&lt;p&gt;Les microservices sont bien entendu un buzzword de plus, mais ils existent bel et bien.
Pour l&amp;#8217;instant peu formalisés, les microservices sont un ensemble de principe à suivre, pour généraliser et étendre l&amp;#8217;architecture orientée service qui est actuellement la norme.
Les ennemis des microservices sont clairement identifiés (le couplage fort, les applications monolithiques, les latences, les problèmes de montée en puissance…), les drames possibles le sont un peu&amp;nbsp;moins.&lt;/p&gt;
&lt;p&gt;Toutes innovations amènent avec elles une catastrophe potentielle. Les gens qui ont fabriqué le Titanic ont aussi inventé le naufrage à grande échelle.
Il est donc important d&amp;#8217;anticiper les drames potentiels pour ne pas se laisser surprendre, et de surveiller les erreurs que font les autres, pour éviter de les&amp;nbsp;reproduire.&lt;/p&gt;</content><category term="Ops"></category><category term="microservices"></category><category term="container"></category><category term="cgroups"></category></entry><entry><title>Un bac à sable pour Ruby</title><link href="http://blog.garambrogne.net/un-bac-a-sable-pour-ruby.html" rel="alternate"></link><published>2015-04-13T20:00:00+02:00</published><updated>2015-04-13T20:00:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-04-13:/un-bac-a-sable-pour-ruby.html</id><summary type="html">&lt;p&gt;Isoler du code ruby avec les outils des&amp;nbsp;conteneurs.&lt;/p&gt;</summary><content type="html">&lt;h1 id="un-bac-a-sable-pour-ruby"&gt;Un bac à sable pour&amp;nbsp;Ruby&lt;/h1&gt;
&lt;p&gt;Je ne sais plus trop comment cette histoire a commencé. Hum… une discussion engagée par un développeur de &lt;a href="http://codepen.io"&gt;codepen.io&lt;/a&gt; sur la possibilité d&amp;#8217;utiliser des Dockers jetables pour &amp;#8220;rendre&amp;#8221; des fichiers &lt;a href="http://haml.info/"&gt;&lt;span class="caps"&gt;HAML&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;HAML&lt;/span&gt; est un format de template pour Ruby très peu verbeux, basé sur l&amp;#8217;indentation. Il permet, entre autres, de faire des choses dangereuses, comme &lt;a href="http://haml.info/docs/yardoc/file.REFERENCE.html#ruby_evaluation"&gt;insérer du ruby dans le gabarit&lt;/a&gt;. Cette option est débrayable, heureusement, mais cet outil n&amp;#8217;a jamais été conçu dans une optique de sécurité, mais de rendre le développeur heureux. Comme tous les produits Ruby, en&amp;nbsp;fait.&lt;/p&gt;
&lt;p&gt;Proposer de rendre des fichiers &lt;span class="caps"&gt;HAML&lt;/span&gt; comme service web est donc dangereux, il ne doit pas être bien difficile de mettre le bazar en soumettant un &lt;span class="caps"&gt;HAML&lt;/span&gt; mal&amp;nbsp;intentionné.&lt;/p&gt;
&lt;p&gt;Dans l&amp;#8217;absolu, je ne me sens pas super concerné par &lt;span class="caps"&gt;HAML&lt;/span&gt;, ni par les entrailles de Ruby. Mais le challenge est intéressant : proposer un bac à sable avec les outils modernes servant de base à la&amp;nbsp;conteneurisation.&lt;/p&gt;
&lt;h2 id="depuis-linterieur-de-ruby"&gt;Depuis l&amp;#8217;intérieur de&amp;nbsp;ruby&lt;/h2&gt;
&lt;p&gt;Ruby est un langage interprété capable d&amp;#8217;aller très loin dans les abstractions, les bidouilles et les astuces. Tout est permis, il n&amp;#8217;y a aucune limite pour atteindre l&amp;#8217;objectif sacré : &amp;#8220;à la fin, le code est beau&amp;#8221;, le but ultime du développeur&amp;nbsp;Ruby.&lt;/p&gt;
&lt;p&gt;Vouloir border l&amp;#8217;exécution de code ruby est donc complètement utopiste, voire même&amp;nbsp;insultant.&lt;/p&gt;
&lt;p&gt;Il existe des outils liés à la &lt;a href="https://ruby-hacking-guide.github.io/security.html"&gt;sécurité dans Ruby&lt;/a&gt;, comme la variable $&lt;span class="caps"&gt;SAFE&lt;/span&gt;, mais ça semble être peu pratique, et &lt;a href="https://bugs.ruby-lang.org/issues/8468"&gt;remis en cause dans les prochaines versions de Ruby&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il existe la gem &lt;a href="https://github.com/tario/shikashi"&gt;shikashi&lt;/a&gt; qui permet de faire&amp;nbsp;des &lt;code&gt;eval&lt;/code&gt; avec des listes blanches, mais pour l&amp;#8217;utiliser, il faudrait patcher le code de la gem&amp;nbsp;`haml.&lt;/p&gt;
&lt;p&gt;Il y a visiblement des choses à faire dans Ruby même, mais bon, je n&amp;#8217;ai pas super envie d&amp;#8217;aller farfouiller dans ses entrailles, ni même beaucoup d&amp;#8217;espoir de pouvoir y trouver quelque chose de déterministe ou&amp;nbsp;systématique.&lt;/p&gt;
&lt;h2 id="depuis-lexterieur-de-ruby"&gt;Depuis l&amp;#8217;extérieur de&amp;nbsp;Ruby&lt;/h2&gt;
&lt;p&gt;Docker a réussi à démocratiser la conteneurisation sur Linux, et à amener de la visibilité sur le sujet, beaucoup de&amp;nbsp;visibilité.&lt;/p&gt;
&lt;p&gt;Seul bémol, les conteneurs de Docker ne sont pas pour l&amp;#8217;instant prévus pour isoler du code potentiellement agressif.
Une équipe a été montée pour travailler sur ce sujet, et RedHat s&amp;#8217;y intéresse&amp;nbsp;aussi.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;isolation des process est confiée aux &lt;a href="http://man7.org/linux/man-pages/man7/namespaces.7.html"&gt;namespaces&lt;/a&gt;, une innovation apparue dans le kernel 3.8, il y a maintenant 2 ans, mais qui surtout est officiellement troués jusqu&amp;#8217;à la version 3.14 du kernel. Pour rappel, Trusty, la version courante d&amp;#8217;Ubuntu utilise un kernel&amp;nbsp;3.13.&lt;/p&gt;
&lt;p&gt;Bien conscient du problème, Docker propose d&amp;#8217;utiliser l&amp;#8217;une des deux implémentations vedettes des &lt;span class="caps"&gt;LSM&lt;/span&gt; : Apparmor ou SELinux.
Je n&amp;#8217;ai pas eu l&amp;#8217;occasion de me documenter sur SELinux, et Apparmor a le bon gout d&amp;#8217;être installé par défaut sur Ubuntu depuis des années (et Debian le gère sans soucis).
Pour l&amp;#8217;instant, la documentation de Docker sur la sécurité est spartiate (les sources de &lt;a href="https://github.com/docker/libcontainer"&gt;libcontainer&lt;/a&gt; sont lisibles), et rien ne semble prévu pour demander à restreindre des accés à l&amp;#8217;intérieur même du&amp;nbsp;conteneur.&lt;/p&gt;
&lt;p&gt;Github a réalisé un prototype pour emballer du Ruby générique dans un Docker : &lt;a href="https://github.com/github/hoosegow"&gt;Hoosegow&lt;/a&gt;. Ils donnent tous les liens vers les informations disponibles, mais font le pari de confier la sécurité à&amp;nbsp;Docker.&lt;/p&gt;
&lt;h3 id="decouper-en-tranches"&gt;Découper en&amp;nbsp;tranches&lt;/h3&gt;
&lt;p&gt;Docker, c&amp;#8217;est beaucoup de Kernel, un peu d&amp;#8217;&lt;span class="caps"&gt;API&lt;/span&gt;, et pas mal de choix d&amp;#8217;organisation (type de réseaux, système de fichiers en oignons, chemin des Cgroups …). Pour la partie isolation/coercition, le rôle est confié aux Namespaces et aux Cgroups, le tout emballé par Apparmor. Les Namespaces sont officiellement troués.
Deuxième soucis, Apparmor travaille avec des chemins complets, et Docker utilise un &lt;span class="caps"&gt;UUID&lt;/span&gt; au dernier moment, pour nommer le dossier racine du containers. Il y a donc un souci pour préparer le apparmor avant de pouvoir&amp;nbsp;l&amp;#8217;utiliser.&lt;/p&gt;
&lt;p&gt;Docker&amp;nbsp;fourni &lt;code&gt;nsinit&lt;/code&gt;, un outil en ligne de commande pour coudre à la main des namespaces pour tailler sur mesure son propre conteneur. On ne peut pas tout à fait qualifier cet outil de grand public. Il m&amp;#8217;a résisté. Je suis donc parti sur une solution simple, basée sur Apparmor et Cgroup, pour les Namespaces, on verra plus&amp;nbsp;tard.&lt;/p&gt;
&lt;h3 id="isoler-un-service"&gt;Isoler un&amp;nbsp;service&lt;/h3&gt;
&lt;p&gt;La tactique est simple : isoler le code gérant le haml dans un serveur, et le mettre dans une boite qui interdit un maximum d&amp;#8217;actions possibles&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La boite est en lecture seule, sans réseau &lt;span class="caps"&gt;IP&lt;/span&gt;, sans &lt;a href="http://linux.die.net/man/7/capabilities"&gt;capabilites&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Seuls les fichiers nécessaires pour lancer le code ruby sont&amp;nbsp;lisibles.&lt;/li&gt;
&lt;li&gt;Le serveur va communiquer via une socket &lt;span class="caps"&gt;UNIX&lt;/span&gt;, seul élément accessible en&amp;nbsp;écriture.&lt;/li&gt;
&lt;li&gt;Le protocole n&amp;#8217;utilise aucune sérialisation, juste des &lt;a href="http://en.wikipedia.org/wiki/String_%28computer_science%29"&gt;Pascal strings&lt;/a&gt; : une taille sur 4 octets, suivi du&amp;nbsp;message.&lt;/li&gt;
&lt;li&gt;Le client gère un timeout pour ne pas attendre un serveur&amp;nbsp;bloqué.&lt;/li&gt;
&lt;li&gt;Cgroup permet de limiter la mémoire, et la ration de &lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;disponibles.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="bundler"&gt;Bundler&lt;/h4&gt;
&lt;p&gt;Bundler est utilisé pour installer les bibliothèques nécessaires de manière propre et déterministe. Le serveur sera lancé avec&amp;nbsp;un &lt;code&gt;-I&lt;/code&gt; pour ne pas devoir embarquer tout bundler dans Apparmor : pas&amp;nbsp;de &lt;code&gt;bundle exec&lt;/code&gt;. L&amp;#8217;application a un shebang et un chemin en dur&amp;nbsp;: &lt;code&gt;/opt/box/box&lt;/code&gt; pour permettre l&amp;#8217;interception par le démon de&amp;nbsp;Apparmor.&lt;/p&gt;
&lt;h4 id="apparmor"&gt;Apparmor&lt;/h4&gt;
&lt;p&gt;La règle Apparmor est créée en ajoutant les fichiers nécessaires un par un, jusqu&amp;#8217;à ce que le serveur se lance, puis que le client puisse lui parler.
C&amp;#8217;est un peu laborieux, mais je suis moyennement convaincu par l&amp;#8217;outillage actuel, avec l&amp;#8217;activation d&amp;#8217;un audit et surveillance automatique des logs.
On parle de 50 lignes de serveurs utilisant 1 bibliothèque et de 40 lignes de clients, rien de comparable avec la moindre application&amp;nbsp;Rails.&lt;/p&gt;
&lt;h4 id="eye"&gt;Eye&lt;/h4&gt;
&lt;p&gt;L&amp;#8217;application bénéficie d&amp;#8217;un superviseur, &lt;a href="https://github.com/kostya/eye"&gt;Eye&lt;/a&gt;,qui se charge de lancer et relancer le service en cas d&amp;#8217;incident. Pour l&amp;#8217;instant, seul le service est isolé par&amp;nbsp;Apparmor.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Eye&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/var/log/sandbox/eye.log&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Eye&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sandbox&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/var/run/box&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:flapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;within&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;retry_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;stdall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;trash.log&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# stdout,err logs for processes by default&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# eye daemonized process&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:box&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pid_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;box.pid&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# pid_path will be expanded with the working_dir&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;start_command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/usr/bin/ruby -I /opt/box/vendor/bundle/ruby/1.9.1/gems/ /opt/box/box&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;daemonize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="cgroups"&gt;Cgroups&lt;/h4&gt;
&lt;p&gt;Un utilisateur est créé, pour profiter des droits &lt;span class="caps"&gt;UNIX&lt;/span&gt; classiques, et surtout pour pouvoir y accrocher des Cgroups. Il n&amp;#8217;est pas nécessaire de dégainer des outils de haut niveau pour ça, Cgroups se configure avec de simples fichiers montés dans un point de montage de&amp;nbsp;type &lt;code&gt;cgroup&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="initd"&gt;Init.d&lt;/h4&gt;
&lt;p&gt;Le script init.d est laid comme tous les scripts init.d mais comme il est tombé en marche au bout de 10 minutes alors que la version upstart m&amp;#8217;a résisté pendant tout le weekend, je ne critiquerai pas. L&amp;#8217;abandon d&amp;#8217;Upstart dans la prochaine version &lt;span class="caps"&gt;LTS&lt;/span&gt; d&amp;#8217;Ubuntu est par contre une très bonne nouvelle. J&amp;#8217;ai rarement vu un produit aussi pénible et incapable de dire où il a mal, puis qui se bloque au bout d&amp;#8217;un moment, avec le reboot comme seule issue. La doc est pleine de promesses d&amp;#8217;améliorations pour la prochaine release que personne ne verra&amp;nbsp;jamais.&lt;/p&gt;
&lt;p&gt;Le superviseur et le service tournent avec un utilisateur non privilégié lancé par&amp;nbsp;un &lt;code&gt;start-stop-daemon&lt;/code&gt;, init.d se charge de préparer le terrain puis d&amp;#8217;attacher le service dans un&amp;nbsp;cgroup.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;do_pre_start&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/run/box
&lt;span class="w"&gt;    &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;/run/box

&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/var/log/sandbox
&lt;span class="w"&gt;    &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;/var/log/sandbox

&lt;span class="w"&gt;    &lt;/span&gt;cgcreate&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;memory,cpu:box
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# 25%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/sys/fs/cgroup/cpu/box/cpu.shares
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# 32Mo&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/sys/fs/cgroup/memory/box/memory.limit_in_bytes

&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/opt/box/.eye
&lt;span class="w"&gt;    &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;/opt/box/.eye
&lt;span class="o"&gt;}&lt;/span&gt;

do_post_start&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Yes, this is ugly, it should be a loop&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;cgclassify&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;memory,cpu:box&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/run/box/box.pid&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On retrouve la logique de poupées gigognes (les &lt;a href="http://fr.wikipedia.org/wiki/Poupée_russe"&gt;matriochkas&lt;/a&gt;), avec une séparation claire des responsabilités. L&amp;#8217;init crée le contexte puis lance un superviseur, qui lance un service dans un contexte rigoriste. Ruby n&amp;#8217;est jamais lancé avec&amp;nbsp;l&amp;#8217;utilisateur &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Je n&amp;#8217;ai aucune idée du niveau de sécurité qu&amp;#8217;amène cette suite de bonne pratique, mais on est clairement au-dessus de pas mal de sites&amp;nbsp;web.&lt;/p&gt;
&lt;h3 id="ameliorer"&gt;Améliorer&lt;/h3&gt;
&lt;p&gt;Par principe, il faudrait mettre la boite dans une boite de virtualisation, utiliser un kernel patché avec GRSec, ajouté du log&amp;nbsp;(&lt;code&gt;auditd&lt;/code&gt; et &lt;code&gt;tcpspy&lt;/code&gt;
) vers une machine distante, et attendre un drame pour ensuite&amp;nbsp;corriger.&lt;/p&gt;
&lt;h3 id="le-code"&gt;Le&amp;nbsp;code&lt;/h3&gt;
&lt;p&gt;Le code est disponible sur github, avec une jolie licence &lt;span class="caps"&gt;LGPL&lt;/span&gt; : &lt;a href="https://github.com/athoune/sandbox"&gt;Sandbox&lt;/a&gt;&lt;/p&gt;</content><category term="Ops"></category><category term="container"></category><category term="apparmor"></category><category term="cgroups"></category><category term="ruby"></category><category term="eye"></category></entry><entry><title>L’échec de l’élastique comme un service</title><link href="http://blog.garambrogne.net/echec-elastique-comme-un-service.html" rel="alternate"></link><published>2015-04-05T19:35:00+02:00</published><updated>2015-04-05T19:35:00+02:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-04-05:/echec-elastique-comme-un-service.html</id><summary type="html">&lt;p&gt;L&amp;#8217;échec de l&amp;#8217;élastique comme un service (et son&amp;nbsp;renouveau)&lt;/p&gt;</summary><content type="html">&lt;h1 id="lechec-de-lelastique-comme-un-service-et-son-renouveau"&gt;L&amp;#8217;échec de l&amp;#8217;élastique comme un service (et son&amp;nbsp;renouveau)&lt;/h1&gt;
&lt;h2 id="le-cloud-est-elastique"&gt;Le Cloud est&amp;nbsp;élastique&lt;/h2&gt;
&lt;p&gt;Le Cloud n&amp;#8217;a qu&amp;#8217;une seule fonction intéressante : l&amp;#8217;élasticité.
Tout le reste n&amp;#8217;est que blabla commercial ou confusion sémantique.
Attention, je ne suis pas nostalgique du serveur cousu main, à l&amp;#8217;ancienne.
Les offres d&amp;#8217;hébergement sont en constante évolution et mettent à disposition plein de belles choses, mais ce ne sera que rarement du&amp;nbsp;Cloud.&lt;/p&gt;
&lt;p&gt;La définition du Cloud est pourtant simple : &lt;em&gt;Mise à disposition &lt;span class="caps"&gt;DYNAMIQUE&lt;/span&gt; de ressources via une &lt;span class="caps"&gt;API&lt;/span&gt; distante&lt;/em&gt;.
Même &lt;a href="http://fr.wikipedia.org/wiki/Cloud_computing"&gt;Wikipedia le dit&lt;/a&gt;, enfin presque.
Je peux commander des ressources, et les utiliser dans les minutes qui suivent, en demander plus, puis les diminuer un peu plus&amp;nbsp;tard.&lt;/p&gt;
&lt;p&gt;Ces ressources peuvent être matérielles (bien que virtualisées) comme de la puissance de calcul ou du stockage, mais aussi de plus haut niveau, des&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Le Cloud sait mettre à disposition des ressources de bas niveaux : &lt;span class="caps"&gt;CPU&lt;/span&gt;, &lt;span class="caps"&gt;RAM&lt;/span&gt;, réseau, disque… depuis maintenant presque 10 ans, grâce à la&amp;nbsp;virtualisation.&lt;/p&gt;
&lt;p&gt;Mais la virtualisation est beaucoup plus à l&amp;#8217;aise pour dimensionner une machine à froid.
L&amp;#8217;élasticité, ce serait pouvoir redimensionner certaines ressources à chaud, sans redémarrer. Techniquement, des choses sont faisables, mais il est très difficile de mettre à disposition un pool de ressources et de piocher dedans, l&amp;#8217;unité de base reste la machine&amp;nbsp;physique.&lt;/p&gt;
&lt;p&gt;Il est beaucoup plus simple de travailler avec plusieurs machines (réelles ou non), et d&amp;#8217;en ajouter ou d&amp;#8217;en enlever, puis de demander à la partie applicative de gérer cette fluctuation en débranchant et rebranchant les différents&amp;nbsp;éléments.&lt;/p&gt;
&lt;h2 id="le-web-est-elastique"&gt;Le web est&amp;nbsp;élastique&lt;/h2&gt;
&lt;p&gt;Le web se prête bien aux architectures distribuées dynamiques, avec son protocole sans état (en&amp;nbsp;théorie).&lt;/p&gt;
&lt;p&gt;La méthode immuable pour traiter les requêtes web est celle de l&amp;#8217;aiguillage qui va demander le traitement de la demande a des workers.
Les workers peuvent être des process ou des threads sur une même machine, eux-mêmes répartis sur plusieurs serveurs.
&lt;a href="http://www.haproxy.org"&gt;HAproxy&lt;/a&gt; ou Nginx (plus faiblard), sont capable de répartir la charge entre plusieurs&amp;nbsp;machines.&lt;/p&gt;
&lt;p&gt;Le load-balancer devient une pièce maitresse des applications distribuées. Nginx évolue rapidement (principalement sur sa branche privatrice) pour reprendre sa place de proxy universelle, HAproxy va intégrer Lua, et des proxy applicatifs apparaissent, comme le maintenant historique &lt;a href="https://github.com/hipache/hipache"&gt;Hipache&lt;/a&gt; ou le prometteur &lt;a href="http://www.vulcanproxy.com"&gt;Vulcan&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dire que le protocole &lt;span class="caps"&gt;HTTP&lt;/span&gt; est sans état est un mensonge, enfin, un raccourci. La notion de cookie puis celle de session sont apparues très vite, pour conserver l&amp;#8217;état d&amp;#8217;un client sur une période plus ou moins longue. Dans un environnement distribué, il faut pouvoir partager un état entre les workers, rôle traditionnel de &lt;a href="http://memcached.org/"&gt;Memcache&lt;/a&gt;, challengé par &lt;a href="http://redis.io"&gt;Redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;De la même manière, il faut pouvoir gérer les uploads de fichiers de manière centralisée avec S3 et ses clones, ou le classique &lt;span class="caps"&gt;NFS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Pour la persistance, les inébranlables bases de données relationnelles ont encore largement leur place, challengée par la vague du NoSQL, plus ou moins fiable, mais tellement plus adapté au&amp;nbsp;redimensionnement.&lt;/p&gt;
&lt;p&gt;Pour ceux qui ont encore faim, il est possible de gérer tout ça de manière&amp;nbsp;dynamique.&lt;/p&gt;
&lt;p&gt;Tout ça pour atteindre le Graal de&amp;nbsp;l&amp;#8217;élasticité.&lt;/p&gt;
&lt;h2 id="elastique-comme-un-service"&gt;Élastique comme un&amp;nbsp;service&lt;/h2&gt;
&lt;p&gt;La complexité est quand même impressionnante pour un besoin que beaucoup de gens n&amp;#8217;auront&amp;nbsp;jamais.&lt;/p&gt;
&lt;p&gt;Il y a eu une première vague de réponses pour simplifier l&amp;#8217;accès à l&amp;#8217;élastique : &lt;a href="https://cloud.google.com/appengine/docs"&gt;Google App Engine&lt;/a&gt;, puis &lt;a href="http://aws.amazon.com/fr/elasticbeanstalk/"&gt;Elastic Beanstalk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Deux produits contraignants, qui, bien qu&amp;#8217;utilisant des langages libres, enferment l&amp;#8217;utilisateur dans une solution&amp;nbsp;propriétaire.&lt;/p&gt;
&lt;p&gt;Ces deux produits ont causé beaucoup de drames : des langages arbitrairement périmés, un environnement de dev ne ressemblant que très peu à la prod, des performances aléatoires, et surtout l&amp;#8217;impossibilité d&amp;#8217;utiliser des frameworks et des applications largement utilisées dans des environnements classiques. Une vraie&amp;nbsp;catastrophe.&lt;/p&gt;
&lt;p&gt;Et tout ça pour se faire siphonner sa carte bleue, si le site a la mauvaise idée de faire une belle audience. Le plafond de cout étant apparu sur le tard : &amp;#8220;Attention, nous, on scale, mais pas ta carte&amp;nbsp;bleue&amp;#8221;.&lt;/p&gt;
&lt;h2 id="le-conteneur-est-elastique"&gt;Le conteneur est&amp;nbsp;élastique&lt;/h2&gt;
&lt;p&gt;Donc l&amp;#8217;approche bas-niveau ne suffit pas, et les approches de trop haut niveau sont encore pires. Il faut donc voir ce qui est faisable avec une approche plus&amp;nbsp;modérée.&lt;/p&gt;
&lt;p&gt;Les conteneurs, qui ne sont ni de la virtualisation, ni de l&amp;#8217;applicatif, permettent d&amp;#8217;embarquer une application avec tout ce dont elle a besoin, et de la lancer avec quelques paramètres dans un environnement maitrisé (accès aux ressources physiques, isolation). Le Cloud devient un simple pool de ressources dans lequel on va piocher en déployant ou déplaçant un conteneur là où il y a de la&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;Le conteneur peut être considéré comme &lt;a href="https://fr.wiktionary.org/wiki/immutable"&gt;immutable&lt;/a&gt; (en lecture seule), et il confiera ses besoins d&amp;#8217;&lt;span class="caps"&gt;IO&lt;/span&gt; à des services réseau, ou à un point de montage avec un système de fichier. Le réseau est clairement plus souple, mais des systèmes de fichiers modernes permettent de faire de belles choses (comme &lt;span class="caps"&gt;ZFS&lt;/span&gt; avec &lt;a href="https://docs.clusterhq.com/en/0.3.2/advanced/clustering.html#minimal-downtime-volume-migration"&gt;le double push de Flocker&lt;/a&gt;). &lt;/p&gt;
&lt;p&gt;On a donc une application tout ce qu&amp;#8217;il y a de plus classique, en &lt;span class="caps"&gt;PHP&lt;/span&gt;, Python ou je ne sais quoi, qui va se retrouver sur un Linux en lecture seul (ou presque), et dont on va pouvoir multiplier les instances. Se passer du stockage local est intégré dans beaucoup de framework maintenant&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://django-storages.readthedocs.org/en/latest/index.html"&gt;Django&amp;nbsp;Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carrierwaveuploader/carrierwave"&gt;Carrierwave&lt;/a&gt;, &lt;a href="https://github.com/refile/refile"&gt;refile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KnpLabs/Gaufrette"&gt;Gaufrette&lt;/a&gt;, &lt;a href="http://flysystem.thephpleague.com/"&gt;Flysystem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour le reste, il suffit de brancher une poignée de services (session, caches, base de données…) et avoir une première étape de scaling sans&amp;nbsp;douleur.&lt;/p&gt;
&lt;p&gt;La seconde étape arrive quand la base de données commence à crier, et il va falloir négocier pour aller au-delà du combo index/cache/tuning pour avancer.
Mais bon, cette étape arrive lorsque l&amp;#8217;on a un joli trafic, et il est toujours possible de négocier en force en ajoutant un esclave plus gros (plus de coeurs, plus de &lt;span class="caps"&gt;RAM&lt;/span&gt;, plus de &lt;span class="caps"&gt;SSD&lt;/span&gt;…), et de le promouvoir, pour faire du scaling&amp;nbsp;vertical.&lt;/p&gt;
&lt;p&gt;Scaler la couche applicative en utilisant des conteneurs est la voie dans laquelle s&amp;#8217;engagent (à fond) les gros du Cloud, avec des solutions ouvertes ou non&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://kubernetes.io/"&gt;Kubernetes&lt;/a&gt;&amp;nbsp;(Google)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://aws.amazon.com/ecs/"&gt;&lt;span class="caps"&gt;ECS&lt;/span&gt;&lt;/a&gt;&amp;nbsp;(Amazon)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.joyent.com/about/press/joyent-launches-triton-elastic-container-infrastructure"&gt;Triton&lt;/a&gt;&amp;nbsp;(Joyent)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://mesos.apache.org/"&gt;Mesos&lt;/a&gt; (&lt;span class="caps"&gt;UC&lt;/span&gt; Berkeley et&amp;nbsp;Twitter)&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kubernetes, qui a le bon gout d&amp;#8217;être libre, déchaine l&amp;#8217;enthousiasme.
Red Hat se raccroche aux branches et profites de Kubernetes pour faire basculer vers les conteneurs son &lt;a href="http://www.openshift.org/"&gt;Open Shift&lt;/a&gt; pour la version3 en&amp;nbsp;préparation.&lt;/p&gt;
&lt;p&gt;Les conteneurs posent encore beaucoup de questions, et des startups proposent des réponses, comme Flocker de &lt;a href="https://clusterhq.com/"&gt;ClusterHQ&lt;/a&gt; pour avoir de la persistance qui peut migrer.
&lt;a href="https://coreos.com/"&gt;CoreOS&lt;/a&gt; continue de taper tous azimuts.
&lt;a href="https://hashicorp.com/"&gt;HashiCorp&lt;/a&gt; se contentait d&amp;#8217;une gestion opportuniste des conteneurs, mais c&amp;#8217;est en train de bouger. &lt;a href="https://terraform.io/"&gt;Terraform&lt;/a&gt; dans sa &lt;a href="https://hashicorp.com/blog/terraform-0-4.html"&gt;version 0.4&lt;/a&gt; gère Docker, et j&amp;#8217;ai hâte de voir comment &lt;a href="https://www.consul.io/"&gt;Consul&lt;/a&gt; et les autres outils vont&amp;nbsp;s&amp;#8217;adapter.&lt;/p&gt;
&lt;p&gt;La fusion du Cloud et des conteneurs est en train de se faire, là, maintenant. Il y aura des nouveautés, et des morts. Cette fusion amène de nouvelles réponses, et tout autant de questions. Rendre possible l&amp;#8217;élasticité de la partie applicative fait partie des réponses apportées par la combinaison du Cloud et des&amp;nbsp;conteneurs.&lt;/p&gt;</content><category term="Ops"></category><category term="Cloud"></category><category term="elastic"></category></entry><entry><title>La cuisson avec Docker</title><link href="http://blog.garambrogne.net/la-cuisson-avec-docker.html" rel="alternate"></link><published>2015-03-19T22:40:00+01:00</published><updated>2015-03-19T22:40:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-03-19:/la-cuisson-avec-docker.html</id><summary type="html">&lt;p&gt;Préparer une application avec Docker avant sa mise en&amp;nbsp;production&lt;/p&gt;</summary><content type="html">&lt;h1 id="preparer-une-application"&gt;Préparer une&amp;nbsp;application&lt;/h1&gt;
&lt;p&gt;Même si l&amp;#8217;on code massivement avec des langages de script pour ne pas passer par la case compilation, il y a forcément une étape de préparation avant de pouvoir livrer une application à partir de ses sources : télécharger les modules et compiler les potentiels binding en C, mais surtout gérer tout le bazar lié aux assets : JavaScript et &lt;span class="caps"&gt;CSS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Cette étape de préparation avant déploiement porte en anglais le nom poétique de &lt;em&gt;baking&lt;/em&gt; : on cuit le pain avant de le&amp;nbsp;livrer.&lt;/p&gt;
&lt;p&gt;Il n&amp;#8217;y a aucun intérêt à réaliser cette étape sur le serveur de production qui a surement mieux à faire : c&amp;#8217;est long, ça requière des outils plus ou moins louches, et si l&amp;#8217;application est distribuée, il faut recommencer sur chaque&amp;nbsp;instance.&lt;/p&gt;
&lt;h1 id="preparer-isso"&gt;Préparer&amp;nbsp;Isso&lt;/h1&gt;
&lt;p&gt;Voici un exemple commenté de préparation et déploiement de &lt;a href="http://posativ.org/isso/"&gt;Isso&lt;/a&gt;, un clone libre et local de Disqus, qui permet d&amp;#8217;embarquer des commentaires depuis du JavaScript, sur un site qui peut même être statique.
Le serveur est en Python, et il fournit du JavaScript à embarquer dans ses pages &lt;span class="caps"&gt;HTML&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Pour ne pas pourrir ma machine en installant des outils bizarres, j&amp;#8217;utilise des containers &lt;a href="http://docker.com"&gt;Docker&lt;/a&gt; à usage unique.
Pour avoir un build sans trop de répétitions, j&amp;#8217;utilise un simple Makefile. Oui, Makefile est vieux et moche, mais pas plus que bash, et il est un standard de fait. Pour des taches de tailles raisonnables, il reste tout à fait&amp;nbsp;pertinent.&lt;/p&gt;
&lt;p&gt;En montant un dossier local dans le container, le résultat du traitement se retrouvera dans ce dossier. La source et le résultat sont locaux, le traitement&amp;nbsp;distant.&lt;/p&gt;
&lt;p&gt;Cerise sur le gâteau, grâce à &lt;a href="http://boot2docker.io"&gt;boot2docker&lt;/a&gt;,l&amp;#8217;action lancée depuis un Mac, va faire travailler un Linux, et surtout, produire du contenu pour&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Pour éviter toutes surprises, je n&amp;#8217;utilise que des images officielles de Docker, forcément basé sur Debian (et Wheezy, si&amp;nbsp;possible).&lt;/p&gt;
&lt;h2 id="cuire-le-python"&gt;Cuire le&amp;nbsp;Python&lt;/h2&gt;
&lt;p&gt;Isso est packagé sur Pypi : il s&amp;#8217;installe simplement avec pip.
Je sais installer python, mais comme la vie est courte, autant utiliser &lt;a href="https://registry.hub.docker.com/_/python/"&gt;python:2-wheezy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Makefile pense à l&amp;#8217;envers, en pensant prérequis et dépendances.
Pour avoir l&amp;#8217;application Isso, j&amp;#8217;ai besoin d&amp;#8217;un python dans un virtualenv, qui a besoin d&amp;#8217;un dossier app. Ce qui donne&amp;nbsp;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;app/bin/isso&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--volume&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/app:/app&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--workdir&lt;span class="o"&gt;=&lt;/span&gt;/app&lt;span class="w"&gt; &lt;/span&gt;python:2-wheezy&lt;span class="w"&gt; &lt;/span&gt;/app/bin/pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;isso

&lt;span class="nf"&gt;app/bin/python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--volume&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/app:/app&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--workdir&lt;span class="o"&gt;=&lt;/span&gt;/app&lt;span class="w"&gt; &lt;/span&gt;python:2-wheezy&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;.

&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Comme je suis radin, j&amp;#8217;efface (&amp;#8212;rm) le container dès qu&amp;#8217;il a exécuté son&amp;nbsp;action.&lt;/p&gt;
&lt;h2 id="cuire-le-javascript"&gt;Cuire le&amp;nbsp;JavaScript&lt;/h2&gt;
&lt;p&gt;Le JavaScript, de nos jours, utilise plus de bazars que du C++ pour être utilisable. Isso est sobre, il se contente de &lt;a href="http://bower.io"&gt;Bower&lt;/a&gt; pour aller chercher les bibliothèques et leurs dépendances, &lt;a href="https://github.com/mishoo/UglifyJS"&gt;Uglify&lt;/a&gt; pour compacter, et on échappe à &lt;a href="http://gruntjs.com"&gt;Grunt&lt;/a&gt;, un Makefile est utilisé à la&amp;nbsp;place.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;/&lt;span class="n"&gt;isso&lt;/span&gt;/&lt;span class="n"&gt;js&lt;/span&gt;/&lt;span class="n"&gt;embed&lt;/span&gt;.&lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--volume&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/src:/src&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--workdir&lt;span class="o"&gt;=&lt;/span&gt;/src&lt;span class="w"&gt; &lt;/span&gt;node:wheezy&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;npm install -g bower requirejs jade &amp;amp;&amp;amp; make init &amp;amp;&amp;amp; make js&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Your minified javascript files are here:&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;src/isso/js&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;*.min.js

&lt;span class="nf"&gt;src/isso/js/embed.js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/posativ/isso.git&lt;span class="w"&gt; &lt;/span&gt;src/

&lt;span class="nf"&gt;src&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Même tactique, une image jetable avec nodejs : &lt;a href="https://registry.hub.docker.com/_/node/"&gt;node:wheezy&lt;/a&gt; est utilisée. Petite astuce, &amp;#8220;docker run&amp;#8221; n&amp;#8217;accepte qu&amp;#8217;une seule commande en argument, du coup, il faut passer par&amp;nbsp;un &lt;code&gt;sh -c 'pim &amp;amp;&amp;amp; pam &amp;amp;&amp;amp; poum'&lt;/code&gt;&lt;/p&gt;
&lt;h1 id="livrer-le-gateau"&gt;Livrer le&amp;nbsp;gâteau&lt;/h1&gt;
&lt;p&gt;La cuisson s&amp;#8217;est faite dans des containers, mais le résultat est à chaque fois un simple dossier, tout ce qu&amp;#8217;il y a de plus&amp;nbsp;traditionnel.&lt;/p&gt;
&lt;p&gt;Il est possible de déployer l&amp;#8217;application ainsi, avec&amp;nbsp;un &lt;code&gt;rsync&lt;/code&gt; et un &lt;a href="http://supervisord.org"&gt;supervisor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;J&amp;#8217;ai fait le choix de la déployer avec Docker. Le Dockerfile utilisé est&amp;nbsp;minimaliste.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:2-wheezy&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;/app

&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/conf&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;1234&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/app/bin/isso&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/conf/isso.conf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;run&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le dossier contenant le fichier de configuration est réclamé, par contre, l&amp;#8217;application est&amp;nbsp;embarquée.&lt;/p&gt;
&lt;p&gt;Pour configurer tout ça, un &lt;em&gt;docker-compose.yml&lt;/em&gt; qui va utiliser le &lt;em&gt;Dockerfile&lt;/em&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;isso&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Dans le même dossier&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Aucun privilège&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cap_drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ALL&lt;/span&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 class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1234:1234&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;conf:/conf&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Pour la configuration&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;data:/data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Pour la base de données&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La configuration isso est tout aussi&amp;nbsp;simple&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[general]&lt;/span&gt;
&lt;span class="na"&gt;dbpath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/data/comments.db&lt;/span&gt;
&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://example.tld/&lt;/span&gt;
&lt;span class="k"&gt;[server]&lt;/span&gt;
&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://0.0.0.0:1234/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le dossier &lt;em&gt;data&lt;/em&gt; monté dans compose est utilisé pour accueillir la base sqlite, et le serveur écoute bien le port&amp;nbsp;1234.&lt;/p&gt;
&lt;p&gt;Il faut ensuite tricoter les urls pour avoir le site web et&amp;nbsp;Isso.&lt;/p&gt;
&lt;p&gt;Le site en production n&amp;#8217;aura aucune séquelle de son installation, pas de nodejs, pas de choses qui trainent, juste une Wheezy spartiate et une&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;Tout le &lt;a href="https://github.com/athoune/isso-docker"&gt;code est disponible sur github&lt;/a&gt;, sous Licence &lt;span class="caps"&gt;BSD&lt;/span&gt;.&lt;/p&gt;</content><category term="Ops"></category><category term="container"></category><category term="Docker"></category></entry><entry><title>Plus de machines, des services</title><link href="http://blog.garambrogne.net/plus-de-machines-des-services.html" rel="alternate"></link><published>2015-03-19T22:40:00+01:00</published><updated>2015-03-19T22:40:00+01:00</updated><author><name>Mathieu Lecarme</name></author><id>tag:blog.garambrogne.net,2015-03-19:/plus-de-machines-des-services.html</id><summary type="html">&lt;p&gt;La virtualisation n&amp;#8217;était qu&amp;#8217;une&amp;nbsp;étape.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La virtualisation n&amp;#8217;était qu&amp;#8217;une&amp;nbsp;étape.&lt;/p&gt;
&lt;h1 id="le-besoin-de-virtualisation"&gt;Le besoin de&amp;nbsp;virtualisation&lt;/h1&gt;
&lt;p&gt;La virtualisation est apparue pour en finir avec l&amp;#8217;ajout sans cesse de machines dans les salles serveur.
Pour pouvoir garantir de la qualité, limiter la portée des incidents, permettre des cycles de vies différents, il est important de pouvoir isoler les services.
La virtualisation permet de découper des serveurs physiques.
En regroupant des services sur un serveur plus gros, on gagne en efficacité, en consommation d&amp;#8217;énergie, et en souplesse pour réorganiser l&amp;#8217;ensemble (il est possible de migrer une machine virtuelle sur une autre machine&amp;nbsp;physique).&lt;/p&gt;
&lt;h1 id="abstraction-materielle"&gt;Abstraction&amp;nbsp;matérielle&lt;/h1&gt;
&lt;p&gt;Cette nouvelle couche d&amp;#8217;abstraction n&amp;#8217;a pas été indolore, mais des optimisations sont arrivées rapidement.
Les processeurs se sont adaptés en rajoutant des jeux d&amp;#8217;instructions spécifiques (Intel &lt;span class="caps"&gt;VT&lt;/span&gt; et &lt;span class="caps"&gt;AMD&lt;/span&gt;-V).
L&amp;#8217;augmentation du nombre de coeurs au sein d&amp;#8217;une même puce, et l&amp;#8217;augmentation constante de la quantité de &lt;span class="caps"&gt;RAM&lt;/span&gt; disponible permet de passer en force.
La virtualisation pure et dure est rapidement passée à la paravitualisation, l&amp;#8217;&lt;span class="caps"&gt;OS&lt;/span&gt; invité devant faire quelques efforts d&amp;#8217;adaptation en échange d&amp;#8217;un gain en performance&amp;nbsp;important.&lt;/p&gt;
&lt;p&gt;Techniquement, il est possible de mélanger les &lt;span class="caps"&gt;OS&lt;/span&gt; (Linux, Windows, FreeBSD…), mais rester sur du tout Linux, en se contentant de mélanger les versions ou les distributions, simplifie beaucoup de&amp;nbsp;choses.&lt;/p&gt;
&lt;p&gt;Il est possible de redimensionner ou de déplacer une &lt;span class="caps"&gt;VM&lt;/span&gt;, mais concrètement peu d&amp;#8217;hébergeurs le proposent, à part &lt;a href="https://www.gandi.net/hebergement/serveur/livescaling"&gt;Gandi Cloud&lt;/a&gt;, peut-être. Le redimensionnement à froid est plus simple, et souvent, la migration vers une &lt;span class="caps"&gt;VM&lt;/span&gt; plus grosse est&amp;nbsp;préconisée.&lt;/p&gt;
&lt;h1 id="augmenter-la-densite"&gt;Augmenter la&amp;nbsp;densité&lt;/h1&gt;
&lt;p&gt;Un serveur physique met à disposition un certain nombre de ressources (Processeur, &lt;span class="caps"&gt;RAM&lt;/span&gt;, réseau, stockage…). La virtualisation permet d&amp;#8217;en mutualiser certaines (&lt;a href="http://docs.openstack.org/openstack-ops/content/compute_nodes.html de 16:1 pour les CPUs, avec une option plus sage à 4:1"&gt;Openstack parle d&amp;#8217;un ratio&lt;/a&gt;, pour la &lt;span class="caps"&gt;RAM&lt;/span&gt;, on peut monter péniblement à 1.5:1, en mettant en commun des plages de mémoire. Dans les cas les plus courant, la mémoire est partagée à 1:1, et ce sont les accès disques qui sont bloquants. Un ordonnanceur essaye de répartir équitablement les ressources, mais le phénomène du voisin bruyant est bien connu des utilisateurs d&amp;#8217;&lt;span class="caps"&gt;AWS&lt;/span&gt;.&lt;/p&gt;
&lt;h1 id="la-plus-petite-tranche-possible"&gt;La plus petite tranche&amp;nbsp;possible&lt;/h1&gt;
&lt;p&gt;Pour pouvoir profiter d&amp;#8217;un minimum de parallélisme, il faut au moins avoir 2 CPUs dans une &lt;span class="caps"&gt;VM&lt;/span&gt;. Le ratio &lt;span class="caps"&gt;RAM&lt;/span&gt;/&lt;span class="caps"&gt;CPU&lt;/span&gt; et de 1:1 pour les offres basses (&lt;span class="caps"&gt;VPS&lt;/span&gt; &lt;span class="caps"&gt;OVH&lt;/span&gt;, Digital Ocean par exemple) pour monter à 3.75:1 pour de l&amp;#8217;Amazon Web Service et Google Cloud, et grimper autour de 7:1 pour des VMs spécialisées.
Ce qui fait quand même presque 8Go de &lt;span class="caps"&gt;RAM&lt;/span&gt; comme unité de base.
Quand on est plus petit que Netflix ou Twitter, ça fait quand même une grosse tranche, surtout si l&amp;#8217;on veut doubler le nombre de machine pour avoir de la&amp;nbsp;redondance.&lt;/p&gt;
&lt;h1 id="decouper-differemment"&gt;Découper&amp;nbsp;différemment&lt;/h1&gt;
&lt;p&gt;Les machines virtuelles sont une bonne réponse, mais quelle est la question,&amp;nbsp;déjà?&lt;/p&gt;
&lt;p&gt;Pouvoir isoler des&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Les besoins d&amp;#8217;isolations sont finalement divers et négociables&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Répartir au plus juste la consommation des ressources (&lt;span class="caps"&gt;CPU&lt;/span&gt;, &lt;span class="caps"&gt;RAM&lt;/span&gt;, stockage,&amp;nbsp;réseau).&lt;/li&gt;
&lt;li&gt;Isoler les différents utilisateurs qui vont utiliser des ressources physiques communes (&lt;span class="caps"&gt;CPU&lt;/span&gt;, &lt;span class="caps"&gt;RAM&lt;/span&gt;, stockage,&amp;nbsp;réseau)&lt;/li&gt;
&lt;li&gt;Réstreindre les capacités des utilisateurs, pour limiter les possibilités de faire des bêtises, et donc les surfaces&amp;nbsp;d&amp;#8217;attaques.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces différentes possibilités ont été explorées, en patchant directement le noyau Linux. &lt;a href="http://linux-vserver.org/Overview"&gt;Vserver&lt;/a&gt; et &lt;a href="https://openvz.org/Main_Page"&gt;OpenVZ&lt;/a&gt; pour le confinement, &lt;a href="https://grsecurity.net"&gt;GRSec&lt;/a&gt; pour la&amp;nbsp;sécurité.&lt;/p&gt;
&lt;p&gt;Ces pistes, prometteuses, mais intrusives, ont permis la création de nouveaux modules dans le noyau Linux, permettant ensuite la création de produits, sans avoir besoin d&amp;#8217;un noyau&amp;nbsp;patché.&lt;/p&gt;
&lt;p&gt;Les &lt;a href="http://linux.die.net/man/7/capabilities"&gt;capabilities&lt;/a&gt; définissent les actions privilégiées que peut faire un process.
Pour isoler des processus, il y a maintenant les &lt;a href="http://man7.org/linux/man-pages/man7/namespaces.7.html"&gt;namespaces&lt;/a&gt;, pour répartir les ressources, Google à offert les &lt;a href="https://www.kernel.org/doc/Documentation/cgroups/"&gt;CGroups&lt;/a&gt;, pour la sécurité, les &lt;a href="http://en.wikipedia.org/wiki/Linux_Security_Modules"&gt;Linux Security Modules&lt;/a&gt; permettent avec quelques polémiques de brancher &lt;a href="http://wiki.apparmor.net/index.php/Main_Page"&gt;Apparmor&lt;/a&gt;, &lt;a href="http://selinuxproject.org/page/Main_Page"&gt;SELinux&lt;/a&gt; (cadeau de la &lt;span class="caps"&gt;NSA&lt;/span&gt;) ou des choses plus exotiques comme &lt;a href="http://tomoyo.sourceforge.jp/documentation.html.en"&gt;Tomyo&lt;/a&gt; ou &lt;a href="http://en.wikipedia.org/wiki/Smack_(software)"&gt;Smack&lt;/a&gt;. &lt;a href="https://code.google.com/p/seccompsandbox/wiki/overview"&gt;Seccomp&lt;/a&gt; (issu de Google Chrome) permet de créer des règles de sandboxing (par thread) en &lt;a href="http://en.wikipedia.org/wiki/Berkeley_Packet_Filter"&gt;&lt;span class="caps"&gt;BPF&lt;/span&gt;&lt;/a&gt;. &lt;span class="caps"&gt;BPF&lt;/span&gt; est la toute nouvelle machine virtuelle universelle pour Linux qui permet de définir des règles en user space, qui seront utilisées en kernel space, sans avoir à faire d&amp;#8217;aller-retour. &lt;a href="https://github.com/seccomp/libseccomp"&gt;Libseccomp&lt;/a&gt; propose une interface de plus haut niveau pour définir ces règles de&amp;nbsp;sécurité.&lt;/p&gt;
&lt;p&gt;Les solutions de conteneurs officiels sont des assemblages de ces différents outils&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/lmctfy"&gt;&lt;span class="caps"&gt;LMCTFY&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linuxcontainers.org/fr/"&gt;&lt;span class="caps"&gt;LXC&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coreos/rocket"&gt;Rocket&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les conteneurs sont une approche grandboutiennes, il est toujours possible d&amp;#8217;avoir une approche petiboutiste, plus spécifique et du coup plus adapté, comme le propose &lt;a href="http://uwsgi-docs.readthedocs.org/en/latest/"&gt;uwsgi&lt;/a&gt;, un serveur&amp;nbsp;d&amp;#8217;application.&lt;/p&gt;
&lt;p&gt;De toute façon ces outils se démocratisent rapidement, l&amp;#8217;invasion &lt;a href="http://freedesktop.org/wiki/Software/systemd/"&gt;Systemd&lt;/a&gt; amène avec lui les Cgroups (et donc les namespaces), et Apparmor est installé par défaut sur Ubuntu depuis la&amp;nbsp;7.10.&lt;/p&gt;
&lt;h1 id="decouper-le-decoupage"&gt;Découper le&amp;nbsp;découpage&lt;/h1&gt;
&lt;p&gt;Il est possible de découper à grandes tranches avec de la paravirtualisation, qui pour l&amp;#8217;instant est la seule à garantir une isolation forte, puis de confier ensuite la finition à un kernel Linux&amp;nbsp;récent.&lt;/p&gt;
&lt;h1 id="simplifier-le-decoupage"&gt;Simplifier le&amp;nbsp;découpage&lt;/h1&gt;
&lt;p&gt;Une fois la démarche de conteneurisastion amorcée, il est tentant d&amp;#8217;optimiser la pile en enlevant la couche de paravirtualisation. C&amp;#8217;est la démarche qu&amp;#8217;à fait &lt;a href="http://www.enterprisetech.com/2014/05/28/google-runs-software-containers/"&gt;Google, ils ont basculé vers le tout container&lt;/a&gt;. Google ne fait pas tourner de code dont ils n&amp;#8217;ont pas confiance, ce qui facilite largement le passage au tout container. Au pire, ils peuvent avoir du code bête, mais pas du code méchant. &lt;span class="caps"&gt;LMCTFY&lt;/span&gt;, leur solution maison, utilise quand même Apparmor.
Par contre, pour leur offre d&amp;#8217;hébergement, &lt;a href="https://cloud.google.com/compute/docs/faq"&gt;Google Cloud, utilise toujours &lt;span class="caps"&gt;KVM&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="decouper-quoi"&gt;Découper&amp;nbsp;quoi?&lt;/h1&gt;
&lt;p&gt;Une machine virtuelle a une taille minimale plutôt conséquente. Il n&amp;#8217;est pas possible d&amp;#8217;empiler trop de machines virtuelles sur une machine physique. Tout pousse à utiliser les machines virtuelles comme des machines physiques, en y faisant cohabiter différents services.
Certains services étant des prérequis (syslog, ntpd, dnsd, cron, atd…) d&amp;#8217;autres étant un regroupement de services pour ne pas gâcher la&amp;nbsp;place.&lt;/p&gt;
&lt;h1 id="decouper-des-services"&gt;Découper des&amp;nbsp;services&lt;/h1&gt;
&lt;p&gt;Un service peut être rendu par une ou plusieurs machines.
Les services distribués peuvent utiliser le classique pattern master/slave ou des choses plus égalitaires, à base de &lt;a href="http://en.wikipedia.org/wiki/Paxos_(computer_science)"&gt;Paxos&lt;/a&gt; ou de &lt;a href="http://en.wikipedia.org/wiki/Raft_(computer_science)"&gt;Raft&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il est possible de redimensionner (confier plus ou moins de ressource) un service, ou d&amp;#8217;en ajouter des instances, s’il supporte la&amp;nbsp;distribution.&lt;/p&gt;
&lt;h1 id="utiliser-des-services"&gt;Utiliser des&amp;nbsp;services&lt;/h1&gt;
&lt;p&gt;Un service est ce que va utiliser un développeur, il se fiche un peu des détails d&amp;#8217;implémentation ou de paramétrages. Son application, qui n&amp;#8217;est finalement qu&amp;#8217;un service, va utiliser d&amp;#8217;autres services pour fonctionner, et en bout de chaine, consommer des&amp;nbsp;ressources.&lt;/p&gt;
&lt;p&gt;Un développeur va utiliser des services, qui seront dimensionnés en fonction de la demande et du budget&amp;nbsp;disponible.&lt;/p&gt;
&lt;p&gt;Ces services pourront être classique, comme du stockage, du backup, une base de données relationnelle, mais aussi plus abstrait comme de l&amp;#8217;antispam (&lt;a href="http://akismet.com"&gt;Akismet&lt;/a&gt;…), le stockage distribué de S3, la proximité d&amp;#8217;un &lt;span class="caps"&gt;CDN&lt;/span&gt;, la surveillance d&amp;#8217;un &lt;a href="https://www.pingdom.com"&gt;pingdom&lt;/a&gt;, la consolidation des erreurs d&amp;#8217;un &lt;a href="https://getsentry.com/welcome/"&gt;Sentry&lt;/a&gt; (un projet libre), l&amp;#8217;analyse de performance d&amp;#8217;un &lt;a href="http://newrelic.com"&gt;Newrelic&lt;/a&gt;… la liste évolue de jour en&amp;nbsp;jour.&lt;/p&gt;
&lt;p&gt;L&amp;#8217;informatique est maintenant de &lt;a href="http://fr.wiktionary.org/wiki/de_plain-pied"&gt;plain-pied&lt;/a&gt; dans l&amp;#8217;ère des&amp;nbsp;services.&lt;/p&gt;</content><category term="Ops"></category><category term="container"></category><category term="virtualisation"></category></entry></feed>