<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>monitoring on Zwindler's Reflection</title><link>https://blog.zwindler.fr/categories/monitoring/</link><description>Recent content in monitoring on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Mon, 03 Nov 2025 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/categories/monitoring/index.xml" rel="self" type="application/rss+xml"/><item><title>Un nouveau champ `log` pour les network policies Cilium : une idée de use case</title><link>https://blog.zwindler.fr/2025/11/03/cilium-policy-log-field-limitation-fr/</link><pubDate>Mon, 03 Nov 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/11/03/cilium-policy-log-field-limitation-fr/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/10/cilium-hubble.webp" alt="Featured image of post Un nouveau champ `log` pour les network policies Cilium : une idée de use case" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;p&gt;Cilium 1.18 a ajouté un champ &lt;code&gt;log&lt;/code&gt; aux CiliumNetworkPolicies pour taguer les verdicts de flux (FORWARDED, DROPPED, AUDIT, &amp;hellip;) avec des labels personnalisés. Dans l&amp;rsquo;idée, c&amp;rsquo;est la fonctionnalité parfaite pour éviter de logger le trafic bloqué que l&amp;rsquo;on connait dans nos dashboards de monitoring !&lt;/p&gt;
&lt;p&gt;Mais il y a un hic, sans rapport avec cette fonctionnalité, qui rend cette idée inutilisable : on ne peut pas l&amp;rsquo;utiliser avec &lt;code&gt;egressDeny&lt;/code&gt; + &lt;code&gt;toFQDNs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ET, il y a un bug, qui fait que le &amp;ldquo;log&amp;rdquo; n&amp;rsquo;est visible que sur le trafic &amp;ldquo;autorisé&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Je vous raconte&amp;hellip;&lt;/p&gt;
&lt;h2 id="le-problème--monitor-all-the-things-mais-pas-trop"&gt;Le problème : monitor all the things (mais pas trop)
&lt;/h2&gt;&lt;p&gt;Comme toute bonne équipe &amp;ldquo;ops&amp;rdquo; qui se respecte, nous monitorons/loggons les flux réseau de notre cluster Kubernetes avec Hubble pour une analyse ultérieure et de l&amp;rsquo;alerting. Nous (en particulier mon collègue Nicolas Nativel) poussons tous les flux &lt;code&gt;AUDIT&lt;/code&gt; et &lt;code&gt;DROPPED&lt;/code&gt; vers un dashboard Grafana pour pouvoir rapidement repérer quand quelque chose est bloqué et décider :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C&amp;rsquo;est légitime ? → On ouvre le flux&lt;/li&gt;
&lt;li&gt;C&amp;rsquo;est suspect ? → On déclenche l&amp;rsquo;alarme 🚨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/10/cilium-hubble.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Ça fonctionne plutôt bien&amp;hellip; jusqu&amp;rsquo;à ce qu&amp;rsquo;on commence à bloquer &lt;strong&gt;explicitement&lt;/strong&gt; des choses qu&amp;rsquo;on &lt;em&gt;sait&lt;/em&gt; devoir être bloquées.&lt;/p&gt;
&lt;p&gt;Dans notre cas, nous voulions empêcher une application tierce d&amp;rsquo;envoyer les données de &amp;ldquo;télémétrie&amp;rdquo; (ouais, appelons ça comme ça 😏). On parle d&amp;rsquo;appels HTTPS vers des domaines de tracking externes.&lt;/p&gt;
&lt;p&gt;Le problème ? Si on bloque simplement ces flux, ils apparaîtront comme &lt;code&gt;DROPPED&lt;/code&gt; dans Hubble, déclencheront notre monitoring, et on se retrouvera avec des alertes pour quelque chose qu&amp;rsquo;on a &lt;em&gt;intentionnellement&lt;/em&gt; bloqué.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est du bruit dont on ne veut pas.&lt;/p&gt;
&lt;h2 id="et-donc-ce-champ-log-des-network-policies-de-cilium-118-"&gt;Et donc, ce champ log des network policies de Cilium 1.18 ?
&lt;/h2&gt;&lt;p&gt;Bonne nouvelle ! Cilium 1.18 a introduit exactement ce dont nous avions besoin : la possibilité d&amp;rsquo;ajouter des champs de log personnalisés aux verdicts sur les network policies.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cf. &lt;a class="link" href="https://isovalent.com/blog/post/cilium-1-18/#hubble-flow-policy-log-field" target="_blank" rel="noopener"
&gt;l&amp;rsquo;annonce dans le blogpost officiel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;rsquo;idée est simple : vous ajoutez un champ &lt;code&gt;log.value&lt;/code&gt; à votre CiliumNetworkPolicy :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cilium.io/v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CiliumNetworkPolicy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-policy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endpointSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;egress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;toFQDNs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;matchName&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;#34;example.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&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;#34;my-custom-log-tag&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ensuite, quand vous observez les flux dans Hubble, vous pouvez &lt;strong&gt;les filtrer&lt;/strong&gt; en utilisant &lt;a class="link" href="https://kubernetes.io/docs/reference/using-api/cel/" target="_blank" rel="noopener"
&gt;CEL (Common Expression Language)&lt;/a&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hubble observe &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --verdict AUDIT &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --not &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --cel-expression &lt;span class="s2"&gt;&amp;#34;(_flow.policy_log.endsWith(&amp;#39;my-custom-log-tag&amp;#39;))&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --print-raw-filters
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;allowlist:
- &amp;#39;{&amp;#34;verdict&amp;#34;:[&amp;#34;AUDIT&amp;#34;]}&amp;#39;
denylist:
- &amp;#39;{&amp;#34;experimental&amp;#34;:{&amp;#34;cel_expression&amp;#34;:[&amp;#34;(_flow.policy_log.endsWith(&amp;#39;&amp;#39;my-custom-log-tag&amp;#39;&amp;#39;))&amp;#34;]}}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Parfait ! Vous savez maintenant taguer les calls que vous avez explicitement bloqués et les exclure de votre monitoring. 🎉&lt;/p&gt;
&lt;h2 id="le-plan--bloquer-la-télémétrie-élégamment"&gt;Le plan : bloquer la télémétrie élégamment
&lt;/h2&gt;&lt;p&gt;À l&amp;rsquo;aide de cette nouvelle fonctionnalité, nous avons élaboré notre stratégie :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; pour bloquer explicitement les domaines de télémétrie&lt;/li&gt;
&lt;li&gt;Ajouter un champ de log personnalisé : &lt;code&gt;app-explicit-traffic-blocked&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configurer Hubble pour filtrer les verdicts de flux avec ce tag&lt;/li&gt;
&lt;li&gt;Profit ! 🎉&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voici ce que nous avons essayé :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cilium.io/v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CiliumNetworkPolicy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;app-external-block-policy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-namespace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endpointSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# note: egressDeny prend la précédence sur les règles egress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# https://docs.cilium.io/en/stable/security/policy/language/#deny-policies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;egressDeny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Bloquer tout le trafic externe et le logger avec un champ de log arbitraire&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Ceci est utilisé pour empêcher l&amp;#39;app d&amp;#39;envoyer des données de télémétrie à l&amp;#39;extérieur&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# sans déclencher d&amp;#39;alerte AUDIT/DROPPED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# fonctionnalité ajoutée dans cilium 1.18.0 https://github.com/cilium/cilium/pull/39902&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;toFQDNs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;matchPattern&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;#34;*.telemetry.example.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toPorts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;port&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;#34;443&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TCP&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;port&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;#34;80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TCP&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&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;#34;app-explicit-traffic-blocked&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ça devrait fonctionner, non ? On utilise &lt;code&gt;egressDeny&lt;/code&gt; (qui &lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;prend la précédence sur les autres règles&lt;/a&gt;, ce qui est une bonne chose !), et on le tague avec notre log personnalisé.&lt;/p&gt;
&lt;h2 id="retour-à-la-réalité"&gt;Retour à la réalité
&lt;/h2&gt;&lt;p&gt;Et puis&amp;hellip; &lt;strong&gt;patatra&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;En lisant la &lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;documentation Cilium sur les deny policies&lt;/a&gt;, nous sommes tombés sur cette petite note de rien du tout :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deny policies do not support:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;policy enforcement at L7, i.e., specifically denying an URL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;toFQDNs&lt;/strong&gt;, i.e., specifically denying traffic to a specific domain name.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Attendez, quoi ?&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;ne peut pas&lt;/strong&gt; utiliser &lt;code&gt;toFQDNs&lt;/code&gt; avec &lt;code&gt;egressDeny&lt;/code&gt;. Tout notre plan vient de s&amp;rsquo;effondrer 😱.&lt;/p&gt;
&lt;h2 id="pourquoi-cest-un-problème-déjà-"&gt;Pourquoi c&amp;rsquo;est un problème, déjà ?
&lt;/h2&gt;&lt;p&gt;Le problème, c&amp;rsquo;est le modèle de précédence dans Cilium :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les règles &lt;code&gt;egressDeny&lt;/code&gt; prennent la précédence sur les règles &lt;code&gt;egress&lt;/code&gt; (by design, et c&amp;rsquo;est bien !)&lt;/li&gt;
&lt;li&gt;Mais si on utilise &lt;code&gt;egressDeny&lt;/code&gt;, on ne peut pas utiliser &lt;code&gt;toFQDNs&lt;/code&gt; pour cibler intelligemment le domaine incriminé, on doit bloquer par IP ou CIDR&lt;/li&gt;
&lt;li&gt;Ces services de télémétrie utilisent &lt;em&gt;probablement&lt;/em&gt; des IPs dynamiques pour leurs endpoints (bonne chance pour maintenir une liste&amp;hellip;)&lt;/li&gt;
&lt;li&gt;Et si on bloque tout le trafic 80/443 dans &lt;code&gt;egressDeny&lt;/code&gt;, on ne peut pas faire d&amp;rsquo;exceptions pour le trafic légitime dans les règles &lt;code&gt;egress&lt;/code&gt; car&amp;hellip; &lt;em&gt;deny&lt;/em&gt; prend la précédence sur &lt;em&gt;allow&lt;/em&gt; !&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On est coincé entre le marteau et l&amp;rsquo;enclume :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utiliser &lt;code&gt;egress&lt;/code&gt; avec &lt;code&gt;toFQDNs&lt;/code&gt; → ça marche, mais on ne peut pas &lt;strong&gt;bloquer&lt;/strong&gt;, seulement autoriser&lt;/li&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; avec des IPs → on va jouer au chat et à la souris avec des plages IP qui changent&lt;/li&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; pour bloquer tout le 80/443 → on bloque tout, y compris le trafic légitime&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="solutions-de-contournement-potentielles"&gt;Solutions de contournement potentielles
&lt;/h2&gt;&lt;p&gt;En attendant que Cilium supporte &lt;code&gt;toFQDNs&lt;/code&gt; dans les policies &lt;code&gt;egressDeny&lt;/code&gt;, voici quelques approches alternatives que vous pourriez envisager :&lt;/p&gt;
&lt;h3 id="trouver-un-moyen-de-désactiver-la-télémétrie-directement-dans-lapp"&gt;Trouver un moyen de désactiver la télémétrie directement dans l&amp;rsquo;app
&lt;/h3&gt;&lt;p&gt;C&amp;rsquo;est la meilleure option, mais malheureusement pas toujours sur la table.&lt;/p&gt;
&lt;h3 id="blocage-basé-sur-le-dns"&gt;Blocage basé sur le DNS
&lt;/h3&gt;&lt;p&gt;Configurer le serveur DNS pour retourner NXDOMAIN pour les domaines de télémétrie, comme un serveur &lt;a class="link" href="https://pi-hole.net/" target="_blank" rel="noopener"
&gt;pi-hole&lt;/a&gt; personnel le ferait avec les pubs. L&amp;rsquo;application échouera à résoudre le domaine et n&amp;rsquo;enverra pas de données.&lt;/p&gt;
&lt;h3 id="utiliser-egressdeny-basé-sur-les-ips-avec-un-overhead-de-maintenance"&gt;Utiliser egressDeny basé sur les IPs (avec un overhead de maintenance)
&lt;/h3&gt;&lt;p&gt;Résoudre les FQDNs de télémétrie vers leurs plages IP actuelles et les bloquer avec &lt;code&gt;egressDeny&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;egressDeny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;toCIDRSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cidr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;203.0.113.0&lt;/span&gt;&lt;span class="l"&gt;/24 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Exemple de plage IP de télémétrie&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toPorts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;port&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;#34;443&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si la liste n&amp;rsquo;évolue pas trop souvent, c&amp;rsquo;est une bonne option.&lt;/p&gt;
&lt;h2 id="ok-mais-imaginons-quil-ny-ait-pas-de-trafic-légitime-peut-on-utiliser-la-fonctionnalité-pour-ajouter-un-log-sur-le-trafic-droppé-"&gt;OK, mais imaginons qu&amp;rsquo;il n&amp;rsquo;y ait pas de trafic légitime. Peut-on utiliser la fonctionnalité pour ajouter un log sur le trafic droppé ?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Malheureusement non, pas pour le moment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Il y a un bug dans cette nouvelle fonctionnalité de Cilium qui ne log le champ &lt;code&gt;policy_log&lt;/code&gt; que sur les flux &amp;ldquo;autorisés&amp;rdquo;, pas sur les flux audit/dropped.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/issues/42044" target="_blank" rel="noopener"
&gt;Policy log does not work for DROPPED/AUDIT flow&lt;/a&gt; ouverte par mon collègue Nicolas :&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;When defining a CiliumNetworkPolicy with the spec.log field configured, I expect the relevant hubble flows to have the policy_log field. It works for allowed flow.&lt;/p&gt;
&lt;p&gt;But for denied/audited flow resulting from the rule (implicit or explicit), policy_log is never available.&lt;/p&gt;
&lt;p&gt;Note: I observe the same issue with &lt;code&gt;--print-policy-names&lt;/code&gt; option of hubble, the k8s:io.cilium.k8s.policy.derived-from label is not set for denied flows (but correctly set for allowed flows).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;et une autre issue &lt;a class="link" href="https://github.com/cilium/cilium/issues/41912" target="_blank" rel="noopener"
&gt;[Hubble CLI] &amp;ndash;print-policy-names flag does not do anything&lt;/a&gt; ouverte par quelqu&amp;rsquo;un d&amp;rsquo;autre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puisque 2 tickets sont ouverts et que les mainteneurs ont commencé à répondre dessus, on peut espérer que ce soit corrigé un jour.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Dans notre cas d&amp;rsquo;usage, nous n&amp;rsquo;avons finalement pas utilisé cette nouvelle fonctionnalité de Cilium. Mais donner la possibilité d&amp;rsquo;ajouter des détails (et permettre de filtrer dessus également) est toujours une feature sympa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Un grand merci à mon collègue Nicolas Nativel, qui a fait la majorité du travail autour des CiliumNetworkPolicies, incluant les dashboards, le travail exploratoire sur cette fonctionnalité, et a pris le temps de créer l&amp;rsquo;issue sur le repo Cilium.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="références"&gt;Références
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://isovalent.com/blog/post/cilium-1-18/#hubble-flow-policy-log-field" target="_blank" rel="noopener"
&gt;Blogpost officiel pour la sortie de Cilium 1.18 - Hubble flow policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.danielstechblog.io/ciliums-new-hubble-flow-policy-log-field/" target="_blank" rel="noopener"
&gt;Daniel&amp;rsquo;s Tech Blog - Cilium&amp;rsquo;s new Hubble flow policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;Docs Cilium - Deny Policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/pull/39902" target="_blank" rel="noopener"
&gt;GitHub PR #39902 - Add policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/issues/42044" target="_blank" rel="noopener"
&gt;GitHub Issue #42044 - Policy log does not work for DROPPED/AUDIT flow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>SLO, SLI, Error Budget et Critical User Journey expliqués simplement (et pourquoi ce ne sont pas des SLA !) (en plusieurs prompts)</title><link>https://blog.zwindler.fr/2025/06/17/slo-sli-error-budget-critical-user-journey-expliques-simplement/</link><pubDate>Tue, 17 Jun 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/06/17/slo-sli-error-budget-critical-user-journey-expliques-simplement/</guid><description>&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/sre_sre_partout.webp" alt="Featured image of post SLO, SLI, Error Budget et Critical User Journey expliqués simplement (et pourquoi ce ne sont pas des SLA !) (en plusieurs prompts)" /&gt;&lt;p&gt;&lt;strong&gt;NOTE IMPORTANTE :&lt;/strong&gt; cet article a été généré par un LLM. Ceci va à l&amp;rsquo;encontre de règles que je me suis fixé pour ce blog (cf l&amp;rsquo;&lt;a class="link" href="https://blog.zwindler.fr/ai-manifesto/" target="_blank" rel="noopener"
&gt;AI Manifesto&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Je pars du principe que si je ne prends pas la peine d’écrire moi-même le contenu de ce blog, vous ne devriez pas prendre la peine de le lire.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Je l&amp;rsquo;ai fait dans le cadre d&amp;rsquo;une expérience qui est décrite dans l&amp;rsquo;article suivant :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/06/17/reflexions-blogging-technique-ere-llms/" &gt;Réflexions sur le blogging technique à l&amp;rsquo;ère des LLMs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cependant, je ne vous interdit pas de lire cet article ci (les informations qu&amp;rsquo;il contient sur les SLOs sont correctes), je veux juste que vous le fassiez en connaissance de cause ;-P.&lt;/p&gt;
&lt;h2 id="introduction--quand-les-devs-découvrent-le-sre"&gt;Introduction : quand les devs découvrent le SRE
&lt;/h2&gt;&lt;p&gt;Suite à plusieurs discussions récentes avec des collègues développeurs, je me suis rendu compte que les concepts SRE comme les SLO, SLI et Error Budget restaient flous pour beaucoup d&amp;rsquo;entre eux. Pourtant, ces notions sont de plus en plus utilisées dans nos équipes, souvent sans qu&amp;rsquo;on prenne le temps de bien les expliquer.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est donc l&amp;rsquo;occasion parfaite pour revenir aux fondamentaux et expliquer ces concepts tels qu&amp;rsquo;ils ont été conçus à l&amp;rsquo;origine par Google. Car oui, il faut le rappeler : le SRE (Site Reliability Engineering) et tous les concepts associés ont été inventés par Google, plus précisément par Ben Treynor Sloss en 2003, bien avant que DevOps ne devienne populaire !&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée de Google était simple : et si on demandait à des ingénieurs logiciel de concevoir une équipe d&amp;rsquo;exploitation ? De cette approche sont nés des concepts révolutionnaires pour mesurer et gérer la fiabilité des services.&lt;/p&gt;
&lt;p&gt;Fun fact : j&amp;rsquo;avais déjà abordé ces sujets dans &lt;a class="link" href="https://blog.zwindler.fr/talks/2022-sre-sre-partout/index.html" &gt;mon talk sur le SRE en 2022&lt;/a&gt;, mais je me dis qu&amp;rsquo;un article dédié ne fait pas de mal pour clarifier les choses :-).&lt;/p&gt;
&lt;h2 id="sla-vs-slo--ne-mélangeons-pas-tout-"&gt;SLA vs SLO : ne mélangeons pas tout !
&lt;/h2&gt;&lt;p&gt;Avant de rentrer dans le vif du sujet, petit aparté important : &lt;strong&gt;ne confondez pas SLA et SLO&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;Le SLA (Service Level Agreement), c&amp;rsquo;est un contrat, souvent avec des pénalités financières si pas respecté. Genre &amp;ldquo;si le service est en panne plus de X heures dans le mois, on vous rembourse Y€&amp;rdquo;. Le SLO (Service Level Objective), c&amp;rsquo;est un objectif &lt;strong&gt;interne&lt;/strong&gt; que vous vous fixez pour la fiabilité de votre service.&lt;/p&gt;
&lt;p&gt;La différence est importante : les SLA sont souvent moins stricts que les SLO pour avoir une marge de manœuvre. Si votre SLA c&amp;rsquo;est 99,9% de disponibilité, votre SLO interne sera peut-être à 99,95%.&lt;/p&gt;
&lt;p&gt;Bon, maintenant qu&amp;rsquo;on a éclairci ça, rentrons dans le détail !&lt;/p&gt;
&lt;h2 id="critical-user-journey--commencer-par-ce-qui-compte-vraiment"&gt;Critical User Journey : commencer par ce qui compte vraiment
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Est-ce que le &lt;strong&gt;client&lt;/strong&gt; est content d&amp;rsquo;utiliser le service ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;est LA question fondamentale. Et pour y répondre, il faut d&amp;rsquo;abord identifier les &lt;strong&gt;Critical User Journey&lt;/strong&gt; (CUJ), autrement dit les parcours utilisateurs critiques.&lt;/p&gt;
&lt;p&gt;Mais attention, quand on parle d&amp;rsquo;&lt;strong&gt;utilisateur&lt;/strong&gt; dans le contexte SRE, ce n&amp;rsquo;est pas forcément l&amp;rsquo;utilisateur final ! Pour un service backend, l&amp;rsquo;utilisateur peut être le frontend qui fait les appels API. Pour une plateforme de CI/CD, ce sont les développeurs qui veulent livrer une nouvelle version. Pour une base de données, ce sont les applications qui l&amp;rsquo;interrogent.&lt;/p&gt;
&lt;p&gt;Concrètement, ça veut dire quoi ? Prenons l&amp;rsquo;exemple d&amp;rsquo;une plateforme e-commerce. Les CUJ pourraient être : un utilisateur peut rechercher et consulter un produit, il peut ajouter un produit au panier et passer commande, il peut se connecter à son compte.&lt;/p&gt;
&lt;p&gt;On ne va pas définir des SLO pour toutes les fonctionnalités (la page &amp;ldquo;À propos&amp;rdquo; de votre site, on s&amp;rsquo;en fiche un peu), mais se concentrer sur celles qui, si elles tombent en panne, vont vraiment énerver vos utilisateurs.&lt;/p&gt;
&lt;p&gt;Et c&amp;rsquo;est là que ça devient intéressant : définir les CUJ, c&amp;rsquo;est souvent un exercice qui doit impliquer le business, pas seulement les équipes techniques. C&amp;rsquo;est eux qui savent ce qui rapporte de l&amp;rsquo;argent !&lt;/p&gt;
&lt;h2 id="sli--mesurer-ce-qui-compte"&gt;SLI : mesurer ce qui compte
&lt;/h2&gt;&lt;p&gt;Une fois qu&amp;rsquo;on a identifié nos CUJ, il faut les &lt;strong&gt;mesurer&lt;/strong&gt;. C&amp;rsquo;est là qu&amp;rsquo;interviennent les &lt;strong&gt;SLI (Service Level Indicators)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Un SLI, c&amp;rsquo;est simplement une métrique qui indique si votre service fonctionne bien du point de vue de l&amp;rsquo;utilisateur. Les plus classiques sont la disponibilité (pourcentage de requêtes qui réussissent), la latence (temps de réponse du service), le débit (nombre de requêtes traitées par seconde) et la qualité (pourcentage de réponses correctes, sans erreurs de données).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée clé ici, c&amp;rsquo;est de mesurer depuis le point de vue de l&amp;rsquo;utilisateur, pas depuis vos serveurs. Peu importe que votre CPU soit à 10% si l&amp;rsquo;utilisateur voit des erreurs 500 !&lt;/p&gt;
&lt;h3 id="quelques-conseils-pour-choisir-vos-sli"&gt;Quelques conseils pour choisir vos SLI
&lt;/h3&gt;&lt;p&gt;Pour vous aider à concevoir des SLI fiables, vous pouvez vous inspirer des méthodes &lt;strong&gt;USE&lt;/strong&gt; et &lt;strong&gt;RED&lt;/strong&gt;. USE (Utilization, Saturation, Errors) se concentre sur les ressources systèmes, tandis que RED (Rate, Errors, Duration) se concentre sur les requêtes. Ces frameworks vous donnent un bon point de départ pour identifier les métriques qui comptent vraiment.&lt;/p&gt;
&lt;p&gt;Mais attention, un microservice ne doit pas avoir trop de SLI ! Trois ou quatre SLI bien choisies et qui ont un sens métier valent mieux qu&amp;rsquo;une dizaine de métriques que personne ne regarde. D&amp;rsquo;ailleurs, seuls les membres de l&amp;rsquo;équipe qui fournissent le service peuvent vraiment savoir quelles sont les métriques pertinentes. On ne peut pas imposer des SLI génériques à toute une entreprise !&lt;/p&gt;
&lt;p&gt;Exemple concret pour notre CUJ &amp;ldquo;recherche de produit&amp;rdquo; : SLI disponibilité pourrait être &lt;code&gt;(requêtes HTTP 200 sur /search) / (total requêtes sur /search) * 100&lt;/code&gt;, et SLI latence &lt;code&gt;95% des requêtes sur /search répondent en moins de X ms&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="slo--se-fixer-des-objectifs-réalistes"&gt;SLO : se fixer des objectifs réalistes
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on sait &lt;strong&gt;quoi&lt;/strong&gt; mesurer, il faut se fixer des &lt;strong&gt;objectifs&lt;/strong&gt;. C&amp;rsquo;est le rôle des &lt;strong&gt;SLO (Service Level Objectives)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Un SLO, c&amp;rsquo;est tout simplement une valeur cible pour vos SLI sur une période donnée. Par exemple : &amp;ldquo;99,9% des requêtes de recherche doivent réussir sur une période de 30 jours&amp;rdquo; ou &amp;ldquo;95% des pages de recherche doivent s&amp;rsquo;afficher en moins de 500ms sur une période de 7 jours&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="quelques-conseils-pour-bien-définir-vos-slo"&gt;Quelques conseils pour bien définir vos SLO
&lt;/h3&gt;&lt;p&gt;Commencez par mesurer l&amp;rsquo;existant. Inutile de viser 99,99% si votre service actuel est à 98%. Regardez vos métriques historiques et fixez-vous des objectifs atteignables mais ambitieux.&lt;/p&gt;
&lt;p&gt;Pensez S.M.A.R.T. : vos SLO doivent être Spécifiques, Mesurables, Atteignables, Réalistes et Temporellement définis. Comme tout bon objectif !&lt;/p&gt;
&lt;p&gt;Et n&amp;rsquo;oubliez pas que 100% c&amp;rsquo;est mal ! Comme le dit si bien Ben Treynor Sloss (le papa du SRE chez Google) :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;100% is the &lt;strong&gt;wrong&lt;/strong&gt; reliability target for basically everything&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plus on veut de &amp;ldquo;9&amp;rdquo;, plus ça coûte cher exponentiellement. Et au-delà d&amp;rsquo;un certain seuil, les utilisateurs ne voient même plus la différence ! Prenez l&amp;rsquo;exemple d&amp;rsquo;un site web qui ne charge pas sur un smartphone : au-delà d&amp;rsquo;un certain niveau de disponibilité, l&amp;rsquo;utilisateur ne saura pas dire si c&amp;rsquo;est son smartphone qui a un problème, son navigateur, le réseau 5G ou bien le site web qui rencontre un incident. Si recharger la page une fois de temps en temps suffit et que les utilisateurs ne sont pas plus impactés que ça, inutile d&amp;rsquo;investir dans plus de fiabilité.&lt;/p&gt;
&lt;p&gt;Pour vous aider à calculer ces pourcentages et temps d&amp;rsquo;indisponibilité, vous pouvez utiliser &lt;a class="link" href="https://uptime.is/" target="_blank" rel="noopener"
&gt;uptime.is&lt;/a&gt; qui fait les conversions pour vous.&lt;/p&gt;
&lt;h2 id="error-budget--retourner-le-problème"&gt;Error Budget : retourner le problème
&lt;/h2&gt;&lt;p&gt;Et là, c&amp;rsquo;est le moment où ça devient vraiment malin. Au lieu de raisonner en &amp;ldquo;disponibilité&amp;rdquo;, les équipes SRE raisonnent en &lt;strong&gt;Error Budget&lt;/strong&gt; (budget d&amp;rsquo;erreur).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un simple changement de perspective : service accessible 99,9% = service &lt;strong&gt;inaccessible&lt;/strong&gt; 0,1% du temps. Sur 30 jours, ça fait environ 43 minutes d&amp;rsquo;indisponibilité &amp;ldquo;autorisée&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Cette approche change complètement la donne ! Au lieu de voir les pannes comme des échecs, on les voit comme un &lt;strong&gt;budget à dépenser intelligemment&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="comment-utiliser-son-error-budget-"&gt;Comment utiliser son Error Budget ?
&lt;/h3&gt;&lt;p&gt;Contre-intuitivement&amp;hellip; &lt;strong&gt;IL FAUT L&amp;rsquo;UTILISER&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/simpsons.png"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Si votre SLO est respecté (utilisateurs contents), vous pouvez &amp;ldquo;dépenser&amp;rdquo; votre error budget pour faire des déploiements plus risqués, tester des nouvelles fonctionnalités en prod, faire du chaos engineering, ou réaliser des maintenances disruptives.&lt;/p&gt;
&lt;p&gt;À l&amp;rsquo;inverse, si vous &amp;ldquo;cramez&amp;rdquo; votre error budget (SLO pas atteint), alors là, stop : on arrête tout ce qui n&amp;rsquo;améliore pas la fiabilité du service !&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un formidable outil de priorisation entre les équipes produit (qui veulent des nouvelles features) et les équipes ops (qui veulent de la stabilité). Le fameux mur de la confusion :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/mur_de_la_confusion.png"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="exemple-concret--une-api-de-recommendation"&gt;Exemple concret : une API de recommendation
&lt;/h2&gt;&lt;p&gt;Bon, assez de théorie, prenons un exemple concret. Imaginons qu&amp;rsquo;on ait une API de recommandation de produits.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, on définit le CUJ : &amp;ldquo;Un utilisateur doit pouvoir récupérer des recommandations personnalisées en moins de 1 seconde&amp;rdquo;. Ensuite, on choisit les SLI : disponibilité &lt;code&gt;(réponses HTTP 200) / (total requêtes) * 100&lt;/code&gt; et latence &lt;code&gt;P95 du temps de réponse&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour les SLO, on pourrait avoir : &amp;ldquo;99,5% des requêtes sur l&amp;rsquo;API de recommandation doivent réussir sur 30 jours&amp;rdquo; et &amp;ldquo;95% des requêtes doivent répondre en moins de 800ms sur 7 jours&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Enfin, on calcule l&amp;rsquo;Error Budget : 99,5% de disponibilité = 0,5% d&amp;rsquo;indisponibilité autorisée, soit environ 3,6 heures d&amp;rsquo;indisponibilité &amp;ldquo;budgetées&amp;rdquo; sur 30 jours.&lt;/p&gt;
&lt;p&gt;Simple, non ?&lt;/p&gt;
&lt;h2 id="les-pièges-à-éviter"&gt;Les pièges à éviter
&lt;/h2&gt;&lt;p&gt;Le premier piège, c&amp;rsquo;est de vouloir mettre des SLO partout. N&amp;rsquo;essayez pas ! Commencez par 2-3 SLO sur vos CUJ les plus critiques. Vous pourrez étendre ensuite.&lt;/p&gt;
&lt;p&gt;Le deuxième piège, c&amp;rsquo;est de fixer des SLO trop stricts. Si vous mettez la barre trop haut, vous allez passer votre temps en &amp;ldquo;SLO violation&amp;rdquo; et personne ne prendra plus ça au sérieux.&lt;/p&gt;
&lt;p&gt;Enfin, le troisième piège, c&amp;rsquo;est d&amp;rsquo;oublier l&amp;rsquo;aspect organisationnel. Les Error Budgets ne marchent que si toute l&amp;rsquo;organisation (business inclus) adhère au principe. Sinon, vous aurez beau être en SLO violation, on vous demandera quand même de déployer la nouvelle feature&amp;hellip;&lt;/p&gt;
&lt;h2 id="comment-commencer-"&gt;Comment commencer ?
&lt;/h2&gt;&lt;p&gt;Si vous n&amp;rsquo;avez jamais fait de SLO, voici un plan d&amp;rsquo;action simple.&lt;/p&gt;
&lt;p&gt;Identifiez d&amp;rsquo;abord 1-2 CUJ critiques (avec le business !). Regardez ensuite vos métriques actuelles sur ces parcours. Définissez alors des SLO réalistes mais un peu ambitieux. Mettez en place l&amp;rsquo;alerting quand vous êtes en train de consommer votre error budget. Et enfin, itérez ! Les SLO ne sont pas gravés dans le marbre.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;espère que cet article vous aura donné envie de creuser ces concepts ! Les SLO/SLI/Error Budget ne sont pas juste des buzzwords, c&amp;rsquo;est vraiment un changement de paradigme dans la façon d&amp;rsquo;appréhender la fiabilité.&lt;/p&gt;
&lt;p&gt;Et le plus beau, c&amp;rsquo;est que ça marche autant pour une startup avec 3 développeurs que pour une GAFAM avec 10000 ingénieurs. L&amp;rsquo;important, c&amp;rsquo;est de commencer simple et d&amp;rsquo;itérer.&lt;/p&gt;
&lt;p&gt;Pour aller plus loin, je vous recommande chaudement le &lt;a class="link" href="https://sre.google/sre-book/service-level-objectives/" target="_blank" rel="noopener"
&gt;SRE Book de Google&lt;/a&gt; (gratuit !) et leur &lt;a class="link" href="https://cloud.google.com/blog/products/management-tools/practical-guide-to-setting-slos" target="_blank" rel="noopener"
&gt;guide pratique pour définir des SLO&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et si vous voulez approfondir le sujet SRE en général, n&amp;rsquo;hésitez pas à jeter un œil aux &lt;a class="link" href="https://blog.zwindler.fr/talks/2022-sre-sre-partout/index.html" &gt;slides de mon talk de 2022&lt;/a&gt; ;-).&lt;/p&gt;
&lt;p&gt;Bon monitoring !&lt;/p&gt;</description></item><item><title>Retrouvez-moi ce soir au meetup Archilocus</title><link>https://blog.zwindler.fr/2024/05/30/retrouvez-moi-ce-soir-archilocus/</link><pubDate>Thu, 30 May 2024 08:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2024/05/30/retrouvez-moi-ce-soir-archilocus/</guid><description>&lt;img src="https://blog.zwindler.fr/2024/05/archilocus.webp" alt="Featured image of post Retrouvez-moi ce soir au meetup Archilocus" /&gt;&lt;h2 id="on-the-road-again"&gt;On the road again
&lt;/h2&gt;&lt;p&gt;Après quelques mois sans être speaker (&lt;a class="link" href="https://blog.zwindler.fr/2024/02/04/recap-fosdem-2024/" &gt;FOSDEM en février&lt;/a&gt;, ouhalala au moins 3 mois 🤣), j&amp;rsquo;aurais le plaisir de participer avec des anciens collègues / amis à Archilocus ce soir.&lt;/p&gt;
&lt;p&gt;Ca se passe dans les locaux de Zenika Bordeaux à partir de 18h ce soir, au 17 Quai Louis XVIII.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://www.meetup.com/archilocus/events/300879794/" target="_blank" rel="noopener"
&gt;La page du meetup (inscription obligatoire, gratuit) Archilocus #13&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="le-planning-du-meetup"&gt;Le planning du meetup
&lt;/h2&gt;&lt;p&gt;📺 Diffusé simultanément en live sur &lt;a class="link" href="https://twitch.tv/archilocus" target="_blank" rel="noopener"
&gt;twitch.tv/archilocus&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🕕 18h05 - OpenTelemetry: vous ne pourrez plus vous passer de monitoring en prod
par Vivien Maleze Architecte Technique chez Ippon Technology&lt;/p&gt;
&lt;p&gt;🕖 18h45 - Pourquoi privilégions-nous les services managés dans le Cloud ?
par Guillaume Lannebere Head Of Cloud Center Of Excellence chez Betclic&lt;/p&gt;
&lt;p&gt;🕢 19h25 - Comment je monitore ma prod ?
Table ronde avec participation du public
Denis Germain Staff SRE chez Deezer
Sébastien Descamps Directeur R&amp;amp;D et Innovation chez Zenika
Gauthier Astruc-Amato CTO chez CyberVadis&lt;/p&gt;
&lt;h2 id="le-pitch-de-la-session"&gt;Le pitch de la session
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Table ronde : Comment je monitore ma prod ?
Denis Germain, Sébastien Descamps et Gauthier Astruc-Amato partageront leurs expériences et discuteront des meilleures pratiques pour le monitoring de la production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="bonus---deez-is-la-tech"&gt;Bonus - Deez is la Tech
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai aussi oublié de publier que j&amp;rsquo;ai participé à un podcast sorti la semaine dernière, pour parler des Communautés de Pratiques (aka les CoP), dont j&amp;rsquo;avais déjà parlé &lt;a class="link" href="https://blog.zwindler.fr/2021/03/23/monter-une-cop-community-of-practice/" &gt;dans cet article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2024/05/DILT.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Les liens vers les plateformes d&amp;rsquo;écoutes ainsi que le transcript est disponible ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://deezer.io/dilt-s02e06-good-cop-bad-cop-comment-organiser-et-animer-une-communaut%C3%A9-de-pratique-dea6047126fa" target="_blank" rel="noopener"
&gt;Deez is la tech — S02E06 — Good COP, bad COP : comment organiser et animer une Communauté de Pratique&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Ajoutons du monitoring à notre cluster k3s</title><link>https://blog.zwindler.fr/2023/09/15/k3s-ajouter-monitoring/</link><pubDate>Fri, 15 Sep 2023 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2023/09/15/k3s-ajouter-monitoring/</guid><description>&lt;img src="https://blog.zwindler.fr/2023/09/k3s_cilium.webp" alt="Featured image of post Ajoutons du monitoring à notre cluster k3s" /&gt;&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Je pars de l&amp;rsquo;article précédent (&lt;a class="link" href="https://blog.zwindler.fr/2023/09/01/k3s-et-cilium-rapide-et-facile" &gt;k3s et cilium rapide et facile&lt;/a&gt;) comme base pour installer ma stack k3s + cilium (CNI + ingressController).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée ici, ça va être de préparer le monitoring pour le cluster, en vue d&amp;rsquo;y ajouter une (des) application(s).&lt;/p&gt;
&lt;h2 id="prometheus--friends"&gt;Prometheus &amp;amp; friends
&lt;/h2&gt;&lt;p&gt;Pour la partie Prometheus, j&amp;rsquo;ai choisi d&amp;rsquo;utiliser la chart du projet &lt;strong&gt;prometheus-community&lt;/strong&gt;, qui s&amp;rsquo;appuie elle-même sur plusieurs charts, dont grafana ainsi que le Prometheus Operator.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://prometheus-operator.dev/" target="_blank" rel="noopener"
&gt;prometheus-operator.dev/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cette chart est &amp;ldquo;énorme&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Elle propose une quantité folle d&amp;rsquo;options, notamment en termes de divers setups de déploiement, avec ou sans Thanos par exemple. On peut même créer des &amp;ldquo;sharded Prometheus&amp;rdquo;, au cas où la charge devient ingérable par rapport à la taille de vos nodes.&lt;/p&gt;
&lt;p&gt;Note: Ce n&amp;rsquo;est clairement pas la manière la plus simple de rentrer dans Prometheus, si vous débutez (j&amp;rsquo;ai écrit plusieurs articles là-dessus dans le passé, &lt;a class="link" href="https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/" &gt;ici&lt;/a&gt; et &lt;a class="link" href="https://blog.zwindler.fr/2019/11/12/tutoriel-installer-prometheus-grafana-sans-docker/" &gt;là&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;operator ajoute tout un tas de CRDs qui vont nous permettre de gérer la configuration (au sens très large, aussi bien les aspects techniques que pure configuration) de notre instance de Prometheus &amp;ldquo;comme du code&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Le truc cool, c&amp;rsquo;est qu&amp;rsquo;on va pouvoir définir ce qu&amp;rsquo;on veut surveiller sur notre kubernetes sans avoir à aller éditer une &lt;em&gt;ConfigMap&lt;/em&gt;. On peut ainsi donner le pouvoir aux devs de surveiller ce qu&amp;rsquo;ils veulent sans à aller toucher au namespace de monitoring.&lt;/p&gt;
&lt;p&gt;Le truc un peu dommage par contre, c&amp;rsquo;est que par défaut, la chart est livrée avec des &lt;em&gt;Selectors&lt;/em&gt; beaucoup trop restrictifs. Elle ne surveille que Prometheus lui même (release kube-prometheus)&amp;hellip; On va enlever les restrictions (ex. &lt;code&gt;serviceMonitorSelector.matchLabels: {}&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;On va donc modifier les valeurs par défaut, et aussi ajouter un &lt;em&gt;Ingress&lt;/em&gt; pour pouvoir accéder à grafana plus facilement depuis Internet :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat &amp;gt; prometheus-values.yaml &amp;lt;&amp;lt; EOF
nameOverride: prom
prometheusOperator:
enabled: true
admissionWebhooks:
enabled: true
prometheus:
enabled: true
prometheusSpec:
enableAdminAPI: true
probeSelector:
matchLabels: {}
podMonitorSelector:
matchLabels: {}
serviceMonitorSelector:
matchLabels: {}
ruleSelector:
matchLabels: {}
grafana:
ingress:
enabled: true
ingressClassName: cilium
hosts:
- grafana.example.org
cleanPrometheusOperatorObjectNames: true
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On installe donc prometheus via &lt;code&gt;helm&lt;/code&gt; une fois qu&amp;rsquo;on a le fichier de values :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;prometheus-community/prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm upgrade --install kube-prometheus prometheus-community/kube-prometheus-stack --namespace prometheus --create-namespace -f prometheus-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On vérifie le contenu de la CRD &amp;ldquo;Prometheus&amp;rdquo; que la chart à installer ne contient pas/plus les &lt;em&gt;matchLabels.release&lt;/em&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl -n prometheus get Prometheus -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; serviceMonitorNamespaceSelector: &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; serviceMonitorSelector:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; matchLabels: &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On se connecte ensuite à Grafana (accessible à l&amp;rsquo;URL &lt;a class="link" href="http://grafana.example.org" target="_blank" rel="noopener"
&gt;http://grafana.example.org&lt;/a&gt;) pour vérifier que tout est fonctionnel. Ce qui est cool, c&amp;rsquo;est que les datasources vers prometheus et l&amp;rsquo;alertmanager sont déjà préconfigurées pour nous. Un truc de moins à faire !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/09/datasources.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note: le login/mdp est admin/prom-operator (vous auriez pu le trouver vous-même dans le Secret prometheus/kube-prometheus-grafana)&lt;/p&gt;
&lt;h2 id="servicemonitor"&gt;ServiceMonitor
&lt;/h2&gt;&lt;p&gt;Une fois que c&amp;rsquo;est fait, on a notre plateforme de monitoring des métriques opérationnelle. Pour pouvoir surveiller notre &lt;em&gt;IngressController&lt;/em&gt;, il va donc falloir activer le &amp;ldquo;ServiceMonitor&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Problème, on l&amp;rsquo;a pas déployé l&amp;rsquo;option lorsqu&amp;rsquo;on a installé cilium dans le tuto précédent (en vrai, c&amp;rsquo;était volontaire, car il faut déployer la CRD ServiceMonitor AVANT de les activer dans cilium sinon l&amp;rsquo;install échoue).&lt;/p&gt;
&lt;p&gt;Je fais donc un upgrade de la release helm, avec beaucoup plus d&amp;rsquo;options (d&amp;rsquo;où le besoin de faire un fichier de values) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; cilium-values.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;operator:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; prometheus:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; serviceMonitor:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;hubble:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; relay:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; prometheus:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; serviceMonitor:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; metrics:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; serviceMonitor:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enableOpenMetrics: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - dns
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - drop
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - tcp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - icmp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - &amp;#34;flow:sourceContext=workload-name|reserved-identity;destinationContext=workload-name|reserved-identity&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - &amp;#34;kafka:labelsContext=source_namespace,source_workload,destination_namespace,destination_workload,traffic_direction;sourceContext=workload-name|reserved-identity;destinationContext=workload-name|reserved-identity&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - &amp;#34;httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction;sourceContext=workload-name|reserved-identity;destinationContext=workload-name|reserved-identity&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;prometheus:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; serviceMonitor:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.14.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm upgrade --install cilium cilium/cilium --version&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set global.tag&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --set global.containerRuntime.integration&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;containerd&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set global.containerRuntime.socketPath&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/var/run/k3s/containerd/containerd.sock&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set global.kubeProxyReplacement&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;strict&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set global.bpf.masquerade&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set ingressController.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set ingressController.default&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f cilium-values.yaml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace cilium &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On peut se connecter sur Prometheus directement via un &amp;ldquo;port-forward&amp;rdquo; pour vérifier que cilium est bien dans nos &amp;ldquo;targets&amp;rdquo;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl -n prometheus port-forward service/kube-prometheus-prometheus &lt;span class="m"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Forwarding from 127.0.0.1:9090 -&amp;gt; &lt;span class="m"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Forwarding from &lt;span class="o"&gt;[&lt;/span&gt;::1&lt;span class="o"&gt;]&lt;/span&gt;:9090 -&amp;gt; &lt;span class="m"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans Grafana, on peut également commencer à ajouter des dashboards pour voir si notre cilium va bien. Typiquement, Isovalent propose ces deux dashboards :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://grafana.com/grafana/dashboards/16611-cilium-metrics/" target="_blank" rel="noopener"
&gt;Grafana - cilium metrics 1.12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://grafana.com/grafana/dashboards/16613-hubble/" target="_blank" rel="noopener"
&gt;Grafana - hubble metrics 1.12&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="loki--promtail"&gt;Loki + promtail
&lt;/h2&gt;&lt;p&gt;Les métriques, c&amp;rsquo;est bien. Mais tant qu&amp;rsquo;à y être, j&amp;rsquo;aimerais aussi pouvoir lire les logs de mon cluster. Les fans d&amp;rsquo;observabilité et/ou de Grafana me voient venir avec mes gros sabots, je vais ajouter Loki au cluster, via la chart officielle :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo add grafana https://grafana.github.io/helm-charts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; loki-values.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;loki:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; auth_enabled: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; commonConfig:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; replication_factor: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; storage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; type: &amp;#39;filesystem&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;singleBinary:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; replicas: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install loki --namespace loki grafana/loki --create-namespace -f loki-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Par rapport aux valeurs par défaut, je vais brider loki en ne lui affectant qu&amp;rsquo;un seul replica. J&amp;rsquo;ai aussi retiré l&amp;rsquo;authentification. Ne faites évidemment pas ça en prod 😬.&lt;/p&gt;
&lt;p&gt;On a de la chance, Loki a déjà sa définition &lt;em&gt;ServiceMonitor&lt;/em&gt; par défaut :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl -n loki get serviceMonitor loki -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apiVersion: monitoring.coreos.com/v1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kind: ServiceMonitor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; annotations:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; meta.helm.sh/release-name: loki
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; meta.helm.sh/release-namespace: loki
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Loki en lui-même ne va pas faire grand-chose d&amp;rsquo;intéressant. Pour récupérer les logs de nos pods, il va nous falloir ajouter l&amp;rsquo;agent &amp;ldquo;promtail&amp;rdquo;, qui va faire l&amp;rsquo;auto-découverte et la collecte des logs de nos pods.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm upgrade --install promtail grafana/promtail -n loki
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On se connecte à Grafana, on ajoute la source de données pour Loki, puis on vérifie que tout fonctionne correctement avec la fonctionnalité &amp;ldquo;Explore&amp;rdquo; dans Grafana.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/09/datasource_loki.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/09/explore_loki.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="et-si-on-ajoutait-des-traces-"&gt;Et si on ajoutait des traces ???
&lt;/h2&gt;&lt;p&gt;Comme je l&amp;rsquo;ai dit plus haut, normalement, vous m&amp;rsquo;avez vu arriver. Il existe 3 piliers dans l&amp;rsquo;observabilité : les métriques, les logs, et les traces.&lt;/p&gt;
&lt;p&gt;Les métriques et les logs, ça parle à tout le monde. Les traces, moins.&lt;/p&gt;
&lt;p&gt;Pour faire très (trop?) simple : les traces, c&amp;rsquo;est une méthode d&amp;rsquo;observabilité permettant d&amp;rsquo;obtenir des informations de performances sur le parcours d&amp;rsquo;une requête de son entrée dans notre SI jusqu&amp;rsquo;au retour. Les informations remontées par les traces permettent ainsi de remonter la pelote entre (micro)services mais aussi obtenir des informations DANS les services eux-même.&lt;/p&gt;
&lt;p&gt;Note: c&amp;rsquo;est pour ça qu&amp;rsquo;on parle de &amp;ldquo;distributed tracing&amp;rdquo;, cette technique est particulièrement efficace pour debugger des problématiques complexes de performance au sein d&amp;rsquo;architectures microservices.&lt;/p&gt;
&lt;p&gt;Je vous incite à aller lire l&amp;rsquo;article de Mathieu Corbin : &lt;a class="link" href="https://www.mcorbin.fr/posts/2023-08-20-traces/" target="_blank" rel="noopener"
&gt;Tracing avec Opentelemetry: pourquoi c&amp;rsquo;est le futur (et pourquoi ça remplacera les logs)&lt;/a&gt; pour mieux comprendre de quoi il s&amp;rsquo;agit.&lt;/p&gt;
&lt;h2 id="tempo"&gt;Tempo
&lt;/h2&gt;&lt;p&gt;Je n&amp;rsquo;ai pas choisi les outils de Grafana Labs par hasard. Il se trouve que Grafana Labs fourni la stack complète pour disposer d&amp;rsquo;une plateforme d&amp;rsquo;observabilité complète (j&amp;rsquo;aurai même pu remplacer Prometheus/Thanos par Grafana agent + Mimir).&lt;/p&gt;
&lt;p&gt;Pour la partie tracing, je vais donc commencer par installer &lt;a class="link" href="https://grafana.com/docs/tempo/latest/" target="_blank" rel="noopener"
&gt;Grafana Tempo&lt;/a&gt;, comme datasource de tracing pour Grafana. Il existe plusieurs manières d&amp;rsquo;installer Tempo sur Kubernetes. Pour faire simple je vais déployer le composant dit &amp;ldquo;&lt;a class="link" href="https://github.com/grafana/helm-charts/tree/main/charts/tempo" target="_blank" rel="noopener"
&gt;monolithique&lt;/a&gt;&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm upgrade --install tempo grafana/tempo -n tempo --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On ajoute ensuite la datasource dans Grafana comme pour Loki :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/09/datasource_tempo.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note: attention ici encore, les valeurs renseignées ne sont pas du tout des valeurs pour une production. Ici, j&amp;rsquo;ai juste ajouté Tempo, sans aucune haute disponibilité ni persistance (S3).&lt;/p&gt;
&lt;p&gt;Maintenant que Tempo est installé, il faut qu&amp;rsquo;on indique à cilium d&amp;rsquo;envoyer des traces à Tempo. Pour ça, j&amp;rsquo;ai besoin d&amp;rsquo;ajouter opentelemetry, qui est le backend vers lequel les traces vont être envoyées (un peu comme pour loki et promtail).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo add jetstack https://charts.jetstack.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cert-manager jetstack/cert-manager &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace cert-manager &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --create-namespace &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version v1.13.0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set &lt;span class="nv"&gt;installCRDs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set admissionWebhooks.certManager.create&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; opentelemetry-operator-values.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;manager:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; serviceMonitor:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm upgrade --install opentelemetry-operator open-telemetry/opentelemetry-operator &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace opentelemetry-operator --create-namespace &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f opentelemetry-operator-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -n opentelemetry-operator -f manifests/otel-collector.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois l&amp;rsquo;OpenTelemetry operator déployé, on peut (comme pour Prometheus) gérer nos collecteurs comme du code. Je vais donc demander à Otel (le petit nom mignon d&amp;rsquo;OpenTelemetry) Operator de me créer un &lt;em&gt;DaemonSet&lt;/em&gt; pointant sur Tempo :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl create ns opentelemetry&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;cat &amp;gt; opentelemetry-manifest.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;opentelemetry.io/v1alpha1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OpenTelemetryCollector&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;otel&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;opentelemetry&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;daemonset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostNetwork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;otel/opentelemetry-collector-contrib:0.60.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; receivers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; otlp:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; protocols:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; grpc:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; endpoint: 0.0.0.0:4317
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; http:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; endpoint: 0.0.0.0:4318
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; processors:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; memory_limiter:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; check_interval: 1s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; limit_percentage: 75
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; spike_limit_percentage: 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; batch:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; send_batch_size: 10000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; timeout: 10s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; exporters:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; logging:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; loglevel: info
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; otlp:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; endpoint: tempo.tempo.svc.cluster.local:4317
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; tls:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; insecure: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; service:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; pipelines:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; traces:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; receivers: [otlp]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; processors: []
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; exporters:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - logging
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - otlp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl apply -f opentelemetry-manifest.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois créé, des pods OpenTelemetry devraient se créer :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl -n opentelemetry get pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;otel-collector-5wk9s 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 16s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;otel-collector-mv24p 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 16s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Les données de performances qui seront remontées par les pods sur otel seront visibles dans la datasource de Grafana.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;La plateforme d&amp;rsquo;observabilité est opérationnelle. J&amp;rsquo;avais prévenu, c&amp;rsquo;est assez dense :\D.&lt;/p&gt;
&lt;p&gt;Et pour l&amp;rsquo;instant elle ne sert pas à grand-chose, puisque rien n&amp;rsquo;est déployé sur mon cluster xD !&lt;/p&gt;
&lt;p&gt;Cependant, vous l&amp;rsquo;avez deviné, ceci est pour un prochain article&amp;hellip; En attendant, have fun ;-P.&lt;/p&gt;
&lt;h2 id="sources-complémentaires"&gt;Sources complémentaires
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/isovalent/cilium-grafana-observability-demo" target="_blank" rel="noopener"
&gt;Isovalent - Cilium Grafana Observability Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-operator" target="_blank" rel="noopener"
&gt;OpenTelemetry - README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-helm-charts/blob/main/charts/opentelemetry-operator/values.yaml" target="_blank" rel="noopener"
&gt;OpenTelemetry - chart values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Des visualiations ultra personnalisés sur Kibana avec Vega</title><link>https://blog.zwindler.fr/2022/01/03/visualisations-personalises-kibana-vega/</link><pubDate>Mon, 03 Jan 2022 07:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2022/01/03/visualisations-personalises-kibana-vega/</guid><description>&lt;img src="https://blog.zwindler.fr/2021/12/okiwi_bubble_chart.webp" alt="Featured image of post Des visualiations ultra personnalisés sur Kibana avec Vega" /&gt;&lt;h2 id="et-si-on-faisait-un-nuage-de-points-avec-kibana-"&gt;Et si on faisait un nuage de points avec Kibana ?
&lt;/h2&gt;&lt;p&gt;Si vous me suivez sur les réseaux sociaux que je fréquente (Twitter et LinkedIn) vous ne pouvez pas avoir loupé le fait que je participe à l&amp;rsquo;organisation du sondage annuel sur les salaires dans la tech Girondine de l&amp;rsquo;association Okiwi. (OUI, je vous ai bassiné avec ça, mais c&amp;rsquo;est pour la bonne cause :chatpotté:).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aujourd&amp;rsquo;hui, j&amp;rsquo;en parle pas, promis ;-p&lt;/strong&gt; (&lt;em&gt;&lt;a class="link" href="https://blog.zwindler.fr/2021/12/13/okiwi-grille-de-salaires-anonyme-dans-la-tech-girondine-2021-2022" &gt;ou alors, juste un tout petit mini lien ?&lt;/a&gt;&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Cet article est juste un retour d&amp;rsquo;expérience suite à un problème que j&amp;rsquo;ai eu, dans le cadre de ce sondage.&lt;/p&gt;
&lt;p&gt;Frédéric Camblor m&amp;rsquo;a fait remarquer que pour visualiser les différents types de salaires en fonction de certaines catégories, un nuage de points pouvait être une visualisation efficace.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/twitter_fred_camblor.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Or, pas de bol, on ne peut pas nativement faire ce type de visualisation dans Kibana&amp;hellip; Et vous allez voir que la solution est tout sauf triviale&amp;hellip;&lt;/p&gt;
&lt;h2 id="déjà-comment-le-savoir-"&gt;Déjà, comment le savoir ?
&lt;/h2&gt;&lt;p&gt;Pour en avoir le coeur net, j&amp;rsquo;ai épluché toutes les possibilités offertes par la page &amp;ldquo;Visualisation&amp;rdquo; de mon Kibana.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, il y a &lt;strong&gt;Lens&lt;/strong&gt;, a priori c&amp;rsquo;est relativement récent car ça ne me disait rien, mais c&amp;rsquo;est une façon assez pratique, user friendly et intuitive de créer des visualisations.&lt;/p&gt;
&lt;p&gt;On drag&amp;rsquo;n&amp;rsquo;drop la métrique qu&amp;rsquo;on veut représenter et Kibana nous propose une ou plusieurs visualisation avec un type d&amp;rsquo;aggregation (count, avg, median) déterminé automatiquement.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/twitter_elastic_lens.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour l&amp;rsquo;avoir utilisé pour la première fois pour le sondage des salaires de cette année, c&amp;rsquo;est hyper bien pensé et j&amp;rsquo;ai gagné beaucoup de temps par rapport à l&amp;rsquo;ancien éditeur de visualisation.&lt;/p&gt;
&lt;p&gt;Bravo Elastic pour ça.&lt;/p&gt;
&lt;p&gt;Ensuite, il y a l&amp;rsquo;ancienne façon de faire des visualisations, et là encore, pas de nuage de points&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/new_viz.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Enfin, il y a les TSVB mais c&amp;rsquo;est pour les timeseries (donc là ça va pas le faire), et&amp;hellip; Vega&amp;hellip;&lt;/p&gt;
&lt;h2 id="cest-quoi-vega-"&gt;C&amp;rsquo;est quoi, Vega ?
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Nouveauté dans Kibana 6.2 : vous pouvez désormais construire des visualisations riches Vega et Vega-Lite avec vos données Elasticsearch.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Vega-Lite is a high-level grammar of interactive graphics. It provides a concise, declarative JSON syntax to create an expressive range of visualizations for data analysis and presentation.
&amp;ndash; &lt;a class="link" href="https://vega.github.io/vega-lite/" target="_blank" rel="noopener"
&gt;https://vega.github.io/vega-lite/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Grosso modo, pour tout ce que Kibana ne propose pas nativement comme type de visualisation, on vous propose de vous orienter vers le langage Vega, qui permet de déclarer, en JSON, des graphiques assez complexes et spécifiques à votre besoin.&lt;/p&gt;
&lt;p&gt;Cool ! Reste plus qu&amp;rsquo;à trovuer comment ça fonctionne ce biniou, et surtout, comment ça s&amp;rsquo;interface avec les données dans mon ElasticSearch&amp;hellip;&lt;/p&gt;
&lt;p&gt;La première chose que j&amp;rsquo;ai cherché, c&amp;rsquo;est donc des exemples de nuages de points en Vega. Sauf que&amp;hellip; je n&amp;rsquo;avais aucune idée de comment dire &amp;ldquo;nuages de points&amp;rdquo; en anglais (la recherche en Français n&amp;rsquo;ayant rien donné). Et non, ce n&amp;rsquo;est pas &amp;ldquo;cloud dots&amp;rdquo;&amp;hellip;&lt;/p&gt;
&lt;p&gt;Une fois que je savais que c&amp;rsquo;était un &amp;ldquo;bubble chart&amp;rdquo;, c&amp;rsquo;était tout de suite beaucoup plus simple ;-) et j&amp;rsquo;ai trouvé un tuto qui commençait à ressembler à ce que je voulais faire (même si au final ce que j&amp;rsquo;ai fais ne ressemble pas du tout).&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.bigdataboutique.com/2021/04/bubble-chart-in-kibana-with-vega-5il1u2" target="_blank" rel="noopener"
&gt;Bubble Chart in Kibana with Vega&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="ca-ressemble-à-quoi-vega-"&gt;Ca ressemble à quoi, Vega ?
&lt;/h2&gt;&lt;p&gt;Si vous cliquez sur &amp;ldquo;Custom Visualization&amp;rdquo;, vous allez tomber sur cette page dans votre Kibana :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/vega1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A gauche, un aperçu de ce que vous êtes en train de faire, et à droite, le code JSON associé.&lt;/p&gt;
&lt;p&gt;Il y a pas mal de gloubiboulga mais vous allez vite vous rendre compte que c&amp;rsquo;est surtout des commentaires pour vous aider à comprendre ce que vous êtes censé écrire pour que ça fonctionne.&lt;/p&gt;
&lt;p&gt;Si vous enlevez tous les commentaires, vous devriez tomber sur ça :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;$schema:&lt;/span&gt; &lt;span class="err"&gt;https:&lt;/span&gt;&lt;span class="c1"&gt;//vega.github.io/schema/vega-lite/v5.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;title:&lt;/span&gt; &lt;span class="err"&gt;Event&lt;/span&gt; &lt;span class="err"&gt;counts&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;indexes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;url:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;%context%:&lt;/span&gt; &lt;span class="err"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;%timefield%:&lt;/span&gt; &lt;span class="err"&gt;@timestamp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;index:&lt;/span&gt; &lt;span class="err"&gt;_all&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;body:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;aggs:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;time_buckets:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;date_histogram:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;@timestamp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;interval:&lt;/span&gt; &lt;span class="err"&gt;{%autointerval%:&lt;/span&gt; &lt;span class="err"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;extended_bounds:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;min:&lt;/span&gt; &lt;span class="err"&gt;{%timefilter%:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;min&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;max:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%timefilter%:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;max&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;min_doc_count:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;format:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;property:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;aggregations.time_buckets.buckets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="err"&gt;line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;encoding:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;x:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type:&lt;/span&gt; &lt;span class="err"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis:&lt;/span&gt; &lt;span class="err"&gt;{title:&lt;/span&gt; &lt;span class="err"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;y:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;doc_count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type:&lt;/span&gt; &lt;span class="err"&gt;quantitative&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis:&lt;/span&gt; &lt;span class="err"&gt;{title:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Document count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;La difficulté de faire du Vega dans Kibana a été pour moi que je découvrais 2 choses en même temps :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;requêter elastic depuis Kibana avec des notions d&amp;rsquo;aggregations, buckets, etc que je ne maitrisais pas du tout&lt;/li&gt;
&lt;li&gt;découvrir le langage Vega&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/as-you-can-see-i-totally-know-what-im-doing.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="choisir-lindex"&gt;Choisir l&amp;rsquo;index
&lt;/h2&gt;&lt;p&gt;La première chose qu&amp;rsquo;on remarque, c&amp;rsquo;est que les données récupérées d&amp;rsquo;ElasticSearch et donné à manger à Kibana proviennent de TOUS nos index car on a la ligne &lt;code&gt;index: _all&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;data:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;url:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;%context%:&lt;/span&gt; &lt;span class="err"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;%timefield%:&lt;/span&gt; &lt;span class="err"&gt;@timestamp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;index:&lt;/span&gt; &lt;span class="err"&gt;_all&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Clairement, ça va pas le faire ;-) du coup on restreint à l&amp;rsquo;index qui nous intéresse (okiwi-2022 dans mon cas)&lt;/p&gt;
&lt;h2 id="requêter-elastic-avec-des-aggrégations"&gt;Requêter Elastic avec des aggrégations
&lt;/h2&gt;&lt;p&gt;Le deuxième gros morceau à comprendre, c&amp;rsquo;est la partie interrogation de notre ElasticSearch&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;body:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;aggs:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;time_buckets:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;date_histogram:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;@timestamp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;interval:&lt;/span&gt; &lt;span class="err"&gt;{%autointerval%:&lt;/span&gt; &lt;span class="err"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;extended_bounds:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;min:&lt;/span&gt; &lt;span class="err"&gt;{%timefilter%:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;min&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;max:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%timefilter%:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;max&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;min_doc_count:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ici, l&amp;rsquo;exemple fait un graphique en fonction du temps (x-axis). Les données sont regroupées avec une aggrégation de type &lt;code&gt;date_histogram&lt;/code&gt;, avec un intervalle automatique et en se basant sur le timestamp de nos entrée dans ElasticSearch.&lt;/p&gt;
&lt;p&gt;Ce que j&amp;rsquo;ai mis un bon moment à comprendre (car la doc est pas ouf non plus et j&amp;rsquo;ai perdu beaucoup de temps dessus) c&amp;rsquo;est qu&amp;rsquo;on a pas forcément BESOIN de faire des aggrégations.&lt;/p&gt;
&lt;p&gt;Les aggregations possibles, si vous, vous en avez besoin, sont disponibles ici&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html" target="_blank" rel="noopener"
&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html" target="_blank" rel="noopener"
&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typiquement, dans mon cas, je ne voulais pas afficher un point (genre la moyenne) pour un groupe de personne de mon sondage, mais bien un point par personne !&lt;/p&gt;
&lt;h2 id="requêter-elastic-sans-aggrégation"&gt;Requêter Elastic sans aggrégation
&lt;/h2&gt;&lt;p&gt;Pas besoin d&amp;rsquo;aggrégation donc&amp;hellip;&lt;/p&gt;
&lt;p&gt;Il m&amp;rsquo;a fallu un bon moment pour trouver car tous les exemples que j&amp;rsquo;ai trouvé sur Internet passaient TOUS par des aggrégations préalables, mais j&amp;rsquo;ai fini par trouver comment juste faire une requête à ElasticSearch depuis Kibana de la façon suivante (depuis les &amp;ldquo;Dev Tools&amp;rdquo; ou bien un cURL) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;okiwi&lt;/span&gt;&lt;span class="mi"&gt;-2022&lt;/span&gt;&lt;span class="err"&gt;/_search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;size&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nombre d&amp;#39;années d&amp;#39;expérience&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Salaire *partie fixe BRUT* (€) annuel (ex_ 30000)_ &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Type d&amp;#39;entreprise&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ici, je requête l&amp;rsquo;index okiwi-2022 et je lui demande 10 résultats contenant les propriétés &amp;ldquo;Nombre d&amp;rsquo;années d&amp;rsquo;expérience&amp;rdquo;, &amp;ldquo;Salaire &lt;em&gt;partie fixe BRUT&lt;/em&gt; (€) annuel (ex_ 30000)_ &amp;ldquo;, &amp;ldquo;Type d&amp;rsquo;entreprise&amp;rdquo; de mes documents.&lt;/p&gt;
&lt;p&gt;Une fois que je savais ça, j&amp;rsquo;ai juste testé dans Vega&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;body:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nombre d&amp;#39;années d&amp;#39;expérience&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Salaire *partie fixe BRUT* (€) annuel (ex_ 30000)_ &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Type d&amp;#39;entreprise&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bon ok, ça marchait toujours pas 😭😭😭 mais j&amp;rsquo;avais progressé.&lt;/p&gt;
&lt;p&gt;La &lt;em&gt;blague&lt;/em&gt;, c&amp;rsquo;est que le JSON renvoyé par Elasticsearch est un poil différent en fonction du fait que vous utilisiez ou non une aggregation.&lt;/p&gt;
&lt;p&gt;Dans le premier cas, les données sont encapsulées dans &lt;code&gt;aggregations.time_buckets.buckets&lt;/code&gt;, d&amp;rsquo;où la ligne suivante, en dessous du bloc &amp;ldquo;data&amp;rdquo;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;format:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;property:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;aggregations.time_buckets.buckets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans le cas où vous avez une requête sans aggrégation, il suffit &amp;ldquo;juste&amp;rdquo; de remplacer par&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;format:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;property:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;hits.hits&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="le-graphique-en-lui-même"&gt;Le graphique en lui même
&lt;/h2&gt;&lt;p&gt;OK, maintenant, on a les données, on peut faire du Vega purement cosmétique.&lt;/p&gt;
&lt;p&gt;La première chose que j&amp;rsquo;ai voulu changer, c&amp;rsquo;est que dans l&amp;rsquo;exemple donné quand on créé le graphique, on a des traits. Or, moi je veux des points.&lt;/p&gt;
&lt;p&gt;Ca, c&amp;rsquo;est géré par la ligne :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="err"&gt;line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On remplace ça par :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;point&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et puis pour que le graphique soit plus &amp;ldquo;user friendly&amp;rdquo;, on peut activer les &lt;code&gt;tooltips&lt;/code&gt; pour voir les détails du point quand on passe la souris dessus&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;point&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;tooltip&amp;#34;&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&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et puis comme les points sont trop petits (30 de base), on peut grossir un peu&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;point&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;tooltip&amp;#34;&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="err"&gt;size:&lt;/span&gt; &lt;span class="err"&gt;80&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="les-axes"&gt;Les axes
&lt;/h2&gt;&lt;p&gt;Normalement notre graphique est pété depuis nos premières modifs car l&amp;rsquo;exemple donné par Kibana se base sur des axes temporels et des données récupérées depuis une aggrégation et qu&amp;rsquo;on a viré tout ça.&lt;/p&gt;
&lt;p&gt;Il est donc grand temps de rebosser sur les axes et surtout où on place nos points (en fonction de quels axes).&lt;/p&gt;
&lt;p&gt;Cette partie est gérée par le bloc &lt;code&gt;encoding&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;encoding:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;x:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type:&lt;/span&gt; &lt;span class="err"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis:&lt;/span&gt; &lt;span class="err"&gt;{title:&lt;/span&gt; &lt;span class="err"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;y:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="err"&gt;doc_count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type:&lt;/span&gt; &lt;span class="err"&gt;quantitative&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis:&lt;/span&gt; &lt;span class="err"&gt;{title:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Document count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans mon exemple, je veux afficher le salaire des gens en fonction de leur expérience. Mon axe des X est donc le nombre d&amp;rsquo;années d&amp;rsquo;expérience et celui des y est donc le salaire.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;x:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;_source.Nombre d&amp;#39;années d&amp;#39;expérience&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;quantitative&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;title:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Années d&amp;#39;expériences&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;y:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;field:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;_source.Salaire *partie fixe BRUT* (€) annuel (ex_ 30000)_ &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;quantitative&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;title:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Salaire brut&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour faire joli, je leur ai donné un titre avec &lt;code&gt;axis: {title: &amp;quot;&amp;quot;}&lt;/code&gt;, c&amp;rsquo;est quand même plus sympa que le nom dégueulasse que j&amp;rsquo;ai importé du google forms en CSV ;-). Dans les deux cas, mes axes sont &amp;ldquo;quantitatifs&amp;rdquo; et non plus &amp;ldquo;temporels&amp;rdquo;. La documentation Vega sur les axes &lt;a class="link" href="https://vega.github.io/vega/docs/axes/" target="_blank" rel="noopener"
&gt;est disponible ici&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Une fois que tout ça est fait, ça devrait donner un truc qui ressemble grosso modo à ça (la requête à droite est pas la bonne mais le résultat est le même):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/bubble_chart_1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="rendre-ça-joli"&gt;Rendre ça joli
&lt;/h2&gt;&lt;p&gt;On n&amp;rsquo;est plus très loin de ce que Frédéric m&amp;rsquo;a proposé en début d&amp;rsquo;aticle. Reste maintenant à discriminer visuellement les données en fonction du type de société dans laquelle le salarié est embauché.&lt;/p&gt;
&lt;p&gt;Pour ça, on va changer la forme ET la couleur du point en fonction d&amp;rsquo;une 3ème donnée : son type d&amp;rsquo;entreprise.&lt;/p&gt;
&lt;p&gt;Bon, je vais le dire tout de suite, j&amp;rsquo;ai passé pas mal de temps sur cette partie et je ne suis pas réussi à faire ce que je voulais malgré plusieurs essais&amp;hellip;&lt;/p&gt;
&lt;p&gt;La méthode pour faire ça est d&amp;rsquo;utiliser les valeurs &lt;code&gt;shape&lt;/code&gt; et &lt;code&gt;color&lt;/code&gt; dans &lt;code&gt;encoding&lt;/code&gt;. Là où Vega est plutôt malin, c&amp;rsquo;est que si vous mettez la même valeur comme &lt;code&gt;field&lt;/code&gt; dans &lt;code&gt;shape&lt;/code&gt; et &lt;code&gt;color&lt;/code&gt;, les deux sont &amp;ldquo;merge&amp;rdquo; dans la légende, comme le montre cet exemple de la doc de Vega&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/vega_ordinal_scales.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://vega.github.io/vega-lite/docs/scale.html#ordinal" target="_blank" rel="noopener"
&gt;https://vega.github.io/vega-lite/docs/scale.html#ordinal&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;shape:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_source.Type d&amp;#39;entreprise&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nominal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;color:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_source.Type d&amp;#39;entreprise&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nominal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sauf que les couleurs choisies par Kibana (qui utilise sa propre scale) sont plus que bof&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/bublle_couleur_par_defaut.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;En relisant la doc, on voit qu&amp;rsquo;on devrait pouvoir choisir notre propre scale&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The range of an ordinal scale can be an array of desired output values, which are directly mapped to elements in the domain. Both domain and range array can be re-ordered to specify the order and mapping between the domain and the output range. For ordinal color scales, a custom scheme can be set as well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sauf que tous mes tests se sont révélés être des échecs :-( Au mieux, j&amp;rsquo;ai réussi à le faire, mais en pétant la légende.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;color:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;mark:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;point&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;tooltip&amp;#34;&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="err"&gt;size:&lt;/span&gt; &lt;span class="err"&gt;60&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nominal&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;scale&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;domain:&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;Association&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;ESN/SSII&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Editeur&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Entreprise non numérique&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;Public&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rgb(230,230,31)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgb(255,0,0)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgb(0,191,0)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgb(31,31,255)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgb(191,31,191)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/12/bublle_couleur_ameliore.PNG"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Ce &amp;ldquo;besoin&amp;rdquo; de faire un graphique non prévu &amp;ldquo;out of the box&amp;rdquo; dans Kibana m&amp;rsquo;aura permi de découvrir plusieurs choses. D&amp;rsquo;abord, les différentes façon de requêter ElasticSearch car je n&amp;rsquo;avais jamais eu besoin de le faire (au delà de sortir les documents entier avec un bête &lt;code&gt;index/_search&lt;/code&gt; sans filtres).&lt;/p&gt;
&lt;p&gt;Ensuite, j&amp;rsquo;ai réussi à faire un graphique approchant de ce que je voulais faire.&lt;/p&gt;
&lt;p&gt;Et enfin, ça m&amp;rsquo;a surtout permis de voir que je pouvais totalement me passer de Kibana pour faire des visulisations jolies.&lt;/p&gt;
&lt;p&gt;Car beaucoup de gens qui font du Vega utilisent tout simplement une URL pointant vers un CSV et c&amp;rsquo;est totalement suffisant pour afficher des graphiques sur Internet&amp;hellip; On perd le côté interactif bien sûr, mais pour les cas où j&amp;rsquo;ai besoin de quelque chose de simple, ça me suffira largement.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mise à jour mars 2026&lt;/strong&gt; : le serveur Kibana a été décommissionné. Les dashboards interactifs ne sont plus disponibles. Les captures d&amp;rsquo;écran et les exports de données sont consultables dans &lt;a class="link" href="https://blog.zwindler.fr/2026/03/25/decomission-kibana-zwindler-fr-sondages-okiwi" &gt;l&amp;rsquo;article dédié à la décommission&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Have fun :-)&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/fr/blog/getting-started-with-vega-visualizations-in-kibana" target="_blank" rel="noopener"
&gt;https://www.elastic.co/fr/blog/getting-started-with-vega-visualizations-in-kibana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://vega.github.io/vega-lite/examples/circle_bubble_health_income.html" target="_blank" rel="noopener"
&gt;https://vega.github.io/vega-lite/examples/circle_bubble_health_income.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/fr/blog/custom-vega-visualizations-in-kibana" target="_blank" rel="noopener"
&gt;https://www.elastic.co/fr/blog/custom-vega-visualizations-in-kibana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/kibana/6.7/vega-querying-elasticsearch.html" target="_blank" rel="noopener"
&gt;https://www.elastic.co/guide/en/kibana/6.7/vega-querying-elasticsearch.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://discuss.elastic.co/t/bubble-chart-with-ratios/185128" target="_blank" rel="noopener"
&gt;https://discuss.elastic.co/t/bubble-chart-with-ratios/185128&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Mise en place d’une astreinte OPS – partie 2</title><link>https://blog.zwindler.fr/2020/08/24/mise-en-place-dune-astreinte-ops-partie-2/</link><pubDate>Mon, 24 Aug 2020 06:05:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/08/24/mise-en-place-dune-astreinte-ops-partie-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/07/on-call-helmet.webp" alt="Featured image of post Mise en place d’une astreinte OPS – partie 2" /&gt;&lt;h2 id="jpeux-pas-jsuis-dastreinte"&gt;&amp;ldquo;J’peux pas, j’suis d’astreinte&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Voici la seconde partie de mon (gros) article dédié à la mise en place d’une astreinte, dans le cadre du maintien en conditions opérationnelles d’un service informatique. Dans la première partie (&lt;a class="link" href="https://blog.zwindler.fr/2020/08/17/mise-en-place-dune-astreinte-partie-1/" &gt;disponible ici si vous l’avez loupé&lt;/a&gt;), j’ai introduis ce qu’était réellement une astreinte, pourquoi on en met en place et enfin les textes en vigueur.&lt;/p&gt;
&lt;p&gt;Dans cette deuxième partie, je vais donc rentrer un peu plus dans le concret.&lt;/p&gt;
&lt;p&gt;Pour rappel, il y a 2 ans, j’ai intégré une équipe d’ingénieurs systèmes cloud qui venait de se créer. Quand les premiers produits et les premiers clients sont arrivés en production, le besoin d’assurer la continuité d’activité s’est fait sentir. Et donc, par extension, le besoin d’une astreinte.&lt;/p&gt;
&lt;p&gt;J’ai donc eu la chance de pouvoir participer, étant directement concerné (et surtout un peu renseigné sur le sujet) à l’élaboration de cette astreinte. Ça a été l’occasion de traiter directement les aspects suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;problématiques droit du travail&lt;/li&gt;
&lt;li&gt;organisationnel&lt;/li&gt;
&lt;li&gt;implémentation technique&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="concevoir-son-astreinte"&gt;Concevoir son astreinte
&lt;/h2&gt;&lt;h3 id="comment-organiser-les-astreintes-"&gt;Comment organiser les astreintes ?
&lt;/h3&gt;&lt;p&gt;Après la compensation (récupération ou rémunération), c’est souvent LE gros sujet quand on met en place une astreinte.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, il n’est pas souhaitable de mettre 365j/an une seule et même personne d’astreinte ; il faut définir un roulement.&lt;/p&gt;
&lt;p&gt;Pour le roulement, est-ce qu’une même personne est d’astreinte toute la semaine ? Seulement quelques jours ? Est-ce que tous les jours, on change ?&lt;/p&gt;
&lt;p&gt;Dans le premier cas, les périodes d’astreinte sont plus espacées, mais plus longues (et potentiellement génératrices de plus de fatigue). Dans le dernier, même en cas de semaine chaotique, le changement régulier permet de mieux répartir la fatigue entre astreinteurs, mais on est très souvent d’astreinte (pendant de courtes périodes).&lt;/p&gt;
&lt;p&gt;Est-ce que l’astreinte va concerner une journée complète de 24h, ou au contraire, considère-t-on que la journée l’équipe d’exploitation peut gérer les incidents, et l’astreinte à juste vocation à prolonger les horaires de bureaux ?&lt;/p&gt;
&lt;p&gt;Ces questions ne sont pas anodines.&lt;/p&gt;
&lt;p&gt;Au-delà de l’impact que le fait d’être d’astreinte a sur la vie privée (on ne peut pas faire autant de choses que l’on veut quand on est d’astreinte, c’est pour ça qu’on est compensé&amp;hellip;), cela peut avoir des effets très importants sur l’organisation de l’équipe.&lt;/p&gt;
&lt;h3 id="pourquoi-ça-peut-coincer-"&gt;Pourquoi ça peut coincer ?
&lt;/h3&gt;&lt;p&gt;Pour expliciter un peu où je veux en venir, prenons un exemple. Imaginons qu’une équipe de 4 OPS décide de créer une astreinte informatique 7j/7. Les incidents de la journée sont traités par l’équipe et en dehors des horaires de bureau (18h =&amp;gt; 9h le lendemain), l’astreinteur prend le relais.&lt;/p&gt;
&lt;p&gt;L’astreinteur prend son astreinte le vendredi 18h. Pas de chance, le week end est chaotique ! Des appels ont lieu le samedi et le dimanche, nécessitant des interventions à chaque fois.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/astreinte_cas1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A aucun moment du week end, l’astreinteur n’a eu ses 35h de repos hebdomadaire consécutives.&lt;/p&gt;
&lt;p&gt;Dans ce cas un peu extrême (mais vécu IRL), le code du travail l&amp;rsquo;empêche de reprendre le travail que mardi matin. Son absence lundi provoque un déséquilibre dans l’organisation de l’équipe&amp;hellip;&lt;/p&gt;
&lt;p&gt;Le problème ne se limite évidemment pas au week end. On peut avoir le genre de problèmes si les appels d’astreinte ont lieu la nuit (un à 23h, l’autre à 5h par exemple).&lt;/p&gt;
&lt;p&gt;Et le problème est exacerbé si en plus, les appels continuent de pleuvoir pendant la journée. Dans ce cas, si on continue à avoir des alertes en journée, le compteur de repos n’arrive jamais à 11h.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/astreinte_cas2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Quelle que soit l’organisation que vous choisissez, il est dans tous les cas impératif de se débrouiller pour que les appels arrivent le moins souvent possible (travailler pour réduire les alertes intempestives).&lt;/p&gt;
&lt;p&gt;Il n’y a qu’en travaillant sur la réduction du nombre d’incidents qu’on peut réussir à impacter le moins possible l’organisation de l’équipe &amp;ldquo;de jour&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="quel-outillage-pour-réussir-son-astreinte-"&gt;Quel outillage pour réussir son astreinte ?
&lt;/h2&gt;&lt;p&gt;Je n’imagine pas une seule seconde une astreinte où l’on aurait pas &amp;ldquo;le kit de l’astreinteur&amp;rdquo;. Sauf à imposer à l’astreinteur d’être assigné à résidence, ce qui va à l’encontre de la définition de l’astreinte je pense, il est nécessaire pour l’astreinteur OPS d’avoir :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PC portable&lt;/li&gt;
&lt;li&gt;smartphone&lt;/li&gt;
&lt;li&gt;connexion 4G de qualité (je résiste à insérer un troll 5G).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces 3 items fonctionnent ensemble. Le smartphone permet de recevoir les appels et les alertes automatiques (via la 4G). Le PC portable permet d’y remédier en se connectant rapidement sur l’infra, via le modem 4G du smartphone.&lt;/p&gt;
&lt;p&gt;On pourrait également considérer qu’un astreinteur pouvant être seul, il est nécessaire qu’il dispose d’un &lt;a class="link" href="https://fr.wikipedia.org/wiki/Protection_du_travailleur_isol%C3%A9" target="_blank" rel="noopener"
&gt;PTI/DATI (protection travailleur isolé)&lt;/a&gt;. Ça serait particulièrement vrai dans le cas où l’astreinte nécessite des interventions sur site (en DC par exemple), où un accident, hors des horaires de bureaux, pourrait être dramatique.&lt;/p&gt;
&lt;p&gt;Bon, ça, c’est le strict minimum.&lt;/p&gt;
&lt;p&gt;Si on se contente de ça, on va avoir une astreinte qui subit. Les astreinteurs seront prévenus des incidents par les clients (ou le management) et il sera parfois trop tard pour réparer la situation.&lt;/p&gt;
&lt;p&gt;Mettre en place une astreinte efficace nécessite donc quasi obligatoirement d’avoir une plateforme de supervision la plus complète et la plus pertinente possible&lt;/p&gt;
&lt;h3 id="surveiller-et-alerter-en-cas-de-problème"&gt;Surveiller et alerter en cas de problème
&lt;/h3&gt;&lt;p&gt;D’abord, ça permet de ne plus subir.&lt;/p&gt;
&lt;p&gt;L’astreinteur est prévenu de manière automatique qu’un incident est en cours. Ça permet de retirer un facteur humain dans la chaîne d’intervention (attendre que quelqu’un se plaigne du souci et pense à prévenir la bonne personne) et donc de gagner du temps.&lt;/p&gt;
&lt;p&gt;Parfois, on peut même être prévenu &lt;strong&gt;avant&lt;/strong&gt; la catastrophe (eh, ho, ton disque il est bientôt plein là !).&lt;/p&gt;
&lt;p&gt;On devient donc proactif.&lt;/p&gt;
&lt;p&gt;Ensuite, ça permet, en cas d’incident grave qu’on a pas pu éviter, de savoir exactement ce qui s’est passé et quand. Sans supervision (ou sans métriques pertinentes), impossible de faire un diagnostic correct de ce qui s’est passé et d’engager des actions correctives pour que ça ne se reproduise plus (post-mortem).&lt;/p&gt;
&lt;p&gt;Toute la difficulté de l’exercice repose donc dans l’exhaustivité et la pertinence des métriques MAIS la parcimonie des alertes. Pas assez d’alertes, c’est des clients mécontents, mais trop d’alertes, c’est pire&amp;hellip; l’astreinteur sera sollicité trop souvent&amp;hellip;&lt;/p&gt;
&lt;h3 id="alert-fatigue"&gt;Alert fatigue
&lt;/h3&gt;&lt;p&gt;Au delà de la simple fatigue provoqué par trop d’alertes (et des conséquences que ça peut avoir dans l’équipe comme je l’ai exposé plus haut), ce surplus a un autre effet pervers.&lt;/p&gt;
&lt;p&gt;S’ils sont constamment noyés dans des alertes, les astreinteurs vont s’y habituer et cela va inexorablement les conduire à les ignorer. Ce ne sera pourtant pas par flemme ni par manque de professionnalisme.&lt;/p&gt;
&lt;p&gt;En fait, le phénomène est connu sous le terme d’&amp;ldquo;Alert fatigue&amp;rdquo;, que vous connaissez peut-être mieux depuis votre tendre enfant au travers la fable du garçon criant au loup.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Alarm fatigue or alert fatigue occurs when one is exposed to a large number of frequent alarms (alerts) and consequently becomes desensitized to them. Desensitization can lead to longer response times or missing important alarms. [https://en.wikipedia.org/wiki/Alarm_fatigue](Page wikipedia de l’Alarm fatigue)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Concrètement, dans le cas des alertes automatiques, trop d’alertes risque de conduire le cerveau des administrateurs à ignorer un problème important en pensant que c’ est &amp;ldquo;une erreur normale, habituelle&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Il va donc être crucial de n’alerter l’administrateur en astreinte que quand c’est réellement nécessaire.&lt;/p&gt;
&lt;h2 id="implémenter-lastreinte"&gt;Implémenter l’astreinte
&lt;/h2&gt;&lt;p&gt;Maintenant qu’on a bien les idées claires sur ce qu’on doit mettre en place, je vous présente maintenant des solutions que nous avons envisagé (voire retenu).&lt;/p&gt;
&lt;p&gt;Cela n’a pas vocation a être une réponse universelle, mais ça convient aux besoins et aux contraintes que nous avions.&lt;/p&gt;
&lt;h3 id="lorganisation-de-lastreinte"&gt;L’organisation de l’astreinte
&lt;/h3&gt;&lt;p&gt;L’idée était de trouver un compromis entre :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fatigue&lt;/li&gt;
&lt;li&gt;risque d’absence (à cause du repos quotidien ou hebdomadaire)&lt;/li&gt;
&lt;li&gt;répétitions pas trop régulières&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour toutes les raisons que j’ai cité dans le chapitre sur la conception, le roulement qui nous a paru le plus pertinent, et que nous avons mis en place est le suivant :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/astreinte_planning.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Cette organisation permet de garantir que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;si jamais le repos hebdo n’a pas pu être pris en entier, le changement d’astreinteur le lundi lui permet de se reposer&lt;/li&gt;
&lt;li&gt;les appels en journée ne gênent pas la prise du repos quotidien au cas où il n’a pas pu être pris pendant la nuit précédente&lt;/li&gt;
&lt;li&gt;A 5, on a en moyenne 2 semaines sans aucune astreinte, puis une période de 3 ou 4 jours&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ceci permet de garantir donc à la fois les repos et nous parait être un bon compromis entre fréquence et durée des astreintes.&lt;/p&gt;
&lt;h3 id="les-outils-de-la-chaîne-de-supervision"&gt;Les outils de la chaîne de supervision
&lt;/h3&gt;&lt;p&gt;Le choix de l’outil dépendra obligatoirement du contexte. Si vous travaillez dans une équipe réseau, vous n’aurez pas les mêmes besoins et donc pas les mêmes outils qu’une équipe d’exploitation cloud.&lt;/p&gt;
&lt;p&gt;Sans citer directement le nom des produits que nous utilisons, je vais vous donner une liste des types d’outils que nous avons mis en place :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un groupe de serveurs &lt;a class="link" href="https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/" &gt;Prometheus + Thanos&lt;/a&gt; pour collecter et stocker les métriques de nos applications dans Kubernetes, mais aussi des services de notre cloud provider (IaaS, SaaS, DBaaS) et des middlewares que nous gérons nous-même (message brokers, bases NoSQL)&lt;/li&gt;
&lt;li&gt;Un outil d’alerting, AlertManager, le composant d’Alerting de Prometheus&lt;/li&gt;
&lt;li&gt;Une plateforme de visualisation &lt;a class="link" href="https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/" &gt;Grafana&lt;/a&gt; qui nous permet de visualiser les métriques provenant de différentes sources (majoritairement Prometheus, mais aussi les métriques du cloud providers)&lt;/li&gt;
&lt;li&gt;Une plateforme pour centraliser les logs des applications (ex. Splunk/&lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=ElasticStack" &gt;ElasticStack&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Un outil de supervision externe (ex. Pingdom/StatusCake) pour visualiser l’accès aux services web que nous exposons sur Internet depuis plusieurs points dans le monde&lt;/li&gt;
&lt;li&gt;Un outil d’APM (Application Performance Monitoring, ex. AppDynamics/Dynatrace) pour valider que le ressenti des utilisateurs ne se dégrade pas (plus pernicieux que la coupure franche)&lt;/li&gt;
&lt;li&gt;Un outil pour communiquer en temps réel avec les équipes (chat+audio+visio, de type Teams/Slack/Discord).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour le dernier point, Slack nous était tellement utile pour gérer les incidents que j’ai même créé un bot pour créer des channels dédiés pour chaque incident et y inviter les personnes concernées (managers, ops, call center). Si ça vous intéresse, &lt;a class="link" href="https://github.com/zwindler/redalert" target="_blank" rel="noopener"
&gt;ça s’appelle redalert, c’est open source&lt;/a&gt; et j’en parle dans cet article :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2020/06/15/redalert-gerer-des-incidents-avec-un-bot-slack/" &gt;redalert : gérer les incidents avec un bot Slack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="one-tool-to-rule-them-all"&gt;One tool to rule them all
&lt;/h3&gt;&lt;p&gt;Comme vous pouvez le voir, ça fait quand même beaucoup de types d’outils différents. Et même si les éditeurs tentent de vous convaincre que vous pouvez tout faire avec un seul outil, c’est probablement faux dès que votre contexte est un peu complexe.&lt;/p&gt;
&lt;p&gt;Cependant, pour éviter de se retrouver avec un trop grand nombre de sources de données distinctes, le mieux est de remonter toutes les alertes vers une seule et même plateforme :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un outil de réponse aux incidents (tel que OpsGenie/PagerDuty par exemple). L’outil de réponse aux incidents permet de gérer les rotations de notre équipe d’astreinte, l’éventuelle escalade, d’avoir des métriques de base sur les incidents, leur provenance et leur durée, etc. Mais le plus important : de notifier la personne d’astreinte sur différents types de canaux (notification push sur smartphone, SMS, appel vocal via TTS, slack, &amp;hellip;), de manière entièrement configurable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cet outil est vraiment la pièce maîtresse, qui apporte la cohérence à tout le reste. Choisissez donc le bien !&lt;/p&gt;
&lt;h2 id="quest-ce-quil-manque-"&gt;Qu’est ce qu’il manque ?
&lt;/h2&gt;&lt;p&gt;Vous aurez très certainement besoin de sortir des informations (dashboards, statistiques) sur les incidents du mois (pour suivre la charge, la fatigue des équipes, gérer la paie si les heures d’intervention sont payées ou récupérées).&lt;/p&gt;
&lt;p&gt;Généralement, on peut utiliser le reporting intégré à l’outil de réponse aux incidents, mais c’est souvent assez pauvre.&lt;/p&gt;
&lt;p&gt;Comme nous voulions pouvoir faire des stats et &amp;ldquo;déclarer&amp;rdquo; les durées des astreintes ainsi que leur nombre aux RHs (pour calculer les récupérations), nous avons pris le parti de tenir à jour nous-même un fichier des interventions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/astreinte_saisie.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Cette feuille de calcul, assez riche (avec beaucoup de macros et de formules magiques), nous permet d’avoir toutes les données pour ensuite calculer tous les indicateurs dont nous avons besoin.&lt;/p&gt;
&lt;p&gt;Actuellement, la seule chose qui pourrait manquer est un outil pour calculer si le repos quotidien/hebdomadaire a été respecté ou non.&lt;/p&gt;
&lt;p&gt;Ceci pourrait être fait via la feuille de calcul (puisqu’on a toutes les infos) mais n’est pas très user friendly (ni trivial à implémenter).&lt;/p&gt;
&lt;p&gt;J’ai demandé sur Twitter s’il existait un outil pour faire ça, mais a priori rien n’existe &amp;ldquo;out of the box&amp;rdquo;&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/tweet_astreinte.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="et-vous-vous-faites-comment-"&gt;Et vous, vous faites comment ?
&lt;/h2&gt;&lt;p&gt;Enfin fini ! Je suis bavard, je sais ;). Mais comme vous avez pu le voir, c’est un sujet aussi complet que complexe.&lt;/p&gt;
&lt;p&gt;Cependant, ces deux posts n’étaient que ma vision propre de l’astreinte. Je suis sûr que vous avez vous aussi des besoins et des contraintes différentes.&lt;/p&gt;
&lt;p&gt;Donc vraiment (encore plus que d’habitude) n’hésitez pas à utiliser les commentaires pour donner votre avis et nous parler de votre propre organisation.&lt;/p&gt;
&lt;p&gt;Ça pourra en aider d’autres :).&lt;/p&gt;</description></item><item><title>[Update] Les salaires de l’IT dans un Kibana</title><link>https://blog.zwindler.fr/2020/07/10/les-salaires-de-lit-dans-un-kibana/</link><pubDate>Fri, 10 Jul 2020 18:46:29 +0000</pubDate><guid>https://blog.zwindler.fr/2020/07/10/les-salaires-de-lit-dans-un-kibana/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/07/shirley2-2.webp" alt="Featured image of post [Update] Les salaires de l’IT dans un Kibana" /&gt;&lt;h2 id="update"&gt;Update
&lt;/h2&gt;&lt;p&gt;Shirley a eu la gentillesse de rajouter de nouvelles valeurs dans son sondage. J’ai donc intégré les résultats dans le Kibana (après nettoyage).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/09/shirley-new.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Comme la dernière fois, n’hésitez pas à me demander les accès !&lt;/p&gt;
&lt;h2 id="étude-des-salaires-dans-lit-de-shirley-almosni-chiche"&gt;Étude des salaires dans l’IT de Shirley Almosni Chiche
&lt;/h2&gt;&lt;p&gt;Shirley Almosni Chiche (agent de carrière IT, pour ceux qui ne la connaissent pas encore) a lancé il y a quelques jours une étude des salaires en « Open Source », comme elle dit. Elle est limitée aux salariés et à la France.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/tweet_shirley.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;L’avantage de sa démarche, c’est que les résultats (dont les personnes ont accepté la diffusion) sont librement accessible sur un Google Docs !&lt;/p&gt;
&lt;h2 id="ça-vous-rappelle-pas-quelque-chose-"&gt;Ça vous rappelle pas quelque chose ?
&lt;/h2&gt;&lt;p&gt;Ceux qui me suivent sur LinkedIn se souviennent peut être que j’avais déjà participé à une étude sur les salaires dans l’IT (&lt;a class="link" href="https://okiwi.org/" target="_blank" rel="noopener"
&gt;association Okiwi&lt;/a&gt; cette fois là), mais limitée à la région bordelaise.&lt;/p&gt;
&lt;p&gt;Là aussi, les résultats étaient librement accessibles et j’avais utilisé les résultats pour alimenter un dashboard Kibana.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/okiwi.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Une capture d’écran d’une partie du dashboard Okiwi 2019/2020&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Et comme je suis quelqu’un de sympa, je n’avais pas gardé ça juste pour moi et avait mis à disposition le dashboard à tous ceux qui me l’avaient demandé.&lt;/p&gt;
&lt;h2 id="vous-me-voyez-venir"&gt;Vous me voyez venir&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Et oui, j’ai récidivé !&lt;/p&gt;
&lt;p&gt;J’ai une nouvelle fois exploité les données librement accessibles pour faire un nouveau dashboard, cette fois ci plus limité à la seule région bordelaise mais à toute la France (salariés uniquement) !&lt;/p&gt;
&lt;p&gt;Voici quelques screenshots de ce que je commence à avoir :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/09/shirley.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="pourquoi-kibana-"&gt;Pourquoi Kibana ?
&lt;/h2&gt;&lt;p&gt;Après tout, on aurait simplement pu se contenter d’utiliser le fichier sur Google Sheet et y ajouter des graphiques en sélectionnant les colonnes !&lt;/p&gt;
&lt;p&gt;En vrai, ça fonctionne très bien.&lt;/p&gt;
&lt;p&gt;Cependant (là encore c’est possible), ça devient plus compliqué de le rendre dynamique. Par exemple, si je ne veux sélectionner que les femmes dans mes graphiques, il faut utiliser des filtres, potentiellement des formules, si je veux quelque chose de plus poussé.&lt;/p&gt;
&lt;p&gt;Avec Kibana, tout est dynamique,nativement.&lt;/p&gt;
&lt;p&gt;Si je ne veux afficher que les réponses de femmes dans l’IT, il me suffit de simplement cliquer sur « femmes » dans mon donut des genres, et paf :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/07/shirley3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Idem si je veux faire une recherche uniquement sur les SRE, etc&amp;hellip;&lt;/p&gt;
&lt;p&gt;Vous l’aurez compris, c’est méga super trop chouette :D&lt;/p&gt;
&lt;h2 id="comment-y-avoir-accès-"&gt;Comment y avoir accès ?
&lt;/h2&gt;&lt;p&gt;Et c’est là où j’ai besoin de vous !&lt;/p&gt;
&lt;p&gt;Pourquoi ?&lt;/p&gt;
&lt;p&gt;Tout simplement parce que c’est étude ne valent pas grand chose si elles ne sont pas remplies par le plus grand nombre.&lt;/p&gt;
&lt;p&gt;En 3 jours, Shirley avait déjà récolté plus de 400 réponses (250 librement accessibles). C’est pas mal, mais pour être encore plus pertinents, il nous en faut plus.&lt;/p&gt;
&lt;p&gt;C’est pourquoi, si jamais vous voulez un accès à mon dashboard Kibana, je vous demanderai d’abord de remplir le questionnaire de Shirley en premier. Ce n’est pas très long et ça nous permettra à tous d’avoir les résultats les plus pertinents possibles.&lt;/p&gt;
&lt;p&gt;Et comme ça, tout le monde est gagnant :)&lt;/p&gt;
&lt;p&gt;Le formulaire :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.google.com/forms/d/e/1FAIpQLScMWthuEaxmP9zGBKzM0MOZmWOLT4hYO9ngKNiU6m53IogmZA/viewform" target="_blank" rel="noopener"
&gt;docs.google.com/forms/d/e/1FAIpQLScMWthuEaxmP9zGBKzM0MOZmWOLT4hYO9ngKNiU6m53IogmZA/viewform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et une fois rempli, si jamais vous voulez accéder au Kibana, n’hésitez pas à me demander les accès en m’envoyant un message sur &lt;del&gt;Twitter&lt;/del&gt; ou sur LinkedIn.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mise à jour mars 2026&lt;/strong&gt; : le serveur Kibana a été décommissionné. Les dashboards interactifs ne sont plus disponibles. Les captures d&amp;rsquo;écran et les exports de données sont consultables dans &lt;a class="link" href="https://blog.zwindler.fr/2026/03/25/decomission-kibana-zwindler-fr-sondages-okiwi" &gt;l&amp;rsquo;article dédié à la décommission&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Have fun !&lt;/p&gt;</description></item><item><title>Superviser votre instance Jitsi avec Prometheus et Grafana</title><link>https://blog.zwindler.fr/2020/06/08/superviser-votre-instance-jitsi-avec-prometheus-et-grafana/</link><pubDate>Mon, 08 Jun 2020 06:35:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/06/08/superviser-votre-instance-jitsi-avec-prometheus-et-grafana/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/04/10participants.webp" alt="Featured image of post Superviser votre instance Jitsi avec Prometheus et Grafana" /&gt;&lt;h2 id="quel-rapport-entre-jitsi-et-prometheus-"&gt;Quel rapport entre Jitsi et Prometheus ?
&lt;/h2&gt;&lt;p&gt;Si vous avez suivi un peu mes articles depuis le début du confinement, vous aurez vu qu’en ce moment je fais du Jitsi (cf &lt;a class="link" href="https://blog.zwindler.fr/2020/03/17/ta-visio-open-source-comme-un-pro-avec-jitsi/" &gt;Ta visio Open Source comme un pro avec Jitsi&lt;/a&gt;) et du Prometheus (cf &lt;a class="link" href="https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/" &gt;Découvrir Prometheus et Grafana par l’exemple&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Et ça tombe super bien, car aujourd’hui je vais vous parler des deux !&lt;/p&gt;
&lt;h2 id="lappel-des-chatons"&gt;L’appel des Chatons
&lt;/h2&gt;&lt;p&gt;J’ai pas trop communiqué là dessus, mais lorsque le confinement a commencé, les ENT étant down, beaucoup d’enseignants se sont tournés vers Framasoft, qui héberge entre autre des services d’éditions de texte en collaboratif ainsi que des instances de visio conférences Jitsi.&lt;/p&gt;
&lt;p&gt;Ça a pas mal râlé côté Framasoft car ça fait des années qu’ils expliquent qu’en tant qu’association 1901, ils n’ont ni les moyens ni l’envie de supporter les conséquences des mauvais choix technologiques / budgétaires de l’éducation nationale. (Si vous voyez pas trop où que je veux en venir, allez voir leur site, ils l’expliquent mieux que moi). Et de fermer ces deux services temporairement dans la foulée.&lt;/p&gt;
&lt;p&gt;Suite à quoi, les CHATONS se sont proposés de faire le relais en listant un ensemble de services Jitsi et Etherpad mis à dispositions par des CHATONS et des particuliers (et maintenant plus encore).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/04/framasoft_jitsi_chatons.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J’ai pas trop communiqué là dessus, mais moi aussi j’ai mis mon instance à dispo.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/04/chaton.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Coucouuuuuu, je suis là !!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="et-alors-des-gens-sen-servent-"&gt;Et alors, des gens s’en servent ?
&lt;/h2&gt;&lt;p&gt;C’est super cool, j’ai mis à dispo une instance jitsi qui a priori a été utile à plusieurs personnes.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/04/jitsi_croixrouge_be.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Mais jusqu’à présent, au delà du trafic CPU/réseau que je vois monter de temps en temps, je n’avais aucune idée de la quantité de conférences qui se tenaient sur mon instance.&lt;/p&gt;
&lt;p&gt;Puis, j’ai vu ce tweet de pyg, qui m’a relancé sur le sujet.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/04/pyg_jitsi.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ouuuaaaah, la classe :D&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A vue de nez, c’est du Grafana. J’ai donc voulu trouver comment brancher Jitsi à ma supervision existante.&lt;/p&gt;
&lt;h2 id="deux-exporters-pour-le-prix-dun"&gt;Deux exporters pour le prix d’un
&lt;/h2&gt;&lt;p&gt;La première chose à faire était donc de trouver un exporter prometheus compatible avec Jitsi, histoire de pouvoir capitaliser sur l’infrastructure (Grafana/Prom) actuelle.&lt;/p&gt;
&lt;p&gt;J’ai trouvé 2 projets en Go :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;jitsi-prom-exporter dont j’ai trouvé la trace sur &lt;a class="link" href="https://community.jitsi.org/t/monitoring-jvb-metrics-prometheus-grafana/19822" target="_blank" rel="noopener"
&gt;le forum de jitsi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/xsteadfastx/jitsiexporter" target="_blank" rel="noopener"
&gt;jitsiexporter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les deux n’étant pas franchement bien documentés (et je suis une quiche en Go), je me suis d’abord orienté vers jitsi-prom-exporter, plus &amp;ldquo;connu&amp;rdquo; (enfin, ça se joue à 10 étoiles sur Github hein&amp;hellip;).&lt;/p&gt;
&lt;p&gt;Mais je n’ai jamais réussi à le compiler (vive le Go) et comme je n’y comprend rien après avoir ragé quelques heures (ma femme confirme) j’ai laissé tombé. [Edit]En vrai j’ai fini par le faire marché avec de l’aide et beaucoup de trial and error mais bon&amp;hellip; Il faut faire &lt;a class="link" href="https://github.com/karrieretutor/jitsi-prom-exporter/issues/3#issuecomment-614512142" target="_blank" rel="noopener"
&gt;ça&lt;/a&gt;, &lt;a class="link" href="https://github.com/karrieretutor/jitsi-prom-exporter/issues/3#issuecomment-614651103" target="_blank" rel="noopener"
&gt;ça&lt;/a&gt; puis &lt;a class="link" href="https://github.com/karrieretutor/jitsi-prom-exporter/issues/3#issuecomment-614909928" target="_blank" rel="noopener"
&gt;ça&lt;/a&gt; [/Edit]&lt;/p&gt;
&lt;p&gt;En revanche, &lt;a class="link" href="https://github.com/xsteadfastx/jitsiexporter" target="_blank" rel="noopener"
&gt;jitsiexporter&lt;/a&gt;, j’ai réussi à le faire fonctionner assez vite ! Et je vous propose qu’on se l’installe ensemble !&lt;/p&gt;
&lt;h2 id="activer-les-statistiques"&gt;Activer les statistiques
&lt;/h2&gt;&lt;p&gt;La première chose à faire et de vérifier dans votre install de Jitsi si l’API REST est activée ou non. Si ce n’est pas le cas, vous pouvez tenter de modifier les propriétés du sip-communicator de videobridge.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ps -ef | grep jvb
jvb 164 1 0 20:09 ? 00:00:56 java -Xmx3072m [...] --apis=rest,
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour être honnête, je ne suis pas encore bien bien sûr à 100% ce qu’il faut faire pour être sûr que c’est actif. La documentation du projet Github de l’exporter n’est pas hyper claire et j’ai lu pas mal d’instruction contradictoire sur les forums&amp;hellip;&lt;/p&gt;
&lt;p&gt;En fin d’article, je vous ai mis deux liens vers la doc officielle de Jitsi à ce sujet.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vi /etc/jitsi/videobridge/sip-communicator.properties
[...]
org.jitsi.videobridge.ENABLE_STATISTICS=true
org.jitsi.videobridge.STATISTICS_TRANSPORT=muc,colibri
org.jitsi.videobridge.STATISTICS_INTERVAL=1000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Une fois que c’est bon, un petit curl` vous permettra de vous assurer que tout va bien.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl http://127.0.0.1:8080/colibri/stats
{&amp;#34;inactive_endpoints&amp;#34;:0,&amp;#34;inactive_conferences&amp;#34;:0,&amp;#34;total_ice_succeeded_relayed&amp;#34;:0,&amp;#34;total_loss_degraded_participant_seconds&amp;#34;:0,&amp;#34;bit_rate_download&amp;#34;:0,&amp;#34;muc_clients_connected&amp;#34;:1,&amp;#34;total_participants&amp;#34;:0,...
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="installer-lexporter"&gt;Installer l’exporter
&lt;/h2&gt;&lt;p&gt;Maintenant que notre Jitsi nous donne bien des statistiques en local, on va pouvoir commencer à utiliser un exporter pour consommer les métriques périodiquement et les servir au format Prometheus.&lt;/p&gt;
&lt;h3 id="prérequis-pour-la-compilation"&gt;Prérequis pour la compilation
&lt;/h3&gt;&lt;p&gt;D’abord il nous faut go&amp;hellip; Sur un Ubuntu 18.04 ça donne ça :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:longsleep/golang-backports
sudo apt update
sudo apt install golang-go git
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="compiler"&gt;Compiler
&lt;/h3&gt;&lt;p&gt;Ensuite, il faut compiler l’exporter. En théorie, comme Go fait des binaires &amp;ldquo;qui marchent partout&amp;rdquo;, vous pouvez compiler l’exporter sur votre poste et envoyer le binaire sur le serveur jitsi. En théorie&amp;hellip;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;su - jvb
mkdir -p go/src &amp;amp;&amp;amp; mkdir -p go/bin
cd go/src
git clone https://github.com/xsteadfastx/jitsiexporter
cd jitsiexporter
go get ./...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si tout se passe bien, un binaire est créé dans &lt;code&gt;~/go/bin&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="tester-lexporter"&gt;Tester l’exporter
&lt;/h2&gt;&lt;p&gt;Maintenant qu’on a un exporter, on va le tester en le lançant à la main, pour valider toute la chaîne. Les paramètres sont assez simples. Il faut renseigner d’un côté l’URL du serveur REST et de l’autre adresse/port sur lesquels on veut que l’exporter accepte les requêtes de Prometheus.&lt;/p&gt;
&lt;p&gt;Par défaut c’est localhost donc il est fort probable que vous vouliez changer ça.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/share/jitsi-videobridge/go/bin/jitsiexporter --url=&amp;#39;http://127.0.0.1:8080/colibri/stats&amp;#39; --host=192.168.1.100 --port=9700
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="un-service-pour-automatiser-le-démarrage-au-démarrage"&gt;Un service pour automatiser le démarrage au&amp;hellip; démarrage
&lt;/h2&gt;&lt;p&gt;Comme d’habitude, une fois qu’on a l’exporter qui marche oneshot, le mieux c’est quand même d’avoir un service systemd` qui va nous faciliter le démarrage/l’extinction du service :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat &amp;gt; /etc/systemd/system/jitsiexporter.service &amp;lt;&amp;lt; EOF
[Unit]
Description=Jitsi videobridge Prometheus Exporter
After=jitsi-videobridge2.service
Requires=jitsi-videobridge2.service
[Service]
User=jvb
Restart=on-failure
ExecStart=/usr/share/jitsi-videobridge/go/bin/jitsiexporter --url=&amp;#39;http://127.0.0.1:8080/colibri/stats&amp;#39; --host=192.168.1.100 --port=9700
[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable jitsiexporter
systemctl start jitsiexporter
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configurer-prometheus"&gt;Configurer Prometheus
&lt;/h2&gt;&lt;p&gt;Vous l’avez compris, il reste donc maintenant à relier Prometheus à notre exporter pour commencer à scrapper des métriques. Ici, il s’agira donc simplement d’ajouter une nouvelle section &amp;ldquo;job&amp;rdquo; dans notre configuration de Prometheus et à redémarrer pour prise en compte.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vi /usr/share/prometheus/prometheus.yml
[...]
- job_name: &amp;#39;jitsi&amp;#39;
static_configs:
- targets:
- 192.168.1.100:9700 # jitsiexporter for jitsi videobridge
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl restart prometheus
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="et-le-résultat-"&gt;Et le résultat ?
&lt;/h2&gt;&lt;p&gt;Bon, faut avouer que c’est pas foufou, j’ai eu quelques conférences en 2 semaines (pas beaucoup plus d’une par jour), mais un joli score tout de même cette conf d’1h30 avec au max 10 personnes !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/04/10participants.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Vous savez tout ! Have fun :D&lt;/p&gt;
&lt;h2 id="sources-additionnelles"&gt;Sources additionnelles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/statistics.md" target="_blank" rel="noopener"
&gt;Documentation sur les statistiques sur le site de Jitsi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/rest.md" target="_blank" rel="noopener"
&gt;Documentation sur l’API REST sur le site de Jitsi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>