<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Grafana on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/grafana/</link><description>Recent content in Grafana on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Thu, 12 Dec 2024 18:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/grafana/index.xml" rel="self" type="application/rss+xml"/><item><title>Recompiler les dashboards Grafana "MetaMonitoring" de Mimir pour Kubernetes</title><link>https://blog.zwindler.fr/2024/12/12/recompiler-dashboards-mimir-distributed-grafana/</link><pubDate>Thu, 12 Dec 2024 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2024/12/12/recompiler-dashboards-mimir-distributed-grafana/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/20200102_084825-2.webp" alt="Featured image of post Recompiler les dashboards Grafana "MetaMonitoring" de Mimir pour Kubernetes" /&gt;&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Quand on travaille sur l&amp;rsquo;observabilité, il y a un outil qui vient toujours en tête en premier : Grafana.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://grafana.com/" target="_blank" rel="noopener"
&gt;Grafana&lt;/a&gt; est un outil de visualisation open source développé par Grafana Labs, et je suis sûr que vous le connaissez tous (et j&amp;rsquo;ai aussi &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=grafana" target="_blank" rel="noopener"
&gt;écrit à son sujet de nombreuses fois&lt;/a&gt;). Mais au-delà de cela, ils développent également beaucoup d&amp;rsquo;autres outils utiles dans le paysage de l&amp;rsquo;observabilité, au point qu&amp;rsquo;on peut en théorie construire toute sa stack d&amp;rsquo;observabilité uniquement avec des outils Grafana Labs.&lt;/p&gt;
&lt;p&gt;Pour répondre au manque de stockage long terme de Prometheus et au manque de fonctionnalités de haute disponibilité (je n&amp;rsquo;ai JAMAIS compris pourquoi l&amp;rsquo;équipe Prometheus refuse de travailler là-dessus), Grafana Labs a forké Cortex il y a quelques années et l&amp;rsquo;a renommé Mimir.&lt;/p&gt;
&lt;p&gt;Je ne vais pas couvrir l&amp;rsquo;installation de Mimir ici, il y a plein de tutoriels sur Internet et une documentation officielle pour ça.&lt;/p&gt;
&lt;p&gt;À la place, je vais parler d&amp;rsquo;un problème que j&amp;rsquo;ai avec le chart helm officiel de Mimir, et plus précisément avec les dashboards Grafana intégrés qui viennent avec.&lt;/p&gt;
&lt;h2 id="des-dashboards-vous-dites-"&gt;Des dashboards, vous dites ?
&lt;/h2&gt;&lt;p&gt;Mimir est livré avec de nombreux dashboards Grafana utiles pour s&amp;rsquo;assurer que les composants fonctionnent correctement.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2024/12/mimir-dashboard.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Ces dashboards sont compatibles avec les différents modes de déploiement de Mimir. Dans Kubernetes, si vous utilisez le &lt;a class="link" href="https://github.com/grafana/mimir/blob/main/operations/helm/charts/mimir-distributed/values.yaml" target="_blank" rel="noopener"
&gt;chart helm officiel mimir-distributed&lt;/a&gt;, cela peut être activé par une simple valeur :&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;metaMonitoring&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;dashboards&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;enabled&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Mais&lt;/strong&gt;, par défaut, tous les dashboards installés via la valeur &lt;code&gt;metaMonitoring&lt;/code&gt; dans les charts helm de Mimir sont des manifestes JSON précompilés utilisant &lt;strong&gt;jsonnet/mixin&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Par exemple, voici la version précompilée du &amp;ldquo;Mimir / Overview dashboard&amp;rdquo; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/grafana/mimir/blob/2640b8f72127548e9e3da281a763476b03fb4aae/operations/mimir-mixin-compiled/dashboards/mimir-overview.json" target="_blank" rel="noopener"
&gt;github.com/grafana/mimir/blob/2640b8f72127548e9e3da281a763476b03fb4aae/operations/mimir-mixin-compiled/dashboards/mimir-overview.json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par conception, &lt;strong&gt;vous ne pouvez pas changer des choses comme le préfixe du nom des pods mimir&lt;/strong&gt;, ce qui rend ces dashboards précompilés inutiles dans un environnement type helm où le nom de release (mimir-) est un préfixe du pod.&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 monitoring 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;mimir-alertmanager-0 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mimir-alertmanager-1 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mimir-compactor-0 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mimir-distributor-5d668b479f-ksltr 1/1 Running &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;24h ago&lt;span class="o"&gt;)&lt;/span&gt; 6d1h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans ce cas, tous les dashboards seront cassés, affichant tous &amp;ldquo;no data&amp;rdquo; dans Grafana car les données seront incorrectement filtrées. Par exemple, le panneau &amp;ldquo;Write requests / sec&amp;rdquo; dans &amp;ldquo;Mimir / Overview dashboard&amp;rdquo; a un label &lt;code&gt;job=~&amp;quot;($namespace)/((distributor...&lt;/code&gt;, mais notre pod est &lt;code&gt;mimir-distributor&lt;/code&gt;, pas &lt;code&gt;distributor&lt;/code&gt; :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sum by (status) (
label_replace(label_replace(rate(cortex_request_duration_seconds_count{cluster=~&amp;#34;$cluster&amp;#34;, job=~&amp;#34;($namespace)/((distributor.*|cortex|mimir|mimir-write.*))&amp;#34;, route=~&amp;#34;/distributor.Distributor/Push|/httpgrpc.*|api_(v1|prom)_push|otlp_v1_metrics&amp;#34;}[$__rate_interval]),
&amp;#34;status&amp;#34;, &amp;#34;${1}xx&amp;#34;, &amp;#34;status_code&amp;#34;, &amp;#34;([0-9])..&amp;#34;),
&amp;#34;status&amp;#34;, &amp;#34;${1}&amp;#34;, &amp;#34;status_code&amp;#34;, &amp;#34;([a-zA-Z]+)&amp;#34;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La solution est de &lt;strong&gt;désactiver le flag metaMonitoring&lt;/strong&gt; du chart, et de construire / déployer les dashboards séparément.&lt;/p&gt;
&lt;h2 id="procédure"&gt;Procédure
&lt;/h2&gt;&lt;p&gt;Récupérez les sources de Mimir :&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;git clone https://github.com/grafana/mimir.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Heureusement, les fichiers jsonnet/mixin incluent une variable &lt;code&gt;job_prefix&lt;/code&gt; qui va nous aider à corriger cela :&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;sed -i.bak &lt;span class="s2"&gt;&amp;#34;s/job_prefix: &amp;#39;(\$namespace)\/&amp;#39;,/job_prefix: &amp;#39;(\$namespace)\/mimir-&amp;#39;,/&amp;#34;&lt;/span&gt; operations/mimir-mixin/config.libsonnet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Recompilez les dashboards :&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;make build-mixin
&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;podman image inspect grafana/mimir-build-image:pr9491-80f5778956 &amp;gt;/dev/null 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; podman pull grafana/mimir-build-image:pr9491-80f5778956
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;podman tag grafana/mimir-build-image:pr9491-80f5778956 grafana/mimir-build-image:latest
&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;make: Leaving directory &lt;span class="s1"&gt;&amp;#39;/go/src/github.com/grafana/mimir&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 10,10 real 0,02 user 0,01 sys
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note : Si vous n&amp;rsquo;avez pas &lt;code&gt;docker&lt;/code&gt; sur votre machine (j&amp;rsquo;utilise podman), la commande &lt;code&gt;make&lt;/code&gt; va échouer car elle ne peut pas trouver docker et le binaire &lt;code&gt;docker&lt;/code&gt; est hardcodé dans les commandes make. Modifiez le Makefile pour remplacer &lt;code&gt;docker&lt;/code&gt; par &lt;code&gt;podman&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Les fichiers json dans operations/mimir-mixin-compiled/dashboards sont maintenant construits avec les noms de pods corrects.&lt;/p&gt;
&lt;p&gt;Créez un chart helm grafana-dashboards (appelé &lt;strong&gt;yourDashboardsChart&lt;/strong&gt; ici).&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 create yourDashboardsChart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans ce chart, créez un répertoire &lt;code&gt;src/dashboards/mimir&lt;/code&gt; (pour les sources des dashboards json) à côté du répertoire classique &lt;code&gt;templates&lt;/code&gt; contenant les manifestes YAML go-templatés. Nous allons créer les fichiers helm gotemplate juste après :&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;cp operations/mimir-mixin-compiled/dashboards/* ../yourDashboardsChart/src/dashboards/mimir
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Maintenant, pour chaque fichier json généré par jsonnet, nous allons créer un fichier yaml helm gotemplaté, qui à son tour créera une ConfigMap pour chaque dashboard dans notre cluster Kubernetes. Ils ressembleront à ceci :&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="nn"&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="c"&gt;# Source: mimir-distributed/templates/metamonitoring/grafana-dashboards.yaml&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;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConfigMap&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;mimir-alertmanager-dashboard&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="s1"&gt;&amp;#39;{{ $.Release.Namespace }}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;grafana_dashboard&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;1&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;annotations&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;k8s-sidecar-target-directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/dashboards/Mimir Dashboards&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;data&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;mimir-alertmanager.json&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; {{ $.Files.Get &amp;#34;src/dashboards/mimir/mimir-alertmanager.json&amp;#34; | fromJson | toJson }}&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;Pour accélérer le processus, vous pouvez réutiliser &lt;code&gt;helm template&lt;/code&gt; et quelques commandes bash pour générer tous les fichiers helm gotemplate pour vous :&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;helm repo update
&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;mkdir -p mimir
&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 -n monitoring template mimir grafana/mimir-distributed --set metaMonitoring.dashboards.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &amp;gt; helm-output.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;&lt;span class="c1"&gt;# Compter le nombre de séparateurs de documents&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;doc_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;grep -c &lt;span class="s1"&gt;&amp;#39;^---$&amp;#39;&lt;/span&gt; helm-output.yaml&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Diviser le fichier YAML en fichiers séparés pour chaque document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;csplit -f mimir/helm-output- helm-output.yaml &lt;span class="s1"&gt;&amp;#39;/---/&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;doc_count &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;}&amp;#34;&lt;/span&gt; &amp;gt;/dev/null
&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="c1"&gt;# Un peu de nettoyage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; file in mimir/helm-output-*&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; grep -q &lt;span class="s1"&gt;&amp;#39;kind: ConfigMap&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; grep -q &lt;span class="s1"&gt;&amp;#39;dashboard&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.metadata.name&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yq &lt;span class="nb"&gt;eval&lt;/span&gt; -i &lt;span class="s1"&gt;&amp;#39;del(.metadata.labels.&amp;#34;helm.sh/chart&amp;#34;, .metadata.labels.&amp;#34;app.kubernetes.io/name&amp;#34;, .metadata.labels.&amp;#34;app.kubernetes.io/instance&amp;#34;, .metadata.labels.&amp;#34;app.kubernetes.io/version&amp;#34;, .metadata.labels.&amp;#34;app.kubernetes.io/managed-by&amp;#34;)&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yq &lt;span class="nb"&gt;eval&lt;/span&gt; -i &lt;span class="s1"&gt;&amp;#39;.metadata.namespace = &amp;#34;{{ $.Release.Namespace }}&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yq &lt;span class="nb"&gt;eval&lt;/span&gt; -i &lt;span class="s1"&gt;&amp;#39;.data |= with_entries(.value = &amp;#34;{{ $.Files.Get \&amp;#34;src/dashboards/mimir/&amp;#34; + .key + &amp;#34;\&amp;#34; | fromJson | toJson }}&amp;#34;)&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mv &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mimir/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;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;mv mimir/* ../yourDashboardsChart/templates/mimir
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Maintenant, vous devriez avoir tous les fichiers nécessaires pour régénérer les dashboards Grafana dans votre cluster Kubernetes, avec le préfixe correct.&lt;/p&gt;
&lt;p&gt;Have fun!&lt;/p&gt;
&lt;h2 id="source"&gt;Source
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://grafana.com/docs/mimir/latest/manage/monitor-grafana-mimir/requirements/" target="_blank" rel="noopener"
&gt;https://grafana.com/docs/mimir/latest/manage/monitor-grafana-mimir/requirements/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://grafana.com/docs/mimir/latest/manage/monitor-grafana-mimir/installing-dashboards-and-alerts/" target="_blank" rel="noopener"
&gt;https://grafana.com/docs/mimir/latest/manage/monitor-grafana-mimir/installing-dashboards-and-alerts/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Sauvegarder des dashboards Grafana dans Kubernetes, en s’amusant (j’explique les RBAC, Job et les CronJob)</title><link>https://blog.zwindler.fr/2024/10/01/sauvegarder-dashboards-grafana/</link><pubDate>Tue, 01 Oct 2024 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2024/10/01/sauvegarder-dashboards-grafana/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/20200102_084825-2.webp" alt="Featured image of post Sauvegarder des dashboards Grafana dans Kubernetes, en s’amusant (j’explique les RBAC, Job et les CronJob)" /&gt;&lt;p&gt;Note : Pour les allergiques à Kubernetes, cet article peut être vu comme une entrée en la matière, car je vais progressivement explorer certains concepts, de manière très progressive.&lt;/p&gt;
&lt;p&gt;Note 2 : par convention dans cet article, j&amp;rsquo;ai essayé de nommer tous les objets Kubernetes avec une majuscule à tous les mots (exemple : &lt;strong&gt;ServiceAccount&lt;/strong&gt;) pour que ce soit plus clair.&lt;/p&gt;
&lt;h2 id="grafana"&gt;Grafana
&lt;/h2&gt;&lt;p&gt;Je ne vous ferai pas l&amp;rsquo;affront de vous présenter Grafana, l&amp;rsquo;outil de visualisation open source de la société éponyme. C&amp;rsquo;est un outil que j&amp;rsquo;utilise depuis un moment et qui est quasiment incontournable dans toutes les solutions de monitoring modernes (ou non ? xD).&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;ailleurs, &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=grafana" target="_blank" rel="noopener"
&gt;j&amp;rsquo;ai écrit déjà quelques articles sur Grafana au fil des années&lt;/a&gt;, c&amp;rsquo;est un outil aussi simple qu&amp;rsquo;irremplaçable (jusqu&amp;rsquo;à ce qu&amp;rsquo;on trouve mieux, bien entendu).&lt;/p&gt;
&lt;p&gt;Si vous avez déjà utilisé au moins une fois Grafana, vous savez donc que les visualisations sont organisées comme suit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;des dossiers&lt;/li&gt;
&lt;li&gt;dans lesquels on range des dashboards&lt;/li&gt;
&lt;li&gt;qui contiennent des panels (nos visualisations)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On peut donc créer des dashboards à gogo en fonction du besoin, les cloner, les modifier, les importer depuis un magasin sur grafana.com (assez inutilisable maintenant, mais bon) et en afficher le code JSON.&lt;/p&gt;
&lt;p&gt;Et ça, c&amp;rsquo;est super cool, parce que si mon Grafana brûle (mes machines persos ne sont pas hébergées dans les conditions les plus pros possibles&amp;hellip; 😱), je n&amp;rsquo;ai pas envie de tout perdre. Mais aller régulièrement copier / coller le code JSON de chaque dashboard, c&amp;rsquo;est pénible.&lt;/p&gt;
&lt;h2 id="because-im-api"&gt;Because I&amp;rsquo;m API
&lt;/h2&gt;&lt;p&gt;Un truc qui est cool avec Grafana, c&amp;rsquo;est que comme tout logiciel un peu pro qui se respecte, il dispose d&amp;rsquo;une API. Je ne vais pas juger de sa qualité globale d&amp;rsquo;API (vous connaissez peut-être bien mieux que moi), mais elle a le mérite d&amp;rsquo;exister.&lt;/p&gt;
&lt;p&gt;Récemment, je me suis donc demandé quelle quantité d&amp;rsquo;effort, il faudrait déployer pour sauvegarder de manière automatisée des dashboards Grafana avec l&amp;rsquo;API et Kubernetes (oui hein, vous n&amp;rsquo;allez pas y couper).&lt;/p&gt;
&lt;p&gt;Partons donc du principe que j&amp;rsquo;ai un cluster Kubernetes sur lequel &lt;a class="link" href="https://github.com/grafana/helm-charts/tree/main/charts/grafana" target="_blank" rel="noopener"
&gt;j&amp;rsquo;ai déployé la chart Helm officielle de Grafana&lt;/a&gt; (rappel, Helm, c&amp;rsquo;est à la fois un package manager et un moyen de déployer des applications dans Kubernetes).&lt;/p&gt;
&lt;p&gt;À partir de là, de quoi j&amp;rsquo;ai besoin ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Je suis le roi des fainéants, je ne veux pas avoir à aller créer les accès dans Grafana moi même (via 3 clics clics). Il faut donc que j&amp;rsquo;automatise la création d&amp;rsquo;un token pour communiquer avec l&amp;rsquo;API&lt;/li&gt;
&lt;li&gt;Une fois que j&amp;rsquo;ai mon token, je veux créer une tâche périodique qui va se connecter à l&amp;rsquo;API, lister les dashboards disponibles et les sauvegarder sur un bucket s3 chez Scaleway (il y a 75 Go gratuit dans le free-tier, ça suffira largement)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="se-préparer-à-créer-un-token"&gt;Se préparer à créer un token
&lt;/h2&gt;&lt;p&gt;Bon, là en apparence, on se retrouve face à un problème d&amp;rsquo;œuf / poule. Comment je fais pour me connecter à l&amp;rsquo;API si je n&amp;rsquo;ai pas un accès à l&amp;rsquo;API ?&lt;/p&gt;
&lt;p&gt;Heureusement, on va tricher&amp;hellip; grâce à Kubernetes 😏&lt;/p&gt;
&lt;p&gt;Quand on déploie Grafana dans Kubernetes, la chart officielle va créer un &lt;strong&gt;Secret&lt;/strong&gt; (au sens Kubernetes du terme) qui contient le login / password du compte local administrateur de Grafana.&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;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Secret&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;creationTimestamp&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;2024-10-01T13:11:48Z&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;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;grafana&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;grafana&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;resourceVersion&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;1234567&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;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadbeef-dead-cafe-baba-123456789012&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;data&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;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;YWRtaW4=&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;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TmV2ZXJHb25uYUdpdmVZb3VVcAo=&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&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;S&amp;rsquo;il est déconseillé de l&amp;rsquo;utiliser au jour le jour (mieux vaut utiliser des comptes nominatifs, idéalement via un &lt;a class="link" href="https://en.wikipedia.org/wiki/Identity_provider" target="_blank" rel="noopener"
&gt;Identity Provider&lt;/a&gt;), c&amp;rsquo;est super pratique ici !&lt;/p&gt;
&lt;p&gt;Il suffit de créer un &lt;strong&gt;Job&lt;/strong&gt; Kubernetes avec suffisamment de droits pour lire ce &lt;strong&gt;Secret&lt;/strong&gt;, qui va se connecter (une seule fois) sur l&amp;rsquo;API avec le compte admin, créer un token, le récupérer, et le stocker dans un autre &lt;strong&gt;Secret&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;Sans rentrer à fond dans les détails de comment fonctionne le RBAC (Role Based Access Control) dans Kubernetes, voilà à quoi ça ressemble :&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# le compte de service (ServiceAccount) qui va être utilisé par le Job et qui a besoin de droits particuliers&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;ServiceAccount&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;grafana-backup-sa&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;grafana&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# le rôle, c&amp;#39;est la politique de sécurité qui va nous permettre de faire des trucs&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;Role&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;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;grafana&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;grafana-backup-role&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;rules&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="c"&gt;# ici je n&amp;#39;autorise qu&amp;#39;à lire le secret contenant l&amp;#39;admin/password de grafana, pas plus&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&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;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana&amp;#34;&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;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&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="c"&gt;# ici, je vais autoriser le fait de modifier un secret qui s&amp;#39;appelle grafana-backup&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&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;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana-backup&amp;#34;&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;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&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;update&amp;#34;&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;patch&amp;#34;&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="c"&gt;# ici, je vais autoriser le fait de créer des secrets&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&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;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create&amp;#34;&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="nn"&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="c"&gt;# ici, je lie simplement ServiceAccount à un Role pour lui donner les droits dont j&amp;#39;ai besoin&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;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RoleBinding&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;grafana-backup-rolebinding&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;grafana&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;subjects&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="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;ServiceAccount&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;grafana-backup-sa&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;grafana&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;roleRef&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;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;Role&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;grafana-backup-role&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;apiGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbac.authorization.k8s.io&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;Note : vous aurez remarqué que je n&amp;rsquo;ai pas autorisé la création ET la modification du &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup en une seule étape, mais en deux. Ceci est dû à une limitation de l&amp;rsquo;API de Kubernetes, qui ne permet pas de limiter la création d&amp;rsquo;un objet via son &amp;ldquo;ressource name&amp;rdquo;. Je vous laisse aller lire la doc officielle si ça vous intéresse.&lt;/p&gt;
&lt;p&gt;Sachez que je me suis un peu &amp;ldquo;embêté&amp;rdquo; à gérer la politique de droits et qu&amp;rsquo;on aurait pu faire bien plus simple. Mais c&amp;rsquo;est pour respecter &lt;a class="link" href="https://fr.wikipedia.org/wiki/Principe_de_moindre_privil%C3%A8ge" target="_blank" rel="noopener"
&gt;le principe en sécurité informatique du moindre privilège&lt;/a&gt; (c&amp;rsquo;est important).&lt;/p&gt;
&lt;h2 id="bon-on-va-le-créer-ce-token-"&gt;Bon&amp;hellip; on va le créer, ce token ??
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on a géré les aspects de sécurité (ça limitera les possibilités d&amp;rsquo;un attaquant qui réussirait à prendre la main sur notre &lt;strong&gt;Pod&lt;/strong&gt; / container), on peut créer la tâche qui va créer le token.&lt;/p&gt;
&lt;p&gt;Ici, il n&amp;rsquo;y a pas de raison de laisser tourner un container ad vitam une fois que le token est créé, on peut l&amp;rsquo;éteindre. Pour ça, il existe un objet dans Kubernetes qui s&amp;rsquo;appelle le &lt;strong&gt;Job&lt;/strong&gt; (avec une gestion d&amp;rsquo;erreur et de retry intégré). Voilà à quoi il va ressembler :&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;batch/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Job&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;grafana-backup-api-token-job&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;grafana&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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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="c"&gt;# on lui donne bien le bon ServiceAccount sinon on a fait tout ce RBAC pour rien T_T&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;serviceAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup-sa &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-create-api-token&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;# une petite astuce pour de la bidouille, merci Jérôme :-*&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;nixery.dev/shell/curl/jq/kubectl:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# on &amp;#34;monte&amp;#34; le Secret Kubernetes comme des fichiers dans notre container&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;volumeMounts&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;grafana-secret-volume&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;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/grafana-secret&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;readOnly&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;command&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="l"&gt;/bin/sh&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="l"&gt;c&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="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; # Comme on a monté le secret dans notre container, c&amp;#39;est super facile de récupérer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # l&amp;#39;utilisateur et le mot de passe admin sans avoir à le stocker nulle part (surtout pas sur github)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_ADMIN_USER=$(cat /etc/grafana-secret/admin)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_ADMIN_PASS=$(cat /etc/grafana-secret/password)
&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; # On peut maintenant faire une requête HTTP via curl sur l&amp;#39;API de Grafana
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_API_TOKEN=$(curl -s -X POST http://@IPgrafana:80/api/auth/keys \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -u &amp;#34;$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASS&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;Content-Type: application/json&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;name&amp;#34;: &amp;#34;Kubernetes Backup Token&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;role&amp;#34;: &amp;#34;Admin&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;secondsToLive&amp;#34;: 31536000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }&amp;#39; | jq -r &amp;#39;.key&amp;#39;)
&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; # On termine par stocker le token dans le nouveau Secret grafana-backup
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; kubectl create secret generic grafana-backup \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --from-literal=GRAFANA_API_TOKEN=$GRAFANA_API_TOKEN \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --dry-run=client -o yaml | kubectl apply -f -&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;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OnFailure&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;volumes&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;grafana-secret-volume&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;secret&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;secretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&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;Normalement, avec ce bon script code BASH, j&amp;rsquo;ai réconcilié les amateurs de bash avec Kubernetes.&lt;/p&gt;
&lt;p&gt;Comment ça, &amp;ldquo;non&amp;rdquo; ?&lt;/p&gt;
&lt;p&gt;Il ne me reste plus qu&amp;rsquo;à appliquer le manifest YAML sur mon cluster Kubernetes et le &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup contenant la clé d&amp;rsquo;API devrait apparaitre sur notre cluster :&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 apply -f job.yaml&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 get secrets&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;NAME TYPE DATA AGE&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;grafana-backup Opaque 1 1m&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 describe secret grafana-backup&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;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;grafana-backup&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;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;grafana&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;Labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;none&amp;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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&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;Data&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;====&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;GRAFANA_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bytes&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;Première victoire :)&lt;/p&gt;
&lt;h2 id="rbac-again"&gt;RBAC, again
&lt;/h2&gt;&lt;p&gt;On peut s&amp;rsquo;attaquer à la deuxième partie du problème : comment récupérer puis sauvegarder les JSON ?&lt;/p&gt;
&lt;p&gt;Ben, avec une nouvelle couche de moindres privilèges, pardi !!&lt;/p&gt;
&lt;p&gt;Vous connaissez la musique maintenant. On crée un &lt;strong&gt;ServiceAccount&lt;/strong&gt;, on ne lui donne le droit qu&amp;rsquo;à lire les &lt;strong&gt;Secrets&lt;/strong&gt; qui nous intéressent via un &lt;strong&gt;Role&lt;/strong&gt; / &lt;strong&gt;RoleBinding&lt;/strong&gt; pour ne pas faciliter le travail d&amp;rsquo;un attaquant qui aurait compromis mon &lt;strong&gt;Pod&lt;/strong&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ServiceAccount&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;grafana-backup-cron-sa&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;grafana&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Role&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;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;grafana&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;grafana-backup-cron-role&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;rules&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="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&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;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana-backup&amp;#34;&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;grafana-backup-s3-credentials&amp;#34;&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;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RoleBinding&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;grafana-backup-cron-rolebinding&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;grafana&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;subjects&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="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;ServiceAccount&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;grafana-backup-cron-sa&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;grafana&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;roleRef&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;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;Role&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;grafana-backup-cron-role&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;apiGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbac.authorization.k8s.io&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;Petite subtilité, ici, je vais aussi devoir créer un &lt;strong&gt;Secret&lt;/strong&gt; supplémentaire pour que mon container puisse s&amp;rsquo;authentifier sur le bucket S3 sur lequel je vais déposer mes JSONs. Je ne rentre pas dans les détails, on sort du cadre de l&amp;rsquo;article, vous pouvez aller l&lt;a class="link" href="https://www.scaleway.com/en/docs/storage/object/api-cli/object-storage-aws-cli/" target="_blank" rel="noopener"
&gt;ire la doc officielle de scaleway qui est plutôt bien faite (Using Object Storage with the AWS-CLI)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Voilà à quoi ressemble le &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup-s3-credentials :&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;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Secret&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;grafana-backup-s3-credentials&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;grafana&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&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;stringData&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;# le Secret se compose de 2 entrées, qui seront montés comme des fichiers textes &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;# dans le container ; pratique pour des fichiers de config avec des secrets !!&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;credentials&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; [default]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws_access_key_id=SCxxxxxxxxxxxxxxxxxx
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws_secret_access_key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&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; [default]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; region = fr-par
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; output = json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; services = scw-fr-par
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [services scw-fr-par]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; s3 =
&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_url = https://s3.fr-par.scw.cloud&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;Et pour finir, on veut donc créer une tâche planifiée, qui va s&amp;rsquo;occuper de réaliser notre sauvegarde régulièrement. Dans Kubernetes, il s&amp;rsquo;agit de l&amp;rsquo;objet &lt;strong&gt;CronJob&lt;/strong&gt;, qui va lancer périodiquement des &lt;strong&gt;Job&lt;/strong&gt; (le même qu&amp;rsquo;on a vu précédemment).&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;batch/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CronJob&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;grafana-backup-cronjob&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;grafana&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;schedule&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;0 */2 * * *&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;jobTemplate&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;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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;serviceAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup-cron-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup&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;nixery.dev/shell/curl/jq/awscli:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&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;grafana-backup-s3&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;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/.aws/credentials&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;subPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;credentials&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;grafana-backup-s3&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;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/.aws/config&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;subPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;config&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;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OnFailure&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;env&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;GRAFANA_API_TOKEN&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;valueFrom&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;secretKeyRef&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;grafana-backup&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GRAFANA_API_TOKEN&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;command&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="l"&gt;/bin/sh&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="l"&gt;c&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="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; # Ici, on récupère un gros JSON listant les dashboards et on utilise jq pour extraire l&amp;#39;identifiant unique &amp;#34;uid&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; dashboards=$(curl -s -H &amp;#34;Authorization: Bearer $GRAFANA_API_TOKEN&amp;#34; &amp;#39;http://@IPgrafana:80/api/search?query=&amp;amp;type=dash-db&amp;#39; | jq -r &amp;#39;.[] | .uid&amp;#39;)
&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; # On boucle ensuite sur la liste des uid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; for uid in $dashboards; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # On récupère le JSON qu&amp;#39;on copie en local
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; dashboard_json=$(curl -s -H &amp;#34;Authorization: Bearer $GRAFANA_API_TOKEN&amp;#34; http://@IPgrafana:80/api/dashboards/uid/$uid)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;$dashboard_json&amp;#34; &amp;gt; /tmp/dashboard-$uid.json
&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; # Puis on utilise aws-cli pour uploader le fichier sur notre bucket s3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws s3 cp /tmp/dashboard-$uid.json s3://grafana-backup/grafana-backups/dashboard-$uid.json --profile default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done&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;volumes&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;grafana-backup-s3&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;secret&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;secretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;secret-grafana-backup-credentials&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;Toutes les deux heures, nos dashboards seront tous sauvegardés sur notre bucket chez Scaleway. Si la fonction de versioning a été activé sur le bucket, on pourra même garder un historique des versions.&lt;/p&gt;
&lt;p&gt;Deuxième victoire :)&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Ok, écrire ces scripts et les manifests qui vont avec a surement pris un peu plus de temps que d&amp;rsquo;aller manuellement créer un token dans l&amp;rsquo;UI de Grafana, puis de le donner en dur dans un gros script bash.&lt;/p&gt;
&lt;p&gt;Cependant, il y a quand même quelques différences avec cette approche :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A part pour les crédentials du bucket S3 (et encore on aurait pu utiliser minIO dans Kubernetes), on a manipulé aucun identifiant. Toute cette gestion des &lt;strong&gt;Secrets&lt;/strong&gt; est déléguée à Kubernetes, loin de l&amp;rsquo;admin (avec les limitations que ça peut avoir). On n&amp;rsquo;aura pas de leaks sur github de secrets git-és par erreur et si on gère son RBAC correctement, seuls les &lt;strong&gt;Pods&lt;/strong&gt; qui sont habilités à lire ses &lt;strong&gt;Secrets&lt;/strong&gt; pourront le faire.&lt;/li&gt;
&lt;li&gt;Tout est géré &amp;ldquo;as-code&amp;rdquo; et automatisé. Le faire une fois à la main va plus vite. Le faire 100 fois, car on a 100 clusters identiques, c&amp;rsquo;est quand même dommage.&lt;/li&gt;
&lt;li&gt;On profite des fonctions de Kubernetes de gestion des erreurs et de retries des Jobs et des Cronjobs. C&amp;rsquo;est du code (bash ou autre) en moins à gérer si on veut fiabiliser l&amp;rsquo;outil dans une utilisation plus &amp;ldquo;industrielle&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Que je vous aie convaincu ou pas, au-delà de l&amp;rsquo;exemple, le but était surtout d&amp;rsquo;introduire quelques concepts de Kubernetes (le RBAC et les &lt;strong&gt;Jobs&lt;/strong&gt;, comment on construit des manifests, &amp;hellip;) et j&amp;rsquo;espère que ça vous a plu.&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;p&gt;Si vous trouvez ces scripts trop limités pour vos besoins, vous pouvez aller voir les outils suivants, plus poussés. Attention cependant, certains ne sont pas/plus maintenu, et je n&amp;rsquo;ai pas audité le code. A vos risques et périls donc :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://staffordwilliams.com/blog/2021/02/14/grafana-in-kubernetes/" target="_blank" rel="noopener"
&gt;https://staffordwilliams.com/blog/2021/02/14/grafana-in-kubernetes/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/ysde/grafana-backup-tool" target="_blank" rel="noopener"
&gt;https://github.com/ysde/grafana-backup-tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/grafana-wizzy/wizzy" target="_blank" rel="noopener"
&gt;https://github.com/grafana-wizzy/wizzy&lt;/a&gt; (archivé)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/esnet/gdg" target="_blank" rel="noopener"
&gt;https://github.com/esnet/gdg&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>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>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><item><title>Découvrir Prometheus et Grafana par l’exemple</title><link>https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/</link><pubDate>Mon, 13 Apr 2020 06:40:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/20200102_084825-2.webp" alt="Featured image of post Découvrir Prometheus et Grafana par l’exemple" /&gt;&lt;h2 id="grafana-et-prometheus"&gt;Grafana et Prometheus
&lt;/h2&gt;&lt;p&gt;Ça fait plusieurs articles que je vous parle de &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=Prometheus" &gt;Prometheus et de Grafana&lt;/a&gt;, notamment pour l’installer. Mais je n’avais pas encore pris le temps de faire un article pour vous montrer comment les utiliser (et pourquoi ces deux outils sont géniaux) !&lt;/p&gt;
&lt;p&gt;Typiquement, ça va nous permettre de réaliser ce genre de dashboard, qui permettra aux équipes (production, dev, voire même équipes fonctionnelles) de voir en un coup d’œil si tout va bien ou au contraire, ce qui va mal.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/01/20200102_084825-2-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="un-cas-utile"&gt;Un cas utile
&lt;/h2&gt;&lt;p&gt;Et tant qu’à présenter les outils, je me suis dis que j’allais utiliser un des exemples que j’avais eu à mettre en place &lt;em&gt;dans la vraie vie&lt;/em&gt; : tester que mes déploiements Kubernetes respectent bien l’anti-affinité.&lt;/p&gt;
&lt;p&gt;Cet exemple est volontairement &amp;ldquo;un peu complexe&amp;rdquo;, car il a vocation à vous permettre d’appréhender d’un seul coup plusieurs concepts qui seront utiles pour bien débuter dans Grafana et Prometheus. Notamment, la sélection de la bonne métrique, le langage PromQL, l’ajout de variables dans les dashboards, etc.&lt;/p&gt;
&lt;p&gt;Note : Pour ceux qui ne l’ont pas, dans l’orchestrateur de containers Kubernetes, il est possible d’indiquer à l’outil que 2 replicas d’une même application (pour la redondance) ne doivent pas être exécutée sur la même ressource. Ça permet entre autre de garantir qu’il n’y a pas un SPOF au niveau de l’hôte qui exécute les containers alors qu’on croit avoir une application redondante.&lt;/p&gt;
&lt;h2 id="trouver-les-métriques-dans-prometheus"&gt;Trouver les métriques dans Prometheus
&lt;/h2&gt;&lt;p&gt;Je vais partir du principe que vous avez déjà une plateforme opérationnelle, équipée d’un Prometheus et d’un Grafana. Si ce n’est pas le cas, je vous invite à faire une rapide recherche sur votre moteur de recherche préféré ou de &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=prometheus" &gt;consulter mes précédents articles sur le sujet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La première étape avant de commencer à essayer de monter de beaux dashboards consiste à chercher la métrique qui nous intéresse. Car, il faut bien l’admettre, généralement Prometheus en collecte BEAUCOUP !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/prometheus_timeseries01.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rien que Prometheus lui même expose et stocke plus de 700 métriques&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On va donc se connecter sur notre serveur Prometheus, puis requêter l’ensemble des métriques disponibles pour en trouver une qui permette de répondre simplement à notre problème.
Pour reprendre l’exemple que j’ai choisi, je veux :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pour tous les Pods&lt;/li&gt;
&lt;li&gt;m’assurer que plusieurs Pods d’un même déploiement ne sont pas exécuté sur le même serveur&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour ça, j’ai donc besoin d’avoir une métrique qui me permettre d’avoir tous les Pods, un information sur le Deploiement concerné, ainsi que le Node sur lequel le Pod est exécuté.&lt;/p&gt;
&lt;h2 id="container_last_seen"&gt;container_last_seen
&lt;/h2&gt;&lt;p&gt;Il y a probablement plusieurs façon de répondre à cette interrogation, mais une des solutions qui marchent plutôt bien pour moi est d’utiliser la métrique &lt;em&gt;container_last_seen&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;En recherchant des métriques dans la console de Prometheus, j’ai pu remarquer que cette métrique donne, pour tous les containers, la date à laquelle il a été vu pour la dernière fois, ainsi qu’un certain nombre d’information sur chaque container.&lt;/p&gt;
&lt;h2 id="on-valide-sur-un-cas-particulier"&gt;On valide sur un cas particulier
&lt;/h2&gt;&lt;p&gt;Pour vérifier que la métrique que je vous indique répond bien à notre problème, je vous propose de tester avec un exemple.&lt;/p&gt;
&lt;p&gt;La requête PromQL suivante permet d’afficher les containers responsable de la résolution DNS interne de mon Kubernetes (déployé dans le namespace &lt;em&gt;kube-system&lt;/em&gt;) :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2018/09/prometheus1-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;container_last_seen{container_name=~&amp;#34;.*dns.*&amp;#34;,namespace=~&amp;#34;kube-system&amp;#34;}
container_last_seen{[...],container_name=&amp;#34;dns&amp;#34;,instance=&amp;#34;node1&amp;#34;,namespace=&amp;#34;kube-system&amp;#34;,pod_name=&amp;#34;coredns-xxxxxxxx-yyyyy&amp;#34;,[...]} 1577569793
container_last_seen{[...],container_name=&amp;#34;dns&amp;#34;,instance=&amp;#34;node2&amp;#34;,namespace=&amp;#34;kube-system&amp;#34;,pod_name=&amp;#34;coredns-xxxxxxxx-zzzzz&amp;#34;,[...]} 1577566730
container_last_seen{[...],container_name=&amp;#34;dns&amp;#34;,instance=&amp;#34;node3&amp;#34;,namespace=&amp;#34;kube-system&amp;#34;,pod_name=&amp;#34;coredns-xxxxxxxx-aaaaa&amp;#34;,[...]} 1577569313
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour ceux qui débutent en PromQL, il s’agit du langage de requêtage de Prometheus, et qui permet entre autre de filtrer les timeseries affichées en fonction de critères (listés dans la partie entre les accolades).&lt;/p&gt;
&lt;p&gt;Dans les résultats de la requête, on a bien :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le nom de notre Deployment (container_name)&lt;/li&gt;
&lt;li&gt;le nom de l’hôte qui héberge le container (instance)&lt;/li&gt;
&lt;li&gt;le nom du Pod (pod_name)&lt;/li&gt;
&lt;li&gt;le namespace&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="créer-notre-visualisation-dans-grafana"&gt;Créer notre visualisation dans Grafana
&lt;/h2&gt;&lt;p&gt;Maintenant qu’on a trouvé la métriques qui nous intéresse, on peut aller dans Grafana et créer un nouveau Dashboard&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_reate_dashboard.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Puis un nouveau Panel dans notre Dashboard fraîchement créé :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_nouveau_panel.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A partir de là, on pourrait directement afficher les données de notre métrique, mais ça ne serait pas très informatif. On serait noyé sous une masse de conteneurs, chacun avec leurs variables.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_query.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Bref, on va devoir restreindre tout ça, notamment via des variables, pour que ça devienne exploitable !&lt;/p&gt;
&lt;h2 id="il-y-en-a-beaucoup-trop"&gt;Il y en a beaucoup trop
&lt;/h2&gt;&lt;p&gt;Dans la capture que j’ai faite juste avant, j’ai quand même été obligé de restreindre à un seul namespace, pour ne pas noyer Prometheus et Grafana. Et encore, c’est (toujours) parfaitement inexploitable.&lt;/p&gt;
&lt;p&gt;Clairement, on ne va pas vouloir se contenter d’une sélection &amp;ldquo;statique&amp;rdquo; des namespaces, au risque de devoir faire un &lt;em&gt;graphique&lt;/em&gt; par &lt;em&gt;namespace&lt;/em&gt; Kubernetes (et vous en avez peut être beaucoup).&lt;/p&gt;
&lt;p&gt;On va donc récupérer la liste des namespaces disponibles dans Prometheus et l’afficher dans une liste déroulante dans notre Dashboard. Cette liste permettra de filtrer les données par namespace pour ne pas faire planter Prometheus.&lt;/p&gt;
&lt;h2 id="les-variables-dans-le-dashboard"&gt;Les variables dans le Dashboard
&lt;/h2&gt;&lt;p&gt;Pour se faire, on va devoir de nouveau trouver la valeur la plus adaptée dans notre source de données Prometheus.&lt;/p&gt;
&lt;p&gt;Je ne vais pas vous faire retourner dans Prometheus pour ça, celle que moi j’utilise, c’est celle ci :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kube_namespace_labels{namespace!~&amp;#34;kube-.*|default&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette requête PromQL permet de lister l’ensemble des namespaces présents dans votre cluster Kubernetes, puis, entre les accolades, de filtrer pour retirer les namespaces respectant les regexp &amp;ldquo;kube-.*&amp;rdquo; et &amp;ldquo;default&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2018/09/variables2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour autant, on est pas encore complètement sorti d’affaire puisqu’on doit maintenant récupérer uniquement la partie rouge dans ma capture d’écran, qui est la liste des noms des namespaces.&lt;/p&gt;
&lt;p&gt;Arrive alors Grafana, qui fournit une fonction supplémentaire label_values, et qui permet, à partir d’une liste des timeseries Prometheus, de récupérer la valeur d’un seul champ de la timeserie :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;label_values(kube_namespace_labels{namespace!~&amp;#34;kube-.*|default&amp;#34;},namespace)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et tant qu’a y être, on va également se garder sous le coude une autre requête, quasiment identique, mais qui va nous permettre d’avoir la liste des déploiements présents dans votre cluster pour un (ou plusieurs) namespaces donnés :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;label_values(kube_deployment_labels{namespace=~&amp;#34;$k8snamespace&amp;#34;}, deployment)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A partir de là, je peux ajouter des Variables dans mon Dashboard. Pour le faire, on doit cliquer sur la roue crantée en haut à droite de notre Dashboard :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_roue_crantee.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Puis ouvrir le menu &amp;ldquo;Variables&amp;rdquo; et ajouter les variables&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_variable_3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;A partir du moment où votre source de données et votre requête est entrée dans les champs &lt;em&gt;Data source&lt;/em&gt; et &lt;em&gt;query&lt;/em&gt;, Grafana va vous donner un aperçu des valeurs qui seront disponibles (tout en bas du formulaire). Un bon moyen de vérifier que tout est bon avant de passer à l’étape suivante.&lt;/p&gt;
&lt;h2 id="dans-le-dashboard-nos-variables-apparaissent-"&gt;Dans le dashboard, nos variables apparaissent !
&lt;/h2&gt;&lt;p&gt;On sauvegarde et on retourne dans notre Dashboard.&lt;/p&gt;
&lt;p&gt;Si tout s’est bien passé, on a maintenant, en haut de notre Dashboard, plusieurs variables avec des menus déroulant pour les sélectionner.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2018/09/variables3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Mais ce n’est pas magique pour autant&amp;hellip;&lt;/p&gt;
&lt;p&gt;Point un peu pénible, on va devoir maintenant modifier toutes nos visualisations (graphiques) pour prendre en compte le fait qu’on ajoute une variable.&lt;/p&gt;
&lt;p&gt;Je ne saurai donc que trop vous conseiller de bien réfléchir à &lt;em&gt;comment&lt;/em&gt; vous allez variabiliser vos Dashboard &lt;em&gt;avant&lt;/em&gt; d’avoir trop de visualisation statiques&amp;hellip;&lt;/p&gt;
&lt;h2 id="ajout-de-la-variable-dans-notre-visualisation"&gt;Ajout de la variable dans notre visualisation
&lt;/h2&gt;&lt;p&gt;On va donc ajouter la variable de manière à rendre nos graphiques dynamiques en fonction des valeurs sélectionnées dans le Dashboard, en modifiant la requête précédente par celle ci :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name=~&amp;#34;$k8sdeployment.*&amp;#34;,container_name!=&amp;#34;POD&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Qu’est ce qui a changé ?&lt;/p&gt;
&lt;p&gt;Déjà, votre requête devrait répondre beaucoup plus vite ! Et pour cause, on vient non seulement de restreindre à un seul namespace (celui correspondant à la variable Grafana $k8snamespace et non plus la valeur fixe &amp;ldquo;kube-system&amp;rdquo;) mais aussi à un seul Deployment donné (via $k8sdeployment).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: on a également dégagé les containers s’appelant &amp;ldquo;POD&amp;rdquo;, qui vont fausser les statistiques.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Pour autant, notre graphique n’est toujours pas exploitable&amp;hellip; On voit bien qu’il existe 2 Pods pour mon Deployment, et si on cherche bien on pourra voir dans la timeserie sur quel Node chaque replica tourne, mais c’est fastidieux&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_var1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;La valeur de chaque timeserie monte de manière constante. C’est normal, c’est un « uptime »&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="compter-le-nombre-de-pod-par-node"&gt;Compter le nombre de Pod par Node
&lt;/h2&gt;&lt;p&gt;On va s’en sortir en tirant parti d’une autre variable présente dans nos timeseries (qu’on a justement remarqué plus haut) : instance, ainsi que d’une fonction d’aggrégation du PromQL : count.&lt;/p&gt;
&lt;p&gt;Cette variable permet, dans notre requête de savoir sur quel Node Kubernetes se situe notre Pod.&lt;/p&gt;
&lt;p&gt;Voilà ce qu’on obtiendra en modifiant la requête de notre visualisation :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name=~&amp;#34;$k8sdeployment.*&amp;#34;,container_name!=&amp;#34;POD&amp;#34;}) by (instance)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2018/09/grafana1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Avec restriction sur le déploiement&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Là, c’est encore fastidieux, mais on commence à entrevoir la réponse à notre question initiale.
Dans les derniers graphiques, les couleurs représentent les Nodes Kubernetes, et la valeur numérique le nombre de Pods qui tournent dessus pour un Namespace donné. On voit donc que globalement, pour cet exemple précis, les Pods semblent bien répartis (puisque qu’on sélectionne un seul Deployment, on a bien un Pod par Node).&lt;/p&gt;
&lt;h2 id="et-la-solution-"&gt;Et la solution ?
&lt;/h2&gt;&lt;p&gt;Comment on fait pour voir tous les déploiements d’un coup ? Là, ça commence à devenir un peu plus touchy.&lt;/p&gt;
&lt;p&gt;En vrai, la solution n’a plus vraiment d’intérêt en terme de découverte dans l’utilisation de Prometheus et de Grafana, dont j’ai montré les fonctionnalités lors des précédents paragraphes. Cependant, je ne vais pas vous laisser sur un cliffhanger ;-)&lt;/p&gt;
&lt;p&gt;Je ne vais &lt;em&gt;tout&lt;/em&gt; détailler, mais dans l’idée, la solution que j’ai trouvée (il y en a peut être de plus élégantes) est :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;de lister tous les couples nom du déploiement + nom de l’hôte qui l’héberge&lt;/li&gt;
&lt;li&gt;de regrouper &lt;em&gt;par déploiement&lt;/em&gt; les items de la liste précédente et d’en faire une somme pour chaque déploiement&lt;/li&gt;
&lt;li&gt;de diviser chacun des items de cette dernière liste par le nombre total de container par déploiement&lt;/li&gt;
&lt;li&gt;d’afficher la liste des valeurs inférieures à 1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1. count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name,instance))
2. count(count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name,instance)) by (container_name)
3. count(count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name,instance)) by (container_name) / count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name)
4. count(count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name,instance)) by (container_name) / count(container_last_seen{namespace=~&amp;#34;$k8snamespace&amp;#34;,container_name!~&amp;#34;^$|POD&amp;#34;}) by (container_name) &amp;lt; 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ainsi, avec cette dernière formule, on peut lister l’ensemble des Deployments Kubernetes dont il existe un nombre de replica supérieur au nombre de Nodes qui les hébergent (et donc, de dénicher des soucis sur l’anti-affinités et par extension, de potentiels SPOF).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2018/09/grafana6.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;CQFD :-D&lt;/p&gt;</description></item><item><title>Proxmox VE + Prometheus = &lt;3</title><link>https://blog.zwindler.fr/2020/01/06/proxmox-ve-prometheus/</link><pubDate>Mon, 06 Jan 2020 07:15:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/01/06/proxmox-ve-prometheus/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/11/grafana_prometheus_proxmox.webp" alt="Featured image of post Proxmox VE + Prometheus = &lt;3" /&gt;&lt;h2 id="proxmox-et-prometheus-sont-dans-un-bateau"&gt;Proxmox et Prometheus sont dans un bateau&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Si vous avez suivi le précédent &lt;a class="link" href="https://blog.zwindler.fr/2019/11/12/tutoriel-installer-prometheus-grafana-sans-docker/" &gt;article sur Prometheus et Grafana&lt;/a&gt;, vous m’avez peut être vu teaser cet article.&lt;/p&gt;
&lt;p&gt;En effet, j’avais mis une capture d’écran d’un dashboard Grafana avec des métriques provenant de mon cluster Proxmox VE :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/11/grafana_prometheus_proxmox.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On fait du LXC à fond ici !caption&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="petit-récap"&gt;Petit récap’
&lt;/h2&gt;&lt;p&gt;Pour rappel, &lt;a class="link" href="https://blog.zwindler.fr/2019/11/12/tutoriel-installer-prometheus-grafana-sans-docker/" &gt;dans le tuto précédent&lt;/a&gt;, on avait installé le couple Grafana + Prometheus sur une machine virtuelle (ou physique peu importe), et pas dans un container (comme le préconise la plupart des billets de blogs que j’ai pu lire). Maintenant vous comprenez surement mieux pourquoi ;-).&lt;/p&gt;
&lt;p&gt;Pour alimenter notre Prometheus, on va donc vouloir le donner à manger. Et quoi de mieux dans une infrastructure &lt;em&gt;non containerisée&lt;/em&gt; que les métriques de l’hyperviseur ?&lt;/p&gt;
&lt;h2 id="prometheus-pve-exporter"&gt;prometheus-pve-exporter
&lt;/h2&gt;&lt;p&gt;On est plutôt gâté avec Proxmox VE, car les métriques pertinentes sont assez nombreuses et surtout exposées par API.&lt;/p&gt;
&lt;p&gt;S’il n’existe pas d’&lt;em&gt;exporter officiel&lt;/em&gt;, il existe néanmoins des implémentations Open Source réalisées par de gentils contributeurs.&lt;/p&gt;
&lt;p&gt;La plus utilisée semble être celle de &lt;strong&gt;znerol&lt;/strong&gt;, qui a en plus l’avantage d’être la base utilisée dans un dashboard sur le site de Grafana (on y reviendra). Dans la mesure du possible, j’essaye de rester sur les implémentations les plus couramment utilisées. Sauf exception, ça permet d’éviter d’être le seul à avoir un bug. Je vous ai mis une autre implémentation dans les sources en bas d’article, que je n’ai pas testée.&lt;/p&gt;
&lt;p&gt;Les sources et la documentation sont disponibles sur Github à l’adresse suivante : &lt;a class="link" href="https://github.com/znerol/prometheus-pve-exporter" target="_blank" rel="noopener"
&gt;github.com/znerol/prometheus-pve-exporter&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Dans tous les cas, on va devoir créer un utilisateur dans Proxmox VE, a qui on va autoriser l’accès aux métriques depuis l’API. C’est cet utilisateur qu’utilisera notre &lt;em&gt;exporter&lt;/em&gt; pour se connecter à PVE, récupérer les métriques et enfin les exposer au format &lt;a class="link" href="https://openmetrics.io/" target="_blank" rel="noopener"
&gt;OpenMetrics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sur un des serveurs PVE du cluster :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;créer un groupe&lt;/li&gt;
&lt;li&gt;ajouter le rôle PVEAuditor au groupe&lt;/li&gt;
&lt;li&gt;créer un utilisateur&lt;/li&gt;
&lt;li&gt;lui ajouter le groupe, puis un mot de passe&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pveum groupadd monitoring -comment &amp;#39;Monitoring group&amp;#39;
pveum aclmod / -group monitoring -role PVEAuditor
pveum useradd pve_exporter@pve
pveum usermod pve_exporter@pve -group monitoring
pveum passwd pve_exporter@pve
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="installation-de-lexporter"&gt;Installation de l’&lt;em&gt;exporter&lt;/em&gt;
&lt;/h2&gt;&lt;p&gt;A partir de là, on peut installer l’&lt;em&gt;exporter&lt;/em&gt; sur nos serveurs PVE. L’avantage du cet &lt;em&gt;exporter&lt;/em&gt; c’est qu’il sait gérer le cluster. Je veux dire par là qu’avec un seul &lt;em&gt;exporter&lt;/em&gt; vous allez pouvoir collecter l’ensemble des métriques de l’ensemble de vos machines du cluster (containers, VMs, stockage, hyperviseurs, &amp;hellip;).&lt;/p&gt;
&lt;p&gt;En théorie, il n’est donc nécessaire de l’installer que sur une machine. Pour autant, je vous conseille quand même d’installer un &lt;em&gt;exporter&lt;/em&gt; par serveur. Dans les faits, cela vous évitera de perdre toute collecte de données de supervision en cas de panne du seul serveur portant l’&lt;em&gt;exporter&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Sur vos serveurs PVE, lancer les commandes suivantes :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt-get install python-pip
pip install prometheus-pve-exporter
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette implémentation utilise le gestionnaire de paquet de Python, pip.&lt;/p&gt;
&lt;p&gt;On va ensuite créer un fichier de configuration qui va contenir les informations de connexion à notre PVE :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir -p /usr/share/pve_exporter/
cat &amp;gt; /usr/share/pve_exporter/pve_exporter.yml &amp;lt;&amp;lt; EOF
default:
user: pve_exporter@pve
password: myawesomepassword
verify_ssl: false
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Note : remplacer &lt;strong&gt;myawesomepassword&lt;/strong&gt; par un mot de passe vraiment cool.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Temporairement, vous pouvez lancer le binaire manuellement pour voir si ça fonctionne correctement :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/local/bin/pve_exporter /usr/share/pve_exporter/pve_exporter.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si tout s’est bien passé, on va maintenant créer un script de démarrage &lt;em&gt;systemd&lt;/em&gt; pour que notre exporter se démarre tout seul avec l’hyperviseur :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat &amp;gt; /etc/systemd/system/pve_exporter.service &amp;lt;&amp;lt; EOF
[Unit]
Description=Proxmox VE Prometheus Exporter
After=network.target
Wants=network.target
[Service]
Restart=on-failure
WorkingDirectory=/usr/share/pve_exporter
ExecStart=/usr/local/bin/pve_exporter /usr/share/pve_exporter/pve_exporter.yml 9221 192.168.1.1
[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Note : remplacer 192.168.1.1 par l’adresse IP de votre serveur Proxmox VE (aussi accessible par votre serveur Prometheus)&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable pve_exporter
systemctl start pve_exporter
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="la-collecte"&gt;La collecte
&lt;/h2&gt;&lt;p&gt;On a maintenant un endpoint au format OpenMetrics qui peut être collecté par Prometheus. Cool !!&lt;/p&gt;
&lt;p&gt;Le but du jeu va être maintenant d’informer Prometheus qu’il doit scrapper notre &lt;em&gt;exporter&lt;/em&gt;. On va faire ça en ajoutant la configuration suivante à notre serveur Prometheus (puis le redémarrer) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vi /usr/share/prometheus/prometheus.yml
[...]
scrape_configs:
[...]
- job_name: &amp;#39;pve&amp;#39;
static_configs:
- targets:
- 192.168.1.1:9221 # Proxmox VE node with PVE exporter.
- 192.168.1.2:9221 # Proxmox VE node with PVE exporter.
metrics_path: /pve
params:
module: [default]
systemctl restart prometheus.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Note : remplacer les IPs par les adresses IP de vos exporters sur vos serveurs PVE.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="visualiser-tout-ça"&gt;Visualiser tout ça
&lt;/h2&gt;&lt;p&gt;Dernière étape avant d’aller prendre un café, afficher tout ça dans un dashboard. Là ça aurait pu être trivial mais j’ai du bidouiller (un tout petit peu).&lt;/p&gt;
&lt;p&gt;Je l’ai dis au début de l’article, un des avantages de cet &lt;em&gt;exporter&lt;/em&gt;, c’est que quelqu’un a pris la peine de faire un dashboard dans Grafana qui affiche déjà tout sans qu’on ait besoin de faire quoique ce soit.&lt;/p&gt;
&lt;p&gt;On peut donc l’installer juste en copiant l’URL ou l’ID dans notre Grafana &lt;a class="link" href="https://grafana.com/grafana/dashboards/10347" target="_blank" rel="noopener"
&gt;10347&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_add_dashboard.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_add_dashboard2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Trivial !&lt;/p&gt;
&lt;p&gt;La seule petite difficulté, c’est que ce Dashboard gère mal le clustering. Plus particulièrement, il n’aime pas qu’un meme exporter remonte les données de plusieurs nodes, ce qui est dommage pour un cluster.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_add_dashboard_ko.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J’ai donc tweaké le Dashboard en y ajoutant une variable &amp;ldquo;node&amp;rdquo;, permettant de sélectionner les métriques du node qu’on veut (uniquement).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/04/proxmox_grafana_variable.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Enfin, j&amp;rsquo;ai modifié les graphiques concernés en ajoutant un filtre de type &lt;code&gt;id=&amp;quot;node/$node&amp;quot;&lt;/code&gt; utilisant cette variable dans la requête PromQL.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/12/grafana_add_node_in_promql.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Vous avez maintenant un Dashboard qui remonte les métriques de vos serveurs, stockages, vms et containers dans Proxmox VE ! A vous l’observabilité !&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources
&lt;/h2&gt;&lt;p&gt;Une autre implémentation : &lt;a class="link" href="https://github.com/wakeful/pve_exporter" target="_blank" rel="noopener"
&gt;wakeful/pve_exporter&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[Tutoriel] Installer Prometheus/Grafana sans Docker</title><link>https://blog.zwindler.fr/2019/11/12/tutoriel-installer-prometheus-grafana-sans-docker/</link><pubDate>Tue, 12 Nov 2019 07:30:45 +0000</pubDate><guid>https://blog.zwindler.fr/2019/11/12/tutoriel-installer-prometheus-grafana-sans-docker/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/11/grafana_prometheus_proxmox.webp" alt="Featured image of post [Tutoriel] Installer Prometheus/Grafana sans Docker" /&gt;&lt;h2 id="prometheus-et-grafana-dans-docker-quelle-horreur-"&gt;Prometheus et Grafana dans Docker, quelle horreur ?
&lt;/h2&gt;&lt;p&gt;Je sais que certains d’entre vous ne sont pas super fan (euphémisme) de la technologique containers Docker (et je ne parle même pas de Kubernetes, cf &lt;a class="link" href="https://blog.zwindler.fr/2019/09/03/concerning-kubernetes-combien-de-problemes-ces-stacks-ont-generes/" &gt;Concerning Kubernetes&lt;/a&gt;). Pour autant, pas besoin de Docker pour avoir besoin du couple Prometheus / Grafana.&lt;/p&gt;
&lt;p&gt;Prometheus a plein de features sympas (notamment l’auto discovery, le langage de requêtage PromQL, &amp;hellip;). De son côté, Grafana est vraiment top pour ce qui est visualisation rapide provenant de plusieurs sources de données.&lt;/p&gt;
&lt;p&gt;Peut être même que vous avez du Docker (ou même Kubernetes) mais que vous n’avez pas envie d’intégrer la supervision dans votre infra de compute.&lt;/p&gt;
&lt;p&gt;Il y a plein de bonnes raisons pour ça, comme ne pas vouloir héberger la supervision sur l’infra qu’elle est censé surveillée ou encore pour des problématiques de performances, &amp;hellip;&lt;/p&gt;
&lt;p&gt;Avec Docker, lancer Prometheus ou Grafana se fait en une ligne de commande, c’est pour ça qu’on voit cette manière de faire partout. Sans Docker, c’est nécessairement un poil plus compliqué (mais à peine) à faire. Et c’est pourquoi je fais ce petit tuto rapide.&lt;/p&gt;
&lt;h2 id="prometheus"&gt;Prometheus
&lt;/h2&gt;&lt;p&gt;Dans ce tuto, on va partir des sources. Pour &lt;a class="link" href="https://prometheus.io/download/" target="_blank" rel="noopener"
&gt;Prometheus&lt;/a&gt;, vous pourrez trouver un raccourci vers la dernière version &lt;a class="link" href="https://prometheus.io/download/" target="_blank" rel="noopener"
&gt;sur le site officiel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On télécharge cette version et on configure un utilisateur exécuter Prometheus&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;wget https://github.com/prometheus/prometheus/releases/download/v2.13.1/prometheus-2.13.1.linux-amd64.tar.gz
tar xzf prometheus-2.13.1.linux-amd64.tar.gz
sudo mv prometheus-2.13.1.linux-amd64/ /usr/share/prometheus
sudo useradd -u 3434 -d /usr/share/prometheus -s /bin/false prometheus
sudo mkdir -p /var/lib/prometheus/data
sudo chown prometheus:prometheus /var/lib/prometheus/data
sudo chown -R prometheus:prometheus /usr/share/prometheus
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Une fois que c’est fait, le mieux c’est de tester que Prometheus &amp;ldquo;fonctionne&amp;rdquo; correctement en le lançant à la main pour voir si le logiciel se lance bien, avec la configuration par défaut.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/share/prometheus/prometheus --config.file=/usr/share/prometheus/prometheus.yml
[...]
level=info ts=2019-09-20T14:56:18.244Z caller=main.go:768 msg=&amp;#34;Completed loading of configuration file&amp;#34; filename=/usr/share/prometheus/prometheus.yml
level=info ts=2019-09-20T14:56:18.244Z caller=main.go:623 msg=&amp;#34;Server is ready to receive web requests.&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ici tout s’est bien passé, on peut donc le couper (Ctrl-C) et créer un script SystemD pour pouvoir le démarrer automatiquement avec le serveur.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo vi /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network-online.target
[Service]
User=prometheus
Restart=on-failure
WorkingDirectory=/usr/share/prometheus
ExecStart=/usr/share/prometheus/prometheus --config.file=/usr/share/prometheus/prometheus.yml
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable prometheus
sudo systemctl start prometheus
sudo systemctl status prometheus
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="grafana"&gt;Grafana
&lt;/h2&gt;&lt;p&gt;Maintenant que c’est fait, on passe à Grafana. Vous allez voir, ça va aussi vite.&lt;/p&gt;
&lt;p&gt;Dans le cas de Grafana, &lt;a class="link" href="https://grafana.com/grafana/download" target="_blank" rel="noopener"
&gt;la méthode mise en avant sur le site officiel&lt;/a&gt; est l’utilisation des packages systèmes (&amp;quot;.deb&amp;quot; pour Debian ou Ubuntu, RPM pour CentOS). Par souci de cohérence dans l’article, je ne vais pas utiliser le .deb et installer le binaire précompilé pour l’installer de la même manière que Prometheus. Cependant, le .deb aurait très bien fait l’affaire (et ça ira plus vite si vous êtes pressés).&lt;/p&gt;
&lt;p&gt;On télécharge donc la version précompilée, on positionne les bons dossiers/binaires/fichiers de config aux bons endroits.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;wget https://dl.grafana.com/oss/release/grafana-6.4.4.linux-amd64.tar.gz
tar -xzf grafana-6.4.4.linux-amd64.tar.gz
sudo useradd -d /usr/share/grafana -s /bin/false grafana
sudo mkdir -p /var/lib/grafana/plugins /etc/grafana /var/log/grafana
sudo chown -R grafana:grafana /var/lib/grafana
sudo mv grafana-6.4.4/ /usr/share/grafana
sudo cp /usr/share/grafana/bin/grafana-server /usr/sbin/
sudo cp /usr/share/grafana/conf/sample.ini /etc/grafana/grafana.ini
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On configure systemD puis on démarre le service.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo vi /etc/default/grafana-server
GRAFANA_USER=grafana
GRAFANA_GROUP=grafana
GRAFANA_HOME=/usr/share/grafana
LOG_DIR=/var/log/grafana
DATA_DIR=/var/lib/grafana
MAX_OPEN_FILES=10000
CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
PLUGINS_DIR=/var/lib/grafana/plugins
PROVISIONING_CFG_DIR=/etc/grafana/provisioning
PID_FILE_DIR=/var/run/grafana
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;sudo vi /etc/systemd/system/multi-user.target.wants/grafana-server.service
[Unit]
Description=Grafana instance
Documentation=http://docs.grafana.org
Wants=network-online.target
After=network-online.target
After=postgresql.service mariadb.service mysql.service
[Service]
EnvironmentFile=/etc/default/grafana-server
User=grafana
Group=grafana
Type=simple
Restart=on-failure
WorkingDirectory=/usr/share/grafana
RuntimeDirectory=grafana
RuntimeDirectoryMode=0750
ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE_DIR}/grafana-server.pid \
cfg:default.paths.logs=${LOG_DIR} \
cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.plugins=${PLUGINS_DIR} \
cfg:default.paths.provisioning=${PROVISIONING_CFG_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
UMask=0027
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl start grafana-server
systemctl enable grafana-server
systemctl status grafana-server
[...]
Nov 10 15:40:17 nostromo grafana-server[14606]: t=2019-11-10T15:40:17+0000 lvl=info msg=&amp;#34;Initializing Stream Manager&amp;#34;
Nov 10 15:40:17 nostromo grafana-server[14606]: t=2019-11-10T15:40:17+0000 lvl=info msg=&amp;#34;HTTP Server Listen&amp;#34; logger=http.server address=0.0.0.0:3000 protocol
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="on-cable-le-tout-ensemble"&gt;On cable le tout ensemble
&lt;/h2&gt;&lt;p&gt;Vous avez vu, je vous avais pas menti, c’est assez simple en fait.&lt;/p&gt;
&lt;p&gt;Maintenant que Prometheus et Grafana tournent, on va les coufigurer pour qu’ils parlent ensemble.&lt;/p&gt;
&lt;p&gt;Tout se passe sur l’interface d’administration de Grafana, qui devrait maintenant être accessible à l’URL http://@IP_de_votre_serveur:3000&lt;/p&gt;
&lt;p&gt;Authentifiez vous en tant qu’administrateur. Par défaut à la première instanciation, seul un compte &amp;ldquo;admin&amp;rdquo; est créé, avec le mot de passe hautement sécurisé &amp;ldquo;admin&amp;rdquo;. Heureusement on change ça tout de suite&amp;hellip;&lt;/p&gt;
&lt;p&gt;La dernière étape consiste à simplement se rendre dans la partie administration de Grafana, puis de créer un source de données.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/11/grafana_data_source.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Il existe de nombreuses sources de données différentes. Nous dans notre cas, c’est bien une source de type Prometheus qu’on veut créer.&lt;/p&gt;
&lt;p&gt;Renseignez simplement l’URL d’accès à Prometheus (par défaut http://localhost:9090 dans ce tutoriel) et sauvez.&lt;/p&gt;
&lt;p&gt;A partir de maintenant, vous avez un couple Prometheus / Grafana fonctionnel. Vous allez pouvoir commencer à créer des Dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2019/11/grafana_prometheus_proxmox.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Enjoy !&lt;/p&gt;</description></item></channel></rss>