<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Job on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/job/</link><description>Recent content in Job on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-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/job/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>kubectl tips and tricks n°2</title><link>https://blog.zwindler.fr/2020/01/20/kubectl-tips-and-tricks-n2/</link><pubDate>Mon, 20 Jan 2020 07:30:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/01/20/kubectl-tips-and-tricks-n2/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/10/kubectl2.webp" alt="Featured image of post kubectl tips and tricks n°2" /&gt;&lt;h2 id="kubectl"&gt;kubectl
&lt;/h2&gt;&lt;p&gt;Comme vous pouvez le voir, il s’agit du 2ème article d’une série sur la productivité quand on est dans un environnement Kubernetes. Et qui dit productivité dit forcément ligne de commande, donc kubectl :trollface:!&lt;/p&gt;
&lt;p&gt;Le premier article, si vous l’avez loupé, est toujours disponible ici : &lt;a class="link" href="https://blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/" &gt;kubectl tips and tricks n°1&lt;/a&gt;. J’avais parlé du flag &amp;ldquo;wait&amp;rdquo;, de comment relancer un Job ou un CronJob et de comment utiliser les &lt;em&gt;selectors&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Et j’ai aussi &lt;a class="link" href="https://blog.zwindler.fr/2018/08/28/utiliser-kubectx-kubens-pour-changer-facilement-de-context-et-de-namespace-dans-kubernetes/" &gt;écris un article sur kubectx et kubens qui pourrait vous plaire&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Normalement, les astuces que je vais vous montrer ici ne sont pas dans la plupart des tutos que j’ai pu trouver sur le net. Cet article se concentre tout particulièrement sur les Secrets de Kubernetes.&lt;/p&gt;
&lt;p&gt;C’est parti pour du fun avec kubectl !&lt;/p&gt;
&lt;h2 id="encoderdécoder-facilement-en-base64-les-secrets"&gt;Encoder/décoder facilement en base64 les Secrets
&lt;/h2&gt;&lt;p&gt;Quelque chose qu’on a TOUT le temps à faire quand on manipule les objets de type Secrets dans Kubernetes, c’est d’afficher en clair les &amp;ldquo;secrets&amp;rdquo; contenus dans notre Secret (ou de les encoder).&lt;/p&gt;
&lt;p&gt;Car, pour ceux qui ne le savent pas, les Secrets dans Kubernetes ne sont malheureusement pas très secrets, puisqu’il s’agit ni plus ni moins que des strings encodées en base64 (ce qui est donc TOUT sauf secure). A vrai dire, je me demande même pourquoi s’être embêter à les encoder tout court. La seule sécurité qu’on ajoute par rapport aux ConfigMaps, c’est simplement que la string n’est pas lisible par un humain qui passerait sa tête par dessus votre épaule.&lt;/p&gt;
&lt;p&gt;Enfin bref, vous allez surement devoir encoder ou décoder des strings en base64 et c’est parfois un peu pénible. La méthode communément admise est simplement d’utiliser les binaires linux echo et base64.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo &amp;#34;ma string&amp;#34; | base64
bWEgc3RyaW5nCg==
echo bWEgc3RyaW5nCg== | base64 -d
ma string
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C’est relou à taper, mais c’est relativement trivial.&lt;/p&gt;
&lt;h2 id="its-a-trap-"&gt;It’s a trap !
&lt;/h2&gt;&lt;p&gt;Sauf qu’il y a des pièges !&lt;/p&gt;
&lt;p&gt;Le premier vous l’aurez à l’encodage. Dans mon premier exemple, la string est très courte. Et parfois, la taille compte.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo &amp;#34;ma string très longue string pour montrer que ça va pas le faire&amp;#34; | base64
bWEgc3RyaW5nIHRyw6hzIGxvbmd1ZSBzdHJpbmcgcG91ciBtb250cmVyIHF1ZSDDp2EgdmEgcGFz
IGxlIGZhaXJlCg==
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ici, on se retrouve avec un saut de ligne dans notre string en sortie. Mais, si vous copiez collez ça dans votre YAML Kubernetes, vous allez vous prendre une bonne grosse erreur de syntaxe.&lt;/p&gt;
&lt;p&gt;Le YAML ne sera valide que si vous mettez la string complète, sur une seule ligne.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo &amp;#34;ma string très longue string pour montrer que ça va pas le faire&amp;#34; | base64 -w0
bWEgc3RyaW5nIHRyw6hzIGxvbmd1ZSBzdHJpbmcgcG91ciBtb250cmVyIHF1ZSDDp2EgdmEgcGFzIGxlIGZhaXJlCg==
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="et-cest-pas-fini-"&gt;Et c’est pas fini !
&lt;/h2&gt;&lt;p&gt;Le 2 ème piège est encore un souci de saut de ligne, mais dans la string en base64 cette fois.&lt;/p&gt;
&lt;p&gt;En fait, c’est hyper traitre car vous n’allez pas le voir à l’écran de prime abord, mais il faut savoir que echo rajoute un saut de ligne à la fin de votre string. Le retour que vous avez eu en base64 contient donc un saut de ligne, qui sera quasiment systèmatiquement non souhaité lorsqu’on gère des Secrets.&lt;/p&gt;
&lt;p&gt;La bonne commande n’est donc pas echo mais echo -n !&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#Pas bien
echo &amp;#34;ma string&amp;#34; | base64
bWEgc3RyaW5nCg==
#Bien
echo -n &amp;#34;ma string&amp;#34; | base64 -w0
bWEgc3RyaW5n
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="ok-ça-commence-à-devenir-franchement-pénible"&gt;Ok ça commence à devenir franchement pénible&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Pour décoder heureusement, c’est plus simple. La commande donnée au début suffit, même s’il sera plus safe de rajouter le &amp;ldquo;-n&amp;rdquo; au echo :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo -n bWEgc3RyaW5n | base64 -d
ma string
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="gagner-quelques-caractères"&gt;Gagner quelques caractères
&lt;/h2&gt;&lt;p&gt;Comme je suis fainéant, j’ai cherché une astuce pour gagner quelques caractères à taper en moins. Il existe une solution, mais qui ne marche malheureusement que pour decode, puisque dans le cas de l’encodage on risquera d’ajouter un saut de ligne non souhaité :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo bWEgc3RyaW5n | base64 -d
base64 -d &amp;lt;&amp;lt;&amp;lt; bWEgc3RyaW5n
ma string
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On vient de s’économiser 3 caractères (waaaah) mais surtout un &amp;ldquo;|&amp;rdquo;, bien plus pénible à faire sur un clavier azerty standard que 3 &amp;ldquo;&amp;lt;&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Je vous l’accorde, c’est pas foufou.&lt;/p&gt;
&lt;h2 id="un-peu-plus-simple"&gt;Un peu plus simple
&lt;/h2&gt;&lt;p&gt;Heureusement, mon collègue Julien (aka JUL, car il est fan de JUL, bien entendu) nous a écris un petit script pour nous faciliter la vie, donc je vous le partage :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dgermain:~$ cat &amp;gt; b64 &amp;lt;&amp;lt;EOF
&amp;gt; #!/bin/bash
&amp;gt; echo -e &amp;#34;Base64 encoding.. \n&amp;#34;
&amp;gt; for arg in &amp;#34;\$@&amp;#34;; do
&amp;gt; echo &amp;#34;\$arg :&amp;#34;
&amp;gt; echo -n &amp;#34;\$arg&amp;#34; | base64
&amp;gt; echo
&amp;gt; done
&amp;gt; EOF
dgermain:~$ cat &amp;gt; b64d &amp;lt;&amp;lt;EOF
&amp;gt; #!/bin/bash
&amp;gt; echo -e &amp;#34;Base64 decoding.. \n&amp;#34;
&amp;gt; for arg in &amp;#34;\$@&amp;#34;; do
&amp;gt; echo &amp;#34;\$arg :&amp;#34;
&amp;gt; echo -n &amp;#34;\$arg&amp;#34; | base64 -d
&amp;gt; echo
&amp;gt; done
&amp;gt; EOF
dgermain:~$ sudo cp b64* /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vous pouvez maintenant invoquer directement b64 suivi d’un certain nombre de strings pour avoir leur valeur encodée, ou b64d suivi d’un certain nombre de string pour les décoder.&lt;/p&gt;
&lt;h2 id="dernière-astuce-et-après-jarrête"&gt;Dernière astuce et après j’arrête
&lt;/h2&gt;&lt;p&gt;Si jamais vous voulez à l’écran toutes les strings encodées en base64 dans un Secret Kubernetes en une seule étape, j’ai également trouvé ce &lt;a class="link" href="https://stackoverflow.com/questions/56909180/decoding-kubernetes-secret" target="_blank" rel="noopener"
&gt;&lt;em&gt;oneliner&lt;/em&gt; pas piqué des hannetons sur Stackoverflow&lt;/a&gt; qui tire parti de la possibilité de faire des gotemplates directement dans kubectl :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get secret name-of-secret -o go-template=&amp;#39;
{{range $k,$v := .data}}{{printf &amp;#34;%s: &amp;#34; $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{&amp;#34;\n&amp;#34;}}{{end}}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il faudra probablement que je fasse un article entier sur le go-templating avec kubectl car c’est juste ouf ce qu’on peut faire avec ;-)&lt;/p&gt;</description></item><item><title>kubectl tips and tricks n°1</title><link>https://blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/</link><pubDate>Wed, 30 Oct 2019 07:00:44 +0000</pubDate><guid>https://blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/10/kubectl2.webp" alt="Featured image of post kubectl tips and tricks n°1" /&gt;&lt;h2 id="kubectl"&gt;kubectl
&lt;/h2&gt;&lt;p&gt;Ça fait un moment que je garde sous le coudes quelques petites tips pour améliorer votre productivité via la CLI de Kubernetes kubectl.&lt;/p&gt;
&lt;p&gt;Rassurez vous, je ne vais pas faire un énième article sur l’autocomplétion ou autre info triviale comme &amp;ldquo;vous savez que vous pouvez stocker plusieurs contexts dans votre kubectl ?&amp;rdquo; #shocking. (Si ce dernier point vous intéresse, &lt;a class="link" href="https://blog.zwindler.fr/2018/08/28/utiliser-kubectx-kubens-pour-changer-facilement-de-context-et-de-namespace-dans-kubernetes/" &gt;j’avais fais un article sur kubectx et kubens qui va surement vous plaire&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Normalement, les astuces que je vais vous montrer ici ne sont pas dans la plupart des tutos que j’ai pu trouver sur le net.&lt;/p&gt;
&lt;p&gt;C’est parti pour du fun avec kubectl !&lt;/p&gt;
&lt;h2 id="pour-les-accrocs-à-la-flèche-du-haut"&gt;Pour les accrocs à la flèche du haut
&lt;/h2&gt;&lt;p&gt;Si, comme moi, vous faites partie de ces gens impatients qui ne peuvent pas prendre un café le temps qu’une opération se termine toute seule et que vous appuyez frénétiquement une la combinaison &amp;ldquo;flèche du haut + entrée&amp;rdquo; pour rappeler la dernière commande &amp;ldquo;kubectl get monobjetquejattendavecimpatience&amp;rdquo;, ce paragraphe est pour vous.&lt;/p&gt;
&lt;p&gt;Lors d’une commande de type &amp;ldquo;get&amp;rdquo; avec &lt;em&gt;kubectl&lt;/em&gt;, il existe un flag &amp;ldquo;-w&amp;rdquo; qui fait&amp;hellip; oui vous l’avez deviné&amp;hellip; un genre de watch sur le (ou les) objet(s) que vous attendez.&lt;/p&gt;
&lt;p&gt;Par exemple, dans le cas où vous souhaiteriez créer un objet &lt;em&gt;Service&lt;/em&gt; de type &lt;em&gt;LoadBalancer&lt;/em&gt; et que vous avez hâte que votre cloud provider vous assigne une IP publique, utilisez la commande suivante :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=cephk8s get svc traefik-ingress-controller --namespace kube-system -w
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik-ingress-controller LoadBalancer 10.0.21.75 &amp;lt;pending&amp;gt; 80:32347/TCP,443:30388/TCP 45s
traefik-ingress-controller LoadBalancer 10.0.21.75 40.89.187.183 80:32347/TCP,443:30388/TCP 56s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans un premier temps, seul la première ligne &lt;em&gt;LoadBalancer&lt;/em&gt; s’affichera (tant que l’état restera à &amp;ldquo;Pending&amp;rdquo;), puis dès qu’il y aura une modification, la dernière ligne, avec l’IP publique, s’affichera.&lt;/p&gt;
&lt;p&gt;C’est également pratique si vous avez des &lt;em&gt;Pods&lt;/em&gt; qui mettent du temps à s’initialiser dans un &lt;em&gt;Déploiement&lt;/em&gt; complexe.&lt;/p&gt;
&lt;h2 id="relancer-un-job"&gt;Relancer un job
&lt;/h2&gt;&lt;p&gt;Kubernetes, en bon orchestrateur qu’il est, est capable de lancer des tâches planifiées. C’est super pratique lorsque vous avez des tâches bien précises à réaliser mais qu’il n’y a pas de raison de laisser un container tourner H24 pour ça.&lt;/p&gt;
&lt;p&gt;On a donc la notion d’objet Kubernetes &lt;em&gt;Job&lt;/em&gt; et de &lt;em&gt;Cronjob&lt;/em&gt;. Sans rentrer dans les détails, le &lt;em&gt;Job&lt;/em&gt;, c’est celui qui exécute la tâche (il lance un Pod qui contient un ou plusieurs containers). Le &lt;em&gt;Cronjob&lt;/em&gt;, c’est une surcouche qui lance périodiquement le &lt;em&gt;Job&lt;/em&gt;. Rien de bien sorcier.&lt;/p&gt;
&lt;p&gt;Malheureusement pour nous, il arrive que nos Jobs échouent. Il n’y avait pas assez de ressources, ou alors on est tombé sur un bug, ou alors c’est &amp;ldquo;la faute à pas de chance&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Qu’à cela ne tienne, il faut que votre Job tourne aujourd’hui, vous voulez donc le relancer. Pas de chance pour vous, &amp;ldquo;by design&amp;rdquo;, les &lt;em&gt;Jobs&lt;/em&gt; ne peuvent pas être relancés dans Kubernetes.&lt;/p&gt;
&lt;p&gt;Jusqu’à récemment, il n’y avait pas de solution propre pour relancer un Job avec kubectl. Il fallait donc passer par un exposer du JSON du Job pour en recréer un nouveau identique en tout point. &amp;ldquo;Crude but effective&amp;rdquo; comme disent nos amis anglosaxons.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=moncontext --namespace=monnamespace get job monjob -o json | jq &amp;#39;del(.spec.selector)&amp;#39; | jq &amp;#39;del(.spec.template.metadata.labels)&amp;#39; | kubectl --context=moncontext --namespace=monnamespace replace --force -f -
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si vous voulez plus de détails sur ce que oneliner fait vraiment : la première partie dump en JSON la configuration du job, la partie du milieu retire les données spécifiques au Job qui a échoué (autogénéré à la création du Job) et la dernière partie réinjecte le job dans Kubernetes, ce qui a pour effet de le relancer.&lt;/p&gt;
&lt;p&gt;Pas mal hein ?&lt;/p&gt;
&lt;p&gt;Mais&amp;hellip; si vous avez une version plus récente, vous avez de la chance, cette option est maintenant disponible par défaut dans &lt;em&gt;kubectl&lt;/em&gt;, vous permettant de créer de manière unitaire un &lt;em&gt;Job&lt;/em&gt; à partir d’un template contenu dans un &lt;em&gt;Cronjob&lt;/em&gt;, ce qui revient au même.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=moncontext --namespace=monnamespace create job --from=cronjob/lecronjobmaitre unnomuniquepourlejobrelancé
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="les-selecteurs-dans-vos-kubectl"&gt;Les selecteurs dans vos kubectl
&lt;/h2&gt;&lt;p&gt;Last but not least, il est possible de réaliser des opérations sur plusieurs objets d’un même type en même temps.&lt;/p&gt;
&lt;p&gt;La manière la plus simple de le faire est simplement d’ajouter les noms de tous les objets à la fin de votre commande. Par exemple, la commande suivante va supprimer les &lt;em&gt;Pods&lt;/em&gt; pod1, pod2 et pod1000 :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=moncontexte --namespace==monnamespace delete pods pod1 pod2 pod1000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cependant, on va rarement supprimer des pods (ou autre objet) au hasard. Généralement, on va vouloir supprimer tous les pods d’un même déploiement, ou alors supprimer tous les objets de la même applications, ou encore scaler tous les déploiements d’un même client.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, si vous avez bien fait votre travail, tous ces objets Kubernetes auront tous les labels cohérents les uns avec les autres. En partant du principe que les pods de l’exemple précédents ont tous le label app=blopiblop, je peux donc exécuter la commande suivante pour gagner du temps :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=moncontexte --namespace==monnamespace delete pods --selector=app=blopiblop
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Attention à ne pas vous tromper dans les labels, c’est très puissant ;-)&lt;/p&gt;</description></item></channel></rss>