<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubectl on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/kubectl/</link><description>Recent content in Kubectl on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 17 Mar 2026 18:00:00 +0100</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/kubectl/index.xml" rel="self" type="application/rss+xml"/><item><title>GenAI et développement logiciel, épisode 2 : kubectl-debug-pvc, de l'idée à krew en 2x30 minutes</title><link>https://blog.zwindler.fr/2026/03/16/genai-llm-dev-kubectl-debug-pvc/</link><pubDate>Tue, 17 Mar 2026 18:00:00 +0100</pubDate><guid>https://blog.zwindler.fr/2026/03/16/genai-llm-dev-kubectl-debug-pvc/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/03/kubectl-debug-pvc.webp" alt="Featured image of post GenAI et développement logiciel, épisode 2 : kubectl-debug-pvc, de l'idée à krew en 2x30 minutes" /&gt;&lt;h2 id="précédemment-dans-genai-et-dev"&gt;Précédemment, dans &amp;ldquo;GenAI et dev&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Dans &lt;a class="link" href="https://blog.zwindler.fr/2026/03/08/genai-llm-dev-podsweeper/" &gt;mon article précédent&lt;/a&gt;, je vous parlais de mon retour d&amp;rsquo;expérience avec PodSweeper, un projet développé avec OpenCode et Claude Opus. Le bilan était nuancé : vitesse brute impressionnante, mais race conditions, gestion d&amp;rsquo;erreur laxiste, et nécessité constante de supervision humaine (entre autres déconvenues).&lt;/p&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui, je vous parle d&amp;rsquo;un deuxième projet, bien plus simple, né d&amp;rsquo;un vrai besoin en production. Et le constat est assez différent.&lt;/p&gt;
&lt;h2 id="lincident-qui-a-tout-déclenché"&gt;L&amp;rsquo;incident qui a tout déclenché
&lt;/h2&gt;&lt;p&gt;Vous connaissez peut-être cette situation : un pod en production, en état de fonctionnement, qui utilise un PVC en &lt;code&gt;ReadWriteOnce&lt;/code&gt;. Vous avez besoin d&amp;rsquo;aller regarder le contenu de ce volume. Bonne pratique de production : le pod en question ne dispose pas de shell (on réduit la surface d&amp;rsquo;attaque). Pas de &lt;code&gt;/bin/sh&lt;/code&gt;, pas de &lt;code&gt;/bin/bash&lt;/code&gt;, rien.&lt;/p&gt;
&lt;p&gt;Pas grave, on a &lt;code&gt;kubectl debug&lt;/code&gt; pour ça, me direz-vous !&lt;/p&gt;
&lt;p&gt;Ok, créons un conteneur éphémère dans le pod et&amp;hellip; ah non. &lt;code&gt;kubectl debug&lt;/code&gt; ne permet pas de monter les volumes du pod dans le conteneur éphémère. Il n&amp;rsquo;expose tout simplement pas l&amp;rsquo;option &lt;code&gt;volumeMounts&lt;/code&gt; pour les conteneurs éphémères.&lt;/p&gt;
&lt;p&gt;On pourrait couper le pod, ce qui permet de monter le PVC RWO dans un autre pod avec un shell, mais on n&amp;rsquo;a pas envie, c&amp;rsquo;est de la prod.&lt;/p&gt;
&lt;p&gt;Mon collègue Maxime (encore lui !) me propose une méthode de contournement. La procédure manuelle, c&amp;rsquo;est :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Créer un conteneur éphémère sur le pod avec &lt;code&gt;kubectl debug&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Construire à la main un patch JSON pour ajouter des &lt;code&gt;volumeMounts&lt;/code&gt; au conteneur éphémère qu&amp;rsquo;on vient de créer&lt;/li&gt;
&lt;li&gt;Dans un autre terminal, se brancher &lt;del&gt;au cul du camion&lt;/del&gt; sur l&amp;rsquo;API de kube en direct avec &lt;code&gt;kubectl proxy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Appliquer le patch via un curl sur l&amp;rsquo;API Kubernetes&lt;/li&gt;
&lt;li&gt;Attendre que le conteneur soit prêt, s&amp;rsquo;attacher au conteneur&lt;/li&gt;
&lt;li&gt;Se dire qu&amp;rsquo;il y avait quand même plus simple comme métier que patcheur de container à coup de curl.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour ceux qui pensent que c&amp;rsquo;est jouable, &amp;ldquo;téma la gueule du patch&amp;rdquo; comme disent (disaient ?) les jeunes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;curl&lt;/span&gt; &lt;span class="err"&gt;http:&lt;/span&gt;&lt;span class="c1"&gt;//localhost:8001/api/v1/namespaces/&amp;lt;namespace&amp;gt;/pods/&amp;lt;pod&amp;gt;/ephemeralcontainers \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;-X&lt;/span&gt; &lt;span class="err"&gt;PATCH&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;-H&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;Content-Type:&lt;/span&gt; &lt;span class="err"&gt;application/strategic-merge-patch+json&amp;#39;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;-d&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;spec&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ephemeralContainers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;debugger&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ubuntu&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/bin/sh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;targetContainerName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;target-container&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;stdin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;volumeMounts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;volume-name&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mountPath&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/debug/&amp;lt;volume-name&amp;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="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si vous pensez que c&amp;rsquo;est error prone, vous avez raison. Et si vous pensez que c&amp;rsquo;est pénible à faire sous pression pendant un incident de prod, vous avez encore plus raison.&lt;/p&gt;
&lt;h2 id="side-note"&gt;Side note
&lt;/h2&gt;&lt;p&gt;Les plus aguerri·es d&amp;rsquo;entre vous dans les versions récentes de Kubernetes ont peut être entendu parler du &lt;a class="link" href="https://kep.k8s.io/2590" target="_blank" rel="noopener"
&gt;KEP-2590&lt;/a&gt;, qui introduit un (relativement) nouveau flag &lt;code&gt;--subresource&lt;/code&gt; de &lt;code&gt;kubectl&lt;/code&gt;. En effet, les containers éphémères créés par la commande &lt;code&gt;kubectl debug&lt;/code&gt; ne sont pas des &lt;em&gt;resources&lt;/em&gt; (un pod, un deployment, &amp;hellip;) mais des &lt;em&gt;subresources&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Jusqu&amp;rsquo;à cette version 1.33, il était littéralement impossible de réaliser des opérations sur les subresources avec &lt;code&gt;kubectl&lt;/code&gt;, seulement les resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes v1.33: Octarine&lt;/strong&gt; apporte le support du flag &amp;ndash;subresource, &lt;strong&gt;mais seulement&lt;/strong&gt; pour 3 subresources pour l&amp;rsquo;instant &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;resize&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/blog/2025/04/23/kubernetes-v1-33-release/#subresource-support-in-kubectl" target="_blank" rel="noopener"
&gt;https://kubernetes.io/blog/2025/04/23/kubernetes-v1-33-release/#subresource-support-in-kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/reference/kubectl/conventions/#subresources" target="_blank" rel="noopener"
&gt;https://kubernetes.io/docs/reference/kubectl/conventions/#subresources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pas de solution de ce côté-là non plus&amp;hellip;&lt;/p&gt;
&lt;h1 id="le-prompt-de-réunion"&gt;Le prompt de réunion
&lt;/h1&gt;&lt;p&gt;J&amp;rsquo;avais cette idée en tête depuis l&amp;rsquo;incident. Lundi matin, au début d&amp;rsquo;une réunion (pendant que les gens faisaient encore des blagues), j&amp;rsquo;ai lancé OpenCode avec Claude Opus et j&amp;rsquo;ai tapé un prompt qui décrivait :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le problème : &lt;code&gt;kubectl debug&lt;/code&gt; ne monte pas les volumes PVC s&amp;rsquo;ils sont en RWO&lt;/li&gt;
&lt;li&gt;La procédure manuelle que je faisais à la main (les étapes douloureuses décrites un peu plus haut)&lt;/li&gt;
&lt;li&gt;Ce que je voulais : un outil qui automatise tout ça&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenCode + Opus m&amp;rsquo;ont posé 2 questions (et j&amp;rsquo;ai ajouté une consigne) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Quel langage ?&amp;rdquo;&lt;/em&gt; → &lt;strong&gt;Go&lt;/strong&gt; (je persiste)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;CLI seule ou TUI ?&amp;rdquo;&lt;/em&gt; → &lt;strong&gt;Les deux&lt;/strong&gt; (mode non-interactif pour le scripting, TUI pour l&amp;rsquo;usage quotidien)&lt;/li&gt;
&lt;li&gt;Et je lui ai demandé de toujours vérifier à chaque fois qu&amp;rsquo;il estime avoir fini de lancer le linter &lt;code&gt;golangci-lint&lt;/code&gt; (trauma de mon précédent test avec opencode)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il est parti de lui-même sur l&amp;rsquo;idée d&amp;rsquo;une TUI avec &lt;a class="link" href="https://github.com/charmbracelet/bubbletea" target="_blank" rel="noopener"
&gt;Bubble Tea&lt;/a&gt;. Puis j&amp;rsquo;ai arrêté de regarder &amp;amp; j&amp;rsquo;ai suivi ma réunion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;~30 minutes plus tard, fin de la réunion&lt;/strong&gt;, j&amp;rsquo;ai regardé le résultat. Le POC était fonctionnel.&lt;/p&gt;
&lt;h2 id="ce-quopus-a-produit-en-30-minutes"&gt;Ce qu&amp;rsquo;Opus a produit en 30 minutes
&lt;/h2&gt;&lt;p&gt;Sans aucune intervention de ma part, le LLM a scaffoldé un projet Go complet :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Structure propre&lt;/strong&gt; : &lt;code&gt;cmd/&lt;/code&gt; pour le CLI Cobra, &lt;code&gt;pkg/k8s/&lt;/code&gt; pour toute l&amp;rsquo;interaction Kubernetes, &lt;code&gt;pkg/tui/&lt;/code&gt; pour la TUI Bubble Tea&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le coeur du sujet&lt;/strong&gt; : un appel direct à l&amp;rsquo;API Kubernetes pour patcher le subresource &lt;code&gt;ephemeralcontainers&lt;/code&gt; du pod avec un strategic merge patch incluant les &lt;code&gt;volumeMounts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mode non-interactif&lt;/strong&gt; : &lt;code&gt;-n namespace -p pod -v volume:/mount/path&lt;/code&gt;, prêt pour le scripting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TUI complète&lt;/strong&gt; : navigation vim-style (&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;), filtrage fuzzy (&lt;code&gt;/&lt;/code&gt;), multi-sélection de volumes, spinner de chargement&amp;hellip;&lt;/li&gt;
&lt;li&gt;Makefile avec tout un tas de targets (vet, lint, test, build, install)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce que je lui ai demandé d&amp;rsquo;ajouter :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discovery intelligente&lt;/strong&gt; : au lieu de scanner tous les pods de tous les namespaces (lent sur un de mes gros cluster), l&amp;rsquo;outil liste d&amp;rsquo;abord les PVCs cluster-wide (un seul appel API) pour identifier les namespaces pertinents, PUIS les pods dans le namespace sélectionné. On réduit drastiquement le nombre d&amp;rsquo;appels à l&amp;rsquo;API server de kube et la TUI ne montre que les namespaces et pods qui ont des volumes PVC&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Héritage du &lt;code&gt;SecurityContext&lt;/code&gt;&lt;/strong&gt; : le conteneur éphémère copie le &lt;code&gt;securityContext&lt;/code&gt; du conteneur cible, ce qui lui permet de passer les PodSecurity policies (&lt;code&gt;restricted&lt;/code&gt;, &lt;code&gt;baseline&lt;/code&gt;&amp;hellip;). C&amp;rsquo;est un cas que je n&amp;rsquo;avais pas envisagé dans mon prompt initial et qui a planté immédiatement sur un cluster bien configuré.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans ces petites améliorations le tool était déjà utilisable (sauf pour le cas où vous avez configuré des SecurityContext), mais c&amp;rsquo;est nettement plus confortable à l&amp;rsquo;usage (parce que oui, je l&amp;rsquo;utilise).&lt;/p&gt;
&lt;p&gt;Le code de la mécanique centrale (le patch de l&amp;rsquo;API) fait quelques centaines de lignes de Go propre. Il récupère le pod, construit le patch JSON avec le conteneur éphémère et ses &lt;code&gt;volumeMounts&lt;/code&gt;, l&amp;rsquo;applique via &lt;code&gt;Patch()&lt;/code&gt; sur le subresource, attend que le conteneur soit Running, puis lance &lt;code&gt;kubectl attach&lt;/code&gt;. Rien de sorcier, c&amp;rsquo;est juste ce qu&amp;rsquo;il faut, bien fait du premier coup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ephemeralContainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EphemeralContainer&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="nx"&gt;EphemeralContainerCommon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EphemeralContainerCommon&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="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;containerName&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="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&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="nx"&gt;Command&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/bin/sh&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="nx"&gt;Stdin&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="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="nx"&gt;TTY&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="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="nx"&gt;VolumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounts&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="nx"&gt;SecurityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;targetSecurityContext&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="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="nx"&gt;TargetContainerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;targetContainer&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="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="o"&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="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Clientset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CoreV1&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Pods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Patch&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="nx"&gt;ctx&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="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PodName&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="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StrategicMergePatchType&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="nx"&gt;patchBytes&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="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PatchOptions&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="s"&gt;&amp;#34;ephemeralcontainers&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="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="les-itérations-suivantes-30-minutes-ish"&gt;Les itérations suivantes (30 minutes-ish)
&lt;/h2&gt;&lt;p&gt;Une fois le POC validé, j&amp;rsquo;ai enchaîné quelques prompts ciblés. Je lui ai demandé ce qu&amp;rsquo;on pouvait améliorer. Il a proposé (et implémenté) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;quelques corrections mineures de code qu&amp;rsquo;il avait mal codé (mais non bloquant)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Warnings&lt;/strong&gt; : si le SecurityContext hérité a &lt;code&gt;readOnlyRootFilesystem&lt;/code&gt;, &lt;code&gt;runAsNonRoot&lt;/code&gt;, ou autre mesure de sécurité, l&amp;rsquo;outil prévient l&amp;rsquo;utilisateur avant l&amp;rsquo;attach&lt;/li&gt;
&lt;li&gt;Ajouté une CI complète à base de &lt;code&gt;goreleaser&lt;/code&gt; et de github actions&lt;/li&gt;
&lt;li&gt;Ajouté de la documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="la-cerise-sur-le-macdo--publication-sur-krew"&gt;La cerise sur le Macdo : publication sur Krew
&lt;/h2&gt;&lt;p&gt;Une fois que j&amp;rsquo;avais une version propre, j&amp;rsquo;ai demandé à Opus comment faciliter l&amp;rsquo;installation du plugin. Il m&amp;rsquo;a proposé &lt;code&gt;brew&lt;/code&gt;, ou alors &lt;a class="link" href="https://krew.sigs.k8s.io/" target="_blank" rel="noopener"
&gt;Krew&lt;/a&gt;, le gestionnaire de plugins kubectl.&lt;/p&gt;
&lt;p&gt;Je lui ai demandé si le processus d&amp;rsquo;acceptation du plugin était complexe. Il a dit que non, je lui ai dit &amp;ldquo;chiche !&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Il a &lt;strong&gt;réalisé l&amp;rsquo;intégralité du processus&lt;/strong&gt; :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Création d&amp;rsquo;un fork de &lt;a class="link" href="https://github.com/kubernetes-sigs/krew-index" target="_blank" rel="noopener"
&gt;krew-index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rédaction du manifeste Krew (le fichier &lt;code&gt;.yaml&lt;/code&gt; qui décrit le plugin)&lt;/li&gt;
&lt;li&gt;Prise en compte de toutes les bonnes pratiques demandées par les maintainers de krew-index (format des URLs de téléchargement, descriptions courtes, checksums SHA256, etc.)&lt;/li&gt;
&lt;li&gt;Préparation de la PR, directement sur krew-index&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;J&amp;rsquo;ai juste regardé. La PR a été mergée 12 heures plus tard par les mainteneurs.&lt;/p&gt;
&lt;p&gt;Résultat : &lt;code&gt;kubectl krew install debug-pvc&lt;/code&gt; fonctionne.&lt;/p&gt;
&lt;h2 id="mini-échec---déléguer-aussi-la-démo"&gt;Mini échec - déléguer aussi la démo
&lt;/h2&gt;&lt;p&gt;Fort de ce succès avec la PR pour krew-index, je me suis demandé si on ne pourrait pas faire une démo visuelle (un GIF à ajouter dans le README.md du projet qui montre comment ça fonctionne) avec le LLM.&lt;/p&gt;
&lt;p&gt;Je lui ai demandé s&amp;rsquo;il pouvait le faire avec &lt;code&gt;asciinema&lt;/code&gt; et le LLM m&amp;rsquo;a répondu que &amp;ldquo;oui&amp;rdquo; (oui, je discute avec le LLM comme avec mes collègues). Banco, on a essayé.&lt;/p&gt;
&lt;p&gt;Le résultat était &lt;em&gt;presque&lt;/em&gt; bon, j&amp;rsquo;aurais pu m&amp;rsquo;en contenter si j&amp;rsquo;étais pas quelqu&amp;rsquo;un de pénible : c&amp;rsquo;était un poil lent. J&amp;rsquo;ai l&amp;rsquo;impression la réactivité du LLM dans ses actions était inégale, ce qui rendait le rendu un peu désagréable au visionnage. J&amp;rsquo;aurais pu insister jusqu&amp;rsquo;à avoir quelque chose de correct, mais j&amp;rsquo;ai finalement enregistré moi-même une vidéo, convertie en GIF avec &lt;code&gt;ffmpeg&lt;/code&gt;. C&amp;rsquo;était plus fluide et plus rapide que d&amp;rsquo;itérer.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/03/kubectl-debug-pvc-demo.gif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="le-diff-avec-podsweeper"&gt;Le diff avec PodSweeper
&lt;/h2&gt;&lt;p&gt;La différence de ressenti entre ce projet et PodSweeper est frappante. Là où PodSweeper était un combat constant (race conditions, amnésie du LLM, fonctionnalités hors-spécifications), &lt;code&gt;kubectl-debug-pvc&lt;/code&gt; s&amp;rsquo;est déroulé sans accroc notable.&lt;/p&gt;
&lt;p&gt;Pourquoi ? Je pense que ça tient à la nature du projet :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Une seule chose à faire&lt;/strong&gt;, clairement définie : patcher un subresource Kubernetes avec des volumeMounts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pas de logique concurrente&lt;/strong&gt; : on fait une requête API, on attend, on s&amp;rsquo;attache&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un domaine bien documenté&lt;/strong&gt; : l&amp;rsquo;API Kubernetes, Cobra, Bubble Tea. Le LLM connaît tout ça par coeur&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pas d&amp;rsquo;état partagé complexe&lt;/strong&gt; : pas de goroutines qui se marchent dessus, pas de graceful shutdown à gérer, de microservices multiples&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scope limité&lt;/strong&gt; : le projet entier tient dans une quinzaine de fichiers, CI et documentation incluses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est probablement le terrain de jeu idéal pour un LLM. Un problème bien cadré, un domaine technique bien balisé, une solution linéaire.&lt;/p&gt;
&lt;h2 id="quest-ce-que-jen-pense-"&gt;Qu&amp;rsquo;est-ce que j&amp;rsquo;en pense ?
&lt;/h2&gt;&lt;p&gt;Objectivement, ce genre de projet &amp;ldquo;simple&amp;rdquo; (une chose à faire, simple à comprendre) marche &lt;strong&gt;vraiment super bien&lt;/strong&gt; avec OpenCode + Opus. Je pense que c&amp;rsquo;est ce genre à cause (grâce) de ce genre de petits projets que la hype est tellement forte autour du développement agentique. On a une idée en tête qu&amp;rsquo;on avait la flemme de dev, on teste, ça marche. &amp;ldquo;WOW&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Mais je ne veux pas non plus minimiser le travail réalisé. Le fait qu&amp;rsquo;un outil fonctionnel, propre, publié sur Krew et utilisable par n&amp;rsquo;importe qui ait pu émerger d&amp;rsquo;un prompt lancé en début de réunion, c&amp;rsquo;est quand même assez dingue.&lt;/p&gt;
&lt;p&gt;Un peu flippant aussi, de se dire qu&amp;rsquo;un nombre hallucinant de micro tools vont apparaître dans les prochains mois, avec un niveau de qualité probablement inégal.&lt;/p&gt;
&lt;h2 id="le-projet"&gt;Le projet
&lt;/h2&gt;&lt;p&gt;Le code est disponible ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zwindler/kubectl-debug-pvc" target="_blank" rel="noopener"
&gt;https://github.com/zwindler/kubectl-debug-pvc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installation :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Via Krew (recommandé)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl krew install debug-pvc
&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;# Utilisation interactive (TUI)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl debug-pvc
&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;# Utilisation non-interactive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl debug-pvc -n my-namespace -p my-pod-0 -v data:/debug/data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si vous aussi vous avez déjà galéré à inspecter un PVC sur un pod sans shell, essayez. Et si vous trouvez des bugs, vous pourrez blâmer le LLM 😄.&lt;/p&gt;</description></item><item><title>kubectl tips and tricks n°4</title><link>https://blog.zwindler.fr/2021/09/06/kubectl-tips-and-tricks-n4/</link><pubDate>Mon, 06 Sep 2021 06:55:00 +0000</pubDate><guid>https://blog.zwindler.fr/2021/09/06/kubectl-tips-and-tricks-n4/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/10/kubectl2.webp" alt="Featured image of post kubectl tips and tricks n°4" /&gt;&lt;h2 id="encore-des-tips-pour-kubectl-"&gt;Encore des tips pour kubectl !!
&lt;/h2&gt;&lt;p&gt;J’en suis donc bien au numéro 4 pour ces &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=kubectl" &gt;tips and tricks sur kubectl (sans compter d’autres articles plus généralistes)&lt;/a&gt; et il y a encore beaucoup à dire !!&lt;/p&gt;
&lt;h2 id="jarrive-plus-à-me-loguer-sur-mon-cluster-kubernetes-x1f62d"&gt;J’arrive plus à me loguer sur mon cluster Kubernetes 😭
&lt;/h2&gt;&lt;p&gt;Dans plusieurs contextes, j’ai eu à aider des collègues qui n’arrivaient plus à utiliser &lt;strong&gt;kubectl&lt;/strong&gt; (après expiration d’un jeton, changements de confs dans le cluster, etc).&lt;/p&gt;
&lt;p&gt;Un moyen rapide pour « régler » une partie des problèmes dans ce genre de cas peut être simplement de supprimer le contenu des caches de kubectl :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rm -r ~/.kube/cache
rm -r ~/.kube/http-cache
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="non-mais-ya-quoi-dans-mon-cluster-"&gt;Non mais ya quoi dans mon cluster ?
&lt;/h2&gt;&lt;p&gt;Vous le savez, Kubernetes c’est plein d’API misent bout à bout (portée par l’API server et persistée par etcd) et d’autres composants qui se connectent dessus pour faire des choses utiles. Ça implique qu’il y ait un certain nombre d’objets logiques à connaître pour interagir avec l’API server et déployer vos applications. Cependant, difficile pour le néophyte de les connaître tous.&lt;/p&gt;
&lt;p&gt;Bien sûr, on peut toujours parcourir &lt;a class="link" href="https://kubernetes.io/docs/reference/using-api/api-concepts/" target="_blank" rel="noopener"
&gt;l’API à coup de cURL&lt;/a&gt; mais vous avouerez qu’il y a plus user-friendly comme méthode ;). Et même pour l’admin chevronné, l’ajout de &lt;a class="link" href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/" target="_blank" rel="noopener"
&gt;CRDs (Custom Resource Definition)&lt;/a&gt;, difficile de savoir, sur des clusters un peu touffus, quel objet est présent et à quoi il sert.&lt;/p&gt;
&lt;p&gt;Pour ça, vous disposez de deux aides avec &lt;strong&gt;kubectl&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;kubectl api-resources&lt;/em&gt;&lt;/strong&gt; va vous permettre de lister la totalité des objets logiques de l’API disponibles (CRDs comprises) sur votre cluster, ainsi qu’une autre information très utile, s’ils sont « namespacés » ou non.&lt;/p&gt;
&lt;p&gt;Dernier intérêt de cette commande, elle permet également de connaitre les abréviations (shortnames) autorisés, pratique pour sous économiser quelques caractères ;-)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl api-resources
NAME SHORTNAMES APIGROUP NAMESPACED KIND
bindings true Binding
componentstatuses cs false ComponentStatus
configmaps cm true ConfigMap
endpoints ep true Endpoints
events ev true Event
limitranges limits true LimitRange
namespaces ns false Namespace
nodes no false Node
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Un autre outil intéressant pour savoir à quoi sert tel ou tel objet/API est &lt;em&gt;&lt;strong&gt;kubectl explain&lt;/strong&gt;&lt;/em&gt;, qui va vous afficher la documentation en ligne :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl explain RoleBinding
KIND: RoleBinding
VERSION: rbac.authorization.k8s.io/v1
DESCRIPTION:
RoleBinding references a role, but does not contain it. It can reference a
Role in the same namespace or a ClusterRole in the global namespace. It
adds who information via Subjects and namespace information by which
namespace it exists in. RoleBindings in a given namespace only have effect
in that namespace.
FIELDS:
apiVersion &amp;lt;string&amp;gt;
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Malheureusement, j’ai remarqué que plusieurs éditeurs mettant à disposition des CRDs ne mettent pas de doc accessible avec « explain ». C’est vraiment super dommage :/&lt;/p&gt;
&lt;h2 id="afficher-des-labels"&gt;Afficher des labels
&lt;/h2&gt;&lt;p&gt;Tous les objets Kubernetes que vous créés peuvent être agrémentés de labels et d’annotations. Au delà de l’aspect informatif (ce &lt;strong&gt;Pod&lt;/strong&gt; appartient à tel équipe, ce &lt;strong&gt;Node&lt;/strong&gt; à telle capacité en RAM), c’est aussi très pratique pour filtrer les informations renvoyées par les commandes &lt;strong&gt;kubectl get&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Dans le tout premier &lt;a class="link" href="https://blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/" &gt;kubectl tips and tricks&lt;/a&gt;, j’avais parlé de l’option &lt;strong&gt;&amp;ndash;selector&lt;/strong&gt;, qui permet de filtrer l’action d’une commande kubectl (get, delete, &amp;hellip;) à un couple label+valeur&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/08/image.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/" &gt;blog.zwindler.fr/2019/10/30/kubectl-tips-tricks-1/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par défaut, les informations renvoyées par le kubectl get sont assez concises et parfois on manque un peu d’information.&lt;/p&gt;
&lt;p&gt;Dans ce genre de cas, la première chose à tester est simplement d’ajouter un « -o wide » qui permet d’ajouter quelques colonnes (qui dépendent du type d’objet requêté) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
scw-k8s-zealous-chaum-default-4b71eda80e934790 Ready &amp;lt;none&amp;gt; 24m v1.21.3 10.70.116.23 51.158.79.36 Ubuntu 20.04.1 LTS c200a86960 5.4.0-80-generic containerd://1.5.4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cependant, dans le cas ça n’est toujours pas suffisant et qu’on ne veut pas filtrer mais bien obtenir rapidement la valeur d’un label bien particulier sur un ensemble d’objets Kubernetes, il existe une option « &amp;ndash;label-columns » qui permet d’ajouter des colonnes supplémentaires en fonction d’un ou plusieurs labels donnés.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get nodes --label-columns=kubernetes.io/os
NAME STATUS ROLES AGE VERSION OS
scw-k8s-zealous-chaum-default-4b71eda80e934790 Ready &amp;lt;none&amp;gt; 19m v1.21.3 linux
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Les labels sont donc d’autant plus utiles qu’ils sont faciles à afficher !&lt;/p&gt;
&lt;p&gt;Note : les plus chevronnés d’entre vous savent certainement peut aller encore plus loin dans les colonnes ou les informations qu’on peut afficher avec kubectl (notamment via le « -o »). Mais je garde ça pour un prochain épisode&amp;hellip; Donc en attendant, have fun ;-)&lt;/p&gt;</description></item><item><title>Test de SKS, le Kubernetes managé chez Exoscale !</title><link>https://blog.zwindler.fr/2021/05/03/test-de-sks-le-kubernetes-manage-chez-exoscale/</link><pubDate>Mon, 03 May 2021 06:20:00 +0000</pubDate><guid>https://blog.zwindler.fr/2021/05/03/test-de-sks-le-kubernetes-manage-chez-exoscale/</guid><description>&lt;img src="https://blog.zwindler.fr/2021/05/exoscale_sks_features.webp" alt="Featured image of post Test de SKS, le Kubernetes managé chez Exoscale !" /&gt;&lt;h2 id="kewa--exoscale-a-un-kubernetes-managé-"&gt;Kewa ? Exoscale a un Kubernetes Managé ?
&lt;/h2&gt;&lt;p&gt;Lorsque &lt;a class="link" href="https://bsky.app/profile/mcorbin.bsky.social" target="_blank" rel="noopener"
&gt;Mathieu Corbin&lt;/a&gt; m’a parlé du Kubernetes managé qu’ils étaient en train de monter chez Exoscale, je lui ai tout de suite proposé de me le montrer, pour que je lui donne mon point de vue.&lt;/p&gt;
&lt;p&gt;Sans être un expert absolu des offres Kubernetes en SaaS, j’en ai quand même testé quelques uns, notamment &lt;a class="link" href="https://blog.zwindler.fr/2018/12/18/jai-teste-pour-vous-aks-la-plateforme-kubernetes-managee-dazure/" &gt;AKS d’Azure&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2019/07/09/jai-teste-pour-vous-loffre-kubernetes-as-a-service-dovh/" &gt;OVH &lt;/a&gt;et quelques autres (GCP, Kapsule, … pour lesquels je n’ai pas fait d’article).&lt;/p&gt;
&lt;p&gt;Et puis, bon, Kubernetes, c’est un peu mon métier maintenant ;-).&lt;/p&gt;
&lt;p&gt;Au moment où j’ai rédigé le premier brouillon de cet article, on était encore en bêta fermée. Il n’y avait pas d’interface graphique dans le portail (uniquement la CLI), certains paramètres étaient pas totalement dans les règles de l’art (problèmes connus chez eux), …&lt;/p&gt;
&lt;p&gt;Et puis le temps a passé et je n’ai pas sorti l’article (comme souvent par manque de temps) avant que la solution passe GA.&lt;/p&gt;
&lt;p&gt;Heureusement, le passage en GA c’est aussi une bonne occasion pour moi de refaire le test et de comparer le boulot qui a été accompli (en peu de temps) pour gommer les quelques défauts de jeunesse que la solution pouvait encore avoir.&lt;/p&gt;
&lt;h2 id="mais-dabord-exoscale-"&gt;Mais d’abord, Exoscale ?
&lt;/h2&gt;&lt;p&gt;J’vais pas mentir, même si j’avais déjà croisé le nom d’Exoscale plusieurs fois, je n’avais jamais pris le temps de tester leur offre. Pour ceux qui l’ignorent donc, &lt;a class="link" href="https://www.exoscale.com/" target="_blank" rel="noopener"
&gt;Exoscale est un cloud provider&lt;/a&gt; avec plusieurs points de présence en Europe.&lt;/p&gt;
&lt;p&gt;Niveau taille de l’acteur, on est pas du tout comparables à un OVH (ou même Scaleway), mais ils sont suffisamment nombreux pour se permettre d’avoir une team dédiée à la release d’un Kubernetes managé. Tout en gardant suffisamment de bande passante pour quand même avoir une API ouverte et documentée, comme les grands.&lt;/p&gt;
&lt;h2 id="et-donc-sks-"&gt;Et donc, SKS ?
&lt;/h2&gt;&lt;p&gt;Cette micro introduction d’Exoscale passée, revenons donc à nos nuages.&lt;/p&gt;
&lt;p&gt;L’offre Kubernetes managée d’Exoscale s’appelle SKS (pour Scalable Kubernetes Service).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exoscale_sks_features.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On nous promet donc sur le site d’Exoscale un service scalable (horizontalement), qui démarre normalement en 90 secondes.&lt;/p&gt;
&lt;p&gt;La gestion du cycle de vie du control plane est entièrement gérée, on peut le déployer via CLI, API, depuis le portail et &lt;a class="link" href="https://registry.terraform.io/providers/exoscale/exoscale/latest/docs/resources/sks_cluster" target="_blank" rel="noopener"
&gt;il existe aussi un provider terraform&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enfin, Exoscale a développé un composant permettant de faciliter l’intégration de son load balancer managé dans SKS (pour l’instant).&lt;/p&gt;
&lt;p&gt;Et comme je vous l’avais promis, &lt;a class="link" href="https://openapi-v2.exoscale.com/#endpoint-sks" target="_blank" rel="noopener"
&gt;l’API de SKS est documentée ici&lt;/a&gt; si jamais vous aimez faire des cURL.&lt;/p&gt;
&lt;h2 id="et-ça-coute-cher-"&gt;Et ça coute cher ?
&lt;/h2&gt;&lt;p&gt;Dans les offres managées de Kubernetes, il y a plusieurs clans. Ceux qui proposent un SLA sur le control plane et ceux qui n’en proposent pas. Et il y a ceux qui font payer le control plane et ceux qui ne le font pas payer.&lt;/p&gt;
&lt;p&gt;Souvent, ceux qui ne font pas payer le control plane sont également les mêmes qui ne proposent pas de SLA sur le control plane.&lt;/p&gt;
&lt;p&gt;Et pour avoir discuté avec un commercial de chez Azure en fait il y a une certaine logique à ne pas proposer de SLA sur un control plane gratuit. Un SLA étant un contrat, difficile de s’engager sur un service gratuit (et encore plus, difficile de vous rembourser ce que vous ne payer pas en cas de problème).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/sub-buzz-8705-1503572294-5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Chez Exoscale, c’est fromage ou dessert, vous avez le choix. SKS est offert en 2 offres distinctes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une payante, avec SLA (99,95%)&lt;/li&gt;
&lt;li&gt;l’autre gratuite, sans SLA&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Classiquement, les workers sont quant à eux facturés au même prix que n’importe quelle machine IaaS chez Exoscale.&lt;/p&gt;
&lt;p&gt;Petite subtilité qui m’a fait sourire, là où les clouds providers se battaient sur qui bill à l’unité de temps la plus petite il y a 10 ans (d’abord l’heure, puis progressivement à la minute), chez Exoscale, vous êtes billé à la seconde. Difficile de faire plus précis.&lt;/p&gt;
&lt;p&gt;Pour tout ce qui concerne le pricing, je vous laisse &lt;a class="link" href="https://www.exoscale.com/pricing/" target="_blank" rel="noopener"
&gt;aller voir ici&lt;/a&gt;. N’ayant aucun intérêt financier à vous pousser vers Exoscale (ou OVH, ou Azure), je ne vais pas insister plus que ça ;-).&lt;/p&gt;
&lt;h2 id="bon-on-teste-"&gt;Bon, on teste ?
&lt;/h2&gt;&lt;p&gt;Maintenant qu’on a fait le tour de la question, et si on essayait un peu de voir ce que ça donne ?&lt;/p&gt;
&lt;p&gt;La première chose à faire est de se créer un compte sur &lt;a class="link" href="http://www.exoscale.com" target="_blank" rel="noopener"
&gt;www.exoscale.com&lt;/a&gt; et aussi télécharger la dernière version de la CLI.&lt;/p&gt;
&lt;p&gt;J’insiste bien sur la « dernière version » car pendant la bêta j’avais téléchargé la mauvaise version et l’API et les paramètres avaient changé de manière significative en peu de temps.&lt;/p&gt;
&lt;p&gt;A l’heure où j’écris l’article, la dernière version est la 1.28 (1.23 quand j’ai testé la bêta en début d’année) mais les releases sont dispos ici : &lt;a class="link" href="https://github.com/exoscale/cli/releases" target="_blank" rel="noopener"
&gt;github.com/exoscale/cli/releases&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;wget https://github.com/exoscale/cli/releases/download/v1.28.0/exoscale-cli_1.28.0_linux_amd64.tar.gz
tar xzf exoscale-cli_1.28.0_linux_amd64.tar.gz
➜ ./exo
Manage your Exoscale infrastructure easily
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A noter, il existe aussi pour les distributions linux des packages (.deb, .rpm, etc).&lt;/p&gt;
&lt;h2 id="configuration-du-compte"&gt;Configuration du compte
&lt;/h2&gt;&lt;p&gt;Maintenant qu’on a notre CLI sur notre poste, on va aller dans le portail créer une clé d’API qui va nous servir à nous authentifier.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://community.exoscale.com/documentation/tools/exoscale-command-line-interface/" target="_blank" rel="noopener"
&gt;La documentation officielle est disponible ici.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exo_api_key_create.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois que vous aurez cliqué sur « Create », l’ID de la clé ainsi que la clé apparaitra. Attention, comme toujours avec ce genre de mécanisme, la clé ne sera visible que cette fois ci. Sauvegardez là donc bien précieusement (sinon au pire il faudra en générer une nouvelle).
&lt;img src="https://blog.zwindler.fr/2021/05/exo_key_3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On peut ensuite configurer notre CLI&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo config
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exo_config.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Et on teste que la connexion marche bien avec la commande permettant de lister les versions de Kubernetes disponibles sur SKS&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo sks versions
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exo_cli_sks_versions.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Bon, je sais pas vous mais moi a me donne le sourire de voir que fin avril, Exoscale a déjà la 1.21 dispo :-)&lt;/p&gt;
&lt;h2 id="création-du-security-group"&gt;Création du security group
&lt;/h2&gt;&lt;p&gt;Exoscale dispose, comme tous les cloud providers, d’un système de firewalling qui permet d’appliquer des règles de sécurité à nos futures machines.&lt;/p&gt;
&lt;p&gt;Même si la création d’un groupe de sécurité n’est pas obligatoire pour instancier notre cluster SKS, c’est mieux si on le fait dès le début.&lt;/p&gt;
&lt;p&gt;Tout peut se faire via la CLI ou via le portail, comme pour le reste chez Exoscale&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo firewall create sks-zwindler-sg
./exo firewall add sks-zwindler-sg -d &amp;#34;NodePort services&amp;#34; -p tcp -P 30000-32767
./exo firewall add sks-zwindler-sg -d &amp;#34;SKS Logs&amp;#34; -p tcp -P 10250
./exo firewall add sks-zwindler-sg -d &amp;#34;Calico traffic&amp;#34; -p udp -P 4789 -s sks-zwindler-sg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si vous préférez le faie sur le portail, ça ressemblera à ça :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/sks_sg_calico.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J’ai juste repris les valeurs par défaut dans la doc &lt;a class="link" href="https://community.exoscale.com/documentation/sks/quick-start/" target="_blank" rel="noopener"
&gt;de Quick Start&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="créer-un-cluster"&gt;Créer un cluster
&lt;/h2&gt;&lt;p&gt;La commande &lt;code&gt;exo sks create&lt;/code&gt; permet de créer notre cluster. On peut rajouter des flags pour modifier certains paramètres :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;exo sks create -h
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exo_sks_usage.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Au delà des options pour choisir la taille du cluster et le type de node, les options intéressantes sont les 3 « &amp;ndash;no- » qui vous permettent de désactiver l’installation automatique de :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la CNI (Calico par défaut). Calico par défaut est un bon choix mais si vous préférez Cillium ou kube-router (ou flannel… nan j’déconne !).&lt;/li&gt;
&lt;li&gt;la CCM (Cloud Controller Manager), composant développé en interne par Exoscale et qui permettra à Kube d’interagir avec les autres services d’Exoscale (notamment le loadbalancer)&lt;/li&gt;
&lt;li&gt;metrics-server mais là je vois pas pourquoi vous ne voudriez pas metrics-server …&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo sks create sks-zwindler --description &amp;#34;Test SKS cluster&amp;#34; --nodepool-name &amp;#34;sks-zwindler-pool&amp;#34; --nodepool-size 3 --nodepool-security-group &amp;#34;sks-zwindler-sg&amp;#34; --zone de-fra-1 --service-level pro
&amp;amp;#x2714; Creating SKS cluster &amp;#34;sks-zwindler&amp;#34;... 1m57s
&amp;amp;#x2714; Adding Nodepool &amp;#34;sks-zwindler-pool&amp;#34;... 3s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Je ne sais pas si je n’ai pas eu de chance car il me semblait que ça allait un peu plus vite que les deux minutes que j’affiche dans cet extrait de shell. Mais 2 minutes c’est déjà plus que respectable quand on sait que chez Azure, AKS met plus de 20 minutes à poper (quand ça ne plante pas), sans compter les VMs (qui elles aussi mettent parfois 10 minutes avant d’être disponibles)&amp;hellip;&lt;/p&gt;
&lt;h2 id="kubeconfig"&gt;kubeconfig
&lt;/h2&gt;&lt;p&gt;Disponible… ok … Mais comment on s’y connecte en fait ?&lt;/p&gt;
&lt;p&gt;Pas de panique, la CLI permet de générer le kubeconfig qui va vous permettre de vous connecter à l’API server ! (ouf)&lt;/p&gt;
&lt;p&gt;Un point intéressant de l’implémentation d’Exoscale est que vous pouvez tuner un peu le kubeconfig qu’il va vous générer (là où la plupart du temps, c’est un kubeconfig admin, point). Vous pouvez notamment choisir le groupe RBAC ainsi qu’une durée de vie.&lt;/p&gt;
&lt;p&gt;Dans un environnement de production avec beaucoup d’utilisateurs, on préfèrera surement ajouter une authentification tierce (coucou OIDC), cependant, c’est quand même « nice to have ».&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo sks kubeconfig sks-zwindler kube-admin --group system:masters --zone de-fra-1 &amp;gt; sks-zwindler-30d.kubeconfig
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;L’authentification se fait donc par certificat avec une durée que vous pouvez configurer en secondes (par défaut 30 jours). Bon, comme je vois que ce que je crois, j’ai fait le test avec 60 secondes et a priori ça marche ;-).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo sks kubeconfig sks-zwindler kube-admin --group system:masters --zone de-fra-1 --ttl 60 &amp;gt; test-60s.kubeconfig
kubectl --kubeconfig test-60s.kubeconfig get nodes
NAME STATUS ROLES AGE VERSION
pool-afa3f-apzqy Ready &amp;lt;none&amp;gt; 6h v1.21.0
pool-afa3f-iehif Ready &amp;lt;none&amp;gt; 6h v1.21.0
pool-afa3f-ysvki Ready &amp;lt;none&amp;gt; 6h v1.21.0
#plus tard
kubectl --kubeconfig test-60s.kubeconfig get nodes
error: You must be logged in to the server (Unauthorized)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="déployer-une-application-disponible-sur-internet"&gt;Déployer une application disponible sur Internet
&lt;/h2&gt;&lt;p&gt;Comme la CCM nous permet de faire communiquer notre Kubernetes et Exoscale, on va en profiter pour créer un service Kubernetes de type Loadbalancer.&lt;/p&gt;
&lt;p&gt;Ce service Loadbalancer va commander à Exoscale un NLB chez eux, ce qui va nous permettre de router le trafic Internet directement dans notre cluster (il vaudrait mieux passer par un IngressController brancher sur ce service Loadbalancer dans la vraie vie).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --kubeconfig sks-zwindler-30d.kubeconfig create service loadbalancer toto --tcp=80:80
kubectl --kubeconfig sks-zwindler-30d.kubeconfig get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt; 443/TCP 8h
toto LoadBalancer 10.111.39.139 &amp;lt;pending&amp;gt; 80:30001/TCP 22s
kubectl --kubeconfig sks-zwindler-30d.kubeconfig get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt; 443/TCP 8h
toto LoadBalancer 10.111.39.139 89.145.161.242 80:30001/TCP 23s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis on déploie un nginx de test avec un label similaire à celui créé par défaut par le service (app=toto)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --kubeconfig sks-zwindler-30d.kubeconfig run --image nginx --labels app=toto toto
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Très rapidement, vous devriez avoir accès à votre pod via l’IP du NLB Exoscale&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/05/exoscale_nginx.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="nettoyage"&gt;Nettoyage
&lt;/h2&gt;&lt;p&gt;Si vous avez mis votre carte de crédit, n’oubliez de supprimer votre cluster de test avant de partir ;-)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./exo sks delete sks-zwindler -n
[+] Are you sure you want to delete Nodepool &amp;#34;sks-zwindler-pool&amp;#34;? [yN]: y
&amp;amp;#x2714; Deleting Nodepool &amp;#34;sks-zwindler-pool&amp;#34;... 6s
[+] Are you sure you want to delete SKS cluster &amp;#34;sks-zwindler&amp;#34;? [yN]: y
&amp;amp;#x2714; Deleting SKS cluster &amp;#34;sks-zwindler&amp;#34;... 18s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et faites pareil si vous avez un instancié un Loadbalancer pour connecter vos services dans Kubernetes avec l’extérieur (j’avais oublié… oups).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J’avais été impressionné par l’implémentation d’OVH de Kubernetes managé, mais je dois reconnaitre que je le suis encore plus par celui d’Exoscale.&lt;/p&gt;
&lt;p&gt;Le cluster (control plane) est créé TRES vite, mais surtout les VMs popent également extrêmement vite (quelques secondes versus 10 minutes chez Azure !!!). C’est vraiment chouette !&lt;/p&gt;
&lt;p&gt;De ce que j’ai vu (et pu échanger) des entrailles de SKS, tout me parait très sain et bien conçu (sécurité, best practices). Et pourtant, Exoscale donne quand même pas mal de libertés sur la customisation du cluster (CCM, CNI, configuration kubeconfig).&lt;/p&gt;
&lt;p&gt;Les petits bugs de jeunesses (quelques erreurs de configuration du kubelet, des erreurs cosmétiques dans les commands lines, le CCM à installer soit même, …) que j’avais pu remonter à Mathieu lors de mon test en janvier/février ont toutes été gommés en moins de 2 mois.&lt;/p&gt;
&lt;p&gt;Donc, selon moi, si vous cherchez un kube managé, SKS est une alternative viable.&lt;/p&gt;</description></item><item><title>Mettre à jour le CA de Kubernetes, « the hard way »</title><link>https://blog.zwindler.fr/2021/02/15/mettre-a-jour-le-ca-de-kubernetes-the-hard-way/</link><pubDate>Mon, 15 Feb 2021 07:56:12 +0000</pubDate><guid>https://blog.zwindler.fr/2021/02/15/mettre-a-jour-le-ca-de-kubernetes-the-hard-way/</guid><description>&lt;img src="https://blog.zwindler.fr/2021/02/yougetacert.webp" alt="Featured image of post Mettre à jour le CA de Kubernetes, « the hard way »" /&gt;&lt;h2 id="les-ca-certificate-authority-ça-expire-"&gt;Les CA (Certificate Authority), ça expire !
&lt;/h2&gt;&lt;p&gt;Dans cet article, je vais vous ferai un petit retour d’expérience d’un souci un peu tricky que j’ai rencontré il y a quelques mois.&lt;/p&gt;
&lt;p&gt;Cela concerne des clusters Kubernetes, la Certificate Authority (CA), un Vault (Hashicorp) et le tout dans un contexte opérationnel, en production, avec des millions d’utilisateurs.&lt;/p&gt;
&lt;p&gt;Attachez vos ceintures, ça risque de secouer :)&lt;/p&gt;
&lt;h2 id="the-automated-hard-way"&gt;The « automated » hard way
&lt;/h2&gt;&lt;p&gt;J’ai récemment changé de boite.&lt;/p&gt;
&lt;p&gt;Dans ce nouveau contexte technique, Kubernetes est en production depuis plusieurs années. Aujourd’hui, il existe de très nombreuses options pour déployer un cluster Kubernetes (j’en ai déjà abondamment parlé &lt;a class="link" href="https://blog.zwindler.fr/2020/10/26/kubernetes-avec-rancheros-et-rke-partie-1/" &gt;ici&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2019/07/09/jai-teste-pour-vous-loffre-kubernetes-as-a-service-dovh/" &gt;là&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2019/03/21/deployer-en-5-minutes-un-cluster-kubernetes-sur-arm-avec-k3s-et-ansible/" &gt;là&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2017/12/05/installer-kubernetes-kubespray-ansible/" &gt;là&lt;/a&gt; ou encore &lt;a class="link" href="https://blog.zwindler.fr/2018/12/18/jai-teste-pour-vous-aks-la-plateforme-kubernetes-managee-dazure/" &gt;là&lt;/a&gt;). Chaque a ses avantages et ses inconvénients, qui dépendent de la topologie du cluster ainsi que de vos besoins (vous êtes plutôt cloud, baremetal, edge,&amp;hellip;).&lt;/p&gt;
&lt;p&gt;Plot twist : Vous l’avez peut-être deviné, le titre de l’article est une référence au célèbre &lt;a class="link" href="https://github.com/kelseyhightower/kubernetes-the-hard-way" target="_blank" rel="noopener"
&gt;“Kubernetes the hard way”&lt;/a&gt; de Kelsey Hightower.&lt;/p&gt;
&lt;p&gt;La raison pour laquelle j’ai choisi ce titre est que, en 2017, les clusters Kubernetes de l’entreprise dans laquelle je travaille ont été déployés avec des playbooks Ansible écrits à la main. Et d’un point de vue extérieur, ça ressemble un peu à automatiser « Kubernetes the hard way » avec Ansible 😉.&lt;/p&gt;
&lt;p&gt;Aujourd’hui, vous (je) le feriez pas comme ça. Mais il faut se replacer dans le contexte. Au moment où le cluster a été déployé, les options étaient bien plus limitées qu’aujourd’hui en termes de déploiements. Particulièrement pour les installations de type baremetal. &lt;a class="link" href="https://kubernetes.io/docs/reference/setup-tools/kubeadm/" target="_blank" rel="noopener"
&gt;kubeadm&lt;/a&gt; par exemple, maintenant GA, affichait encore des gros messages d’avertissement « &lt;strong&gt;NOT PRODUCTION READY&lt;/strong&gt;« .&lt;/p&gt;
&lt;h2 id="its-the-final-countdown"&gt;It’s the final countdown
&lt;/h2&gt;&lt;p&gt;Toutes nos applications ne sont pas hébergées dans Kubernetes, mais une portion relativement significative d’entre elles y sont. Suffisamment en tout cas pour que si le cluster Kubernetes était down, nos utilisateurs finaux finiraient pas d’en rendre compte. Et c’est là où notre histoire commence :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;L’autorité de certification de notre Kubernetes va expirer dans quelques mois, et avec elle, toute la chaîne de certifications&lt;/p&gt;
&lt;p&gt;Un zwindler un peu inquiet
Pour illustrer un peu mon propos, imaginez qu’à la place de ces indicateurs verts rassurants, il y avait du orange (rouge ?) partout.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/02/certs_expiration.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Comme toutes les communications à l’intérieur du cluster Kubernetes sont chiffrées et authentifiées avec ce CA, le laisser expirer serait une très mauvaise idée. En gros, on perdrait le contrôle du cluster et de toutes les applications hébergées dedans 😱. Si vous faites des petites recherches sur Internet, vous trouverez peut-être des postmortem de ce genre de souci (je vous en mets un en fin d’article).&lt;/p&gt;
&lt;p&gt;Donc, j’avais pour mission de trouver un moment de renouveler le certificat, sans interruption pour les utilisateurs de notre service, et AVANT la date d’expiration.&lt;/p&gt;
&lt;h2 id="pourquoi-est-ce-que-cest-un-problème-"&gt;Pourquoi est ce que c’est un problème ?
&lt;/h2&gt;&lt;p&gt;Renouveler un certificat, ce n’est pas la mer à boire. Cependant, ici, comme le CA est responsable de toutes les communications des composants internes de Kubernetes, c’est un peu moins trivial.&lt;/p&gt;
&lt;p&gt;Pour rendre les choses encore plus complexe, la documentation officielle est incomplète (et même fausse, j’ai fait une PR et il faudrait probablement en faire d’autre) et les documentations externes sont parcellaires.&lt;/p&gt;
&lt;p&gt;Il y a plein de raisons pour ça. La première est que beaucoup d’organisations préfèrent utiliser Kubernetes via une offre managée chez leur cloud provider préféré, qui leur cache la complexité de la gestion du cluster Kubernetes pour eux. Dans ce cas-là, les clouds providers sont ceux qui s’inquiètent de ce genre de problématiques (mais s’y inquiètent ils ?).&lt;/p&gt;
&lt;p&gt;Parmi les utilisateurs restants (on-prem donc), la plupart utilisent les outils comme Kubespray ou kubeadm, qui contiennent dans une certaine mesures des outils pour faciliter les procédures de renouvellement de certificats (mais qui n’étaient pas disponibles au moment où les clusters ont été déployés).&lt;/p&gt;
&lt;p&gt;Enfin, pour les rares damnés qui restent, régénérer un CA à la main n’est pas quelque chose qu’on fait souvent. Les CAs sont généralement générés pour des durées allant de 3 à 10 ans (même si les bonnes pratiques préfèrent des durées courtes). Si on compare cette durée moyenne par rapport à l’adoption relativement récente de Kubernetes (un projet vieux de seulement 6 ans), on peut supposer que beaucoup de CA n’ont pas encore eu l’occasion d’expirer ;-).&lt;/p&gt;
&lt;h2 id="et-maintenant--que-vais-je-faire-"&gt;Et maintenant ? Que vais-je faire ?
&lt;/h2&gt;&lt;p&gt;On doit donc trouver un moyen de modifier le CA de Kubernetes à la volée sans impact utilisateur. Heureusement, avec un peu de planification, ça devrait être possible pour la grande majorité des workloads.&lt;/p&gt;
&lt;p&gt;La première chose à savoir est que, même si le cluster ne répond plus pendant qu’on renouvelle les certificats, les applications qui sont dans Kubernetes (les &lt;em&gt;Pods&lt;/em&gt;) qui sont déjà déployés sont toujours opérationnels. Cependant, les nouveaux &lt;em&gt;Jobs&lt;/em&gt; ne seront pas démarrés, les &lt;em&gt;Pods&lt;/em&gt; en erreur ou les applications sur des nœuds HS ne seront pas redémarrés sur des nœuds en bonne santé.&lt;/p&gt;
&lt;p&gt;La seconde chose à savoir est que toutes vos applications (dans les &lt;em&gt;Pods&lt;/em&gt;) sont exécutées avec un contexte de sécurité qui dépend d’un &lt;em&gt;ServiceAccount&lt;/em&gt;. Si vous n’en spécifiez pas dans le manifeste de l’application, vous hériterez par défaut de celui du &lt;em&gt;Namespace&lt;/em&gt; (d’ailleurs, pour information, ça peut être un problème de sécurité).&lt;/p&gt;
&lt;p&gt;Là où les choses se compliquent, c’est que le token qui authentifie ce &lt;em&gt;ServiceAccount&lt;/em&gt; est généré par le &lt;em&gt;Kubernetes Controller Manager&lt;/em&gt;. Et pas de bol, quand vous générez le CA, tous les tokens sont bons à jeter à la poubelle&amp;hellip;&lt;/p&gt;
&lt;p&gt;Donc&amp;hellip; Le but du jeu va être de ne redémarrer que ce qui est nécessaire, dans le bon ordre, puis de régénérer tous les tokens. Et tout ça, suffisamment vite pour qu’aucune application ne plante ou qu’un &lt;em&gt;Node&lt;/em&gt; tombe en panne pendant l’opération.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Easy peasy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="mais-"&gt;MAIS !
&lt;/h2&gt;&lt;p&gt;Car bien sûr, il y a un mais car sinon ça serait trop facile :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les applications qui ont besoin de communiquer avec l’API de Kubernetes seront perturbées dans leur fonctionnement tant qu’elles n’auront pas été redémarrées (une fois leur token régénéré). Cela inclus probablement vos applications de supervision (comme Prometheus) qui scrap l’API. Vous serez « aveugles » pendant quelques minutes.&lt;/li&gt;
&lt;li&gt;Les applications qui maintiennent des connexions longues (typiquement des WebSockets par exemple) seront probablement coupées à un moment donné car vous devrez probablement redémarrer vos &lt;em&gt;IngressControllers&lt;/em&gt;, qui ont souvent besoin d’accéder à l’API server (cf point précédent). Si vous avez implémenté un mécanisme de retry dans vos applications, ça devrait aller.&lt;/li&gt;
&lt;li&gt;Toutes les applications devront à un moment donné être redémarrées pour obtenir leur nouveau token. Si vos applications n’ont pas de replicas (1 seul &lt;em&gt;Pod&lt;/em&gt;), il y aura nécessairement une coupure. Pour éviter ça (et c’est valable pour tous les contextes, pas seulement quand on renouvelle des certificats) essayez de toujours avoir des replicas pour toutes vos applications. Dans le cas présent, si ces applications n’ont pas besoins d’accéder à l’API de Kubernetes, vous pouvez reporter le redémarrage à plus tard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maintenant vous savez tout, allons y 😊 !&lt;/p&gt;
&lt;h2 id="okééééé-on-renouvelle-un-ca-dans-kubernetes-en-prod-sans-interruption-visible"&gt;Okééééé. On renouvelle un CA dans Kubernetes, en prod, sans interruption (visible)
&lt;/h2&gt;&lt;p&gt;Avant de faire quoique ce soit qu’on pourrait regretter, le mieux est quand même d’être certain qu’on est capable de revenir à l’état initial au cas où on devrait rollback. Cela signifie des sauvegardes et surtout tester les procédures de restauration !&lt;/p&gt;
&lt;p&gt;Sauvegardez tous les certificats que vous utilisez actuellement (probablement dans &lt;em&gt;/etc/kubernetes&lt;/em&gt;, mais aussi dans &lt;em&gt;/var/lib/kubelet&lt;/em&gt;) ainsi que ceux d’&lt;em&gt;etcd&lt;/em&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;CURDATE=`date +&amp;#34;%y%m%d%H%M&amp;#34;`
tar czf /tmp/pkibackup.${CURDATE}.tgz /var/lib/kubelet/pki/kubelet.* /etc/kubernetes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vous allez probablement vouloir aussi sauvegarder tous vos tokens actuels (dans vos &lt;em&gt;ServiceAccounts&lt;/em&gt;). Je rappelle que ces tokens sont générés par le &lt;em&gt;Kubernetes Controller Manager&lt;/em&gt; et qu’ils sont utilisés par les &lt;em&gt;Pods&lt;/em&gt; pour communiquer avec l’API server de Kubernetes (mais on y reviendra).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for namespace in $(kubectl get ns --no-headers | awk &amp;#39;{print $1}&amp;#39;); do
for token in $(kubectl get secrets --namespace &amp;#34;$namespace&amp;#34; --field-selector type=kubernetes.io/service-account-token -o name); do
kubectl get $token --namespace &amp;#34;$namespace&amp;#34; -o yaml &amp;gt;&amp;gt; /tmp/token_dump.${CURDATE}.yaml
done
done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et enfin, faire une sauvegarde complète de l’état du cluster via un dump de la base &lt;em&gt;etcd&lt;/em&gt; est probablement une bonne idée aussi (if all else fail comme on dit).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ETCDCTL_API=3 etcdctl --cacert=yourca.pem --cert=etcd.pem --key=etcd-key.pem --endpoints 127.0.0.1:2379 snapshot save /tmp/etcd.backup.$(date +&amp;#39;\%Y\%m\%d_\%H\%M\%S&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="hashicorp-vault-"&gt;Hashicorp Vault ?
&lt;/h2&gt;&lt;p&gt;Historiquement, les certificats avaient été générés à l’aide de la commande openssl, en se basant sur les recommandations officielles de la documentation de Kubernetes.&lt;/p&gt;
&lt;p&gt;Ça fonctionne parfaitement bien (la documentation met plutôt en avant l’outil cfssl aujourd’hui, mais c’est le même principe). Cependant, cette façon de faire n’est ni efficace, ni vraiment safe (la clé privée du CA est stockée sur disque) et est également un facteur d’erreur humaine.&lt;/p&gt;
&lt;p&gt;Parallèlement à ça, nous utilisons depuis plusieurs années &lt;a class="link" href="https://www.vaultproject.io/" target="_blank" rel="noopener"
&gt;l’outil Vault de Hashicorp&lt;/a&gt; pour stocker nos secrets. Nous avons donc profité de l’opportunité offerte par ce renouvellement pour utiliser le module « pki » d’Hashicorp Vault.&lt;/p&gt;
&lt;p&gt;Dans cet article, je ne rentrerai pas dans les détails de Vault et de son moteur PKI (j’ai fait &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=hashicorp" &gt;quelques articles sur les produits Hashicorp en revanche&lt;/a&gt;), mais grosso modo l’idée est :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;créer un nouveau « secret engine »&lt;/li&gt;
&lt;li&gt;générer un CA qui sera gardé au chaud dans Vault&lt;/li&gt;
&lt;li&gt;configurer un rôle permettant de paramétrer les futurs certificats pour correspondre aux recommandations de Kubernetes&lt;/li&gt;
&lt;li&gt;générer les certificats et les déposer sur les serveurs&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vault policy write pki-policy pki-policy.hcl
vault secrets enable -path=pki_k8s pki
vault secrets tune -max-lease-ttl=43800h pki_k8s
vault write pki_k8s/root/generate/internal common_name=&amp;#34;kubernetes-ca&amp;#34; ttl=43800h
vault write pki_k8s/roles/kubernetes allowed_domains=&amp;#34;kubernetes, default, svc, example.org&amp;#34; allow_subdomains=true allow_bare_domains=true max_ttl=&amp;#34;43800h&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="certs-for-everyone"&gt;Certs for everyone
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/02/yougetacert.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour déposer de manière sécurisée et automatisée les certificats, on utilise un autre outil d’Hashicorp (décidémment) qui s’appelle &lt;a class="link" href="https://github.com/hashicorp/consul-template" target="_blank" rel="noopener"
&gt;consul-template&lt;/a&gt;. Ce binaire va nous permettre de générer des fichiers à partir de fichiers &lt;em&gt;templates&lt;/em&gt; et des objets qu’on a stockés dans Vault (et Consul).&lt;/p&gt;
&lt;p&gt;Là encore, sans rentrer dans les détails, un template va ressembler à ça (je vous prends l’exemple du certificat pour l’API server) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{{- /* apiserver-cert.tpl */ -}}
{{ with secret &amp;#34;pki_k8s/issue/kubernetes&amp;#34; &amp;#34;common_name=kube-apiserver&amp;#34; &amp;#34;alt_names=kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.kubernetes, example.org&amp;#34; &amp;#34;ip_sans=100.64.0.1, IP.ADDRESS.MASTER.1, IP.ADDRESS.MASTER.2, IP.ADDRESS.MASTER.3&amp;#34; &amp;#34;exclude_cn_from_sans=true&amp;#34; &amp;#34;ttl=17520h&amp;#34; }}
{{ .Data.certificate }}{{ end}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le fichier de configuration de &lt;em&gt;consul-template&lt;/em&gt; ressemblera lui à ça :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;template {
source = &amp;#34;/etc/consul.d/templates/ca.pem.tpl&amp;#34;
destination = &amp;#34;/etc/kubernetes/pki/ca.pem&amp;#34;
}
template {
source = &amp;#34;/etc/consul.d/templates/admin-cert.tpl&amp;#34;
destination = &amp;#34;/etc/kubernetes/pki/admin.pem&amp;#34;
}
template {
blah blah blah
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On lancera la commande suivante, qui remplacera d’un coup tous les anciens certificats par de nouveaux qui respectent les besoins de notre cluster et qui sont validé par notre nouveau CA :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;consul-template -config /etc/consul.d/templates/consul-template-config-master.hcl
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Petit bémol, cette solution ne marche pas pour tous les certificats. A chaque appel de consul-template, un nouveau certificat est produit. Pour tous les certificats individuels, comme les certificats pour chaque node, cela fonctionne très bien. Cependant, ce n’est pas vrai pour certains, par exemple pour la clé privée de l’API server qui nécessite d’être la même pour tous les serveurs. Nous avons donc fait une exception dans notre processus : tous les certificats nécessaires pour les masters ont été générés pour un seul serveur puis copiés sur les autres masters.&lt;/p&gt;
&lt;h2 id="ground-control-to-major-tom"&gt;Ground control to Major Tom
&lt;/h2&gt;&lt;p&gt;Tous les certificats sont maintenant régénérés. Mais pour autant, tous les composants de Kubernetes ne supportent pas le renouvellement de certificats à chaud. On va devoir tout redémarrer (et dans le bon ordre, en plus&amp;hellip;).&lt;/p&gt;
&lt;p&gt;La première urgence est de redémarrer les serveurs &lt;em&gt;etcd&lt;/em&gt;, tous en même temps. Une fois que c’est fait, la course commence car l’API server ne sera plus capable d’interroger etcd pour connaître (et mettre à jour) l’état du cluster. Nous avons perdu tout contrôle sur notre cluster 😱.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl restart etcd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A partir de maintenant, toutes les commandes kubectl vont échouer. Toutes les fonctionnalités de Kubernetes (autoscaling, scheduling des pods, etc) vont arrêter de fonctionner.&lt;/p&gt;
&lt;p&gt;Cela va heureusement facilement se régler, simplement en redémarrant l’&lt;em&gt;api-server&lt;/em&gt; manuellement (soit via le service &lt;em&gt;systemd&lt;/em&gt; soit, si c’est un &lt;em&gt;Pod&lt;/em&gt;, envoyer un SIGKILL).&lt;/p&gt;
&lt;p&gt;On peut ensuite s’attaquer au reste des composants du control plane qui nécessitent &lt;em&gt;etcd&lt;/em&gt; ou l’API server.&lt;/p&gt;
&lt;p&gt;Je vous conseille de commencer par le CNI (comme &lt;em&gt;flannel&lt;/em&gt; par exemple), puis de supprimer la paire de certificats de vos &lt;em&gt;kubelet&lt;/em&gt; (ils seront régénérés automatiquement) avant de les redémarrer, sur tous les &lt;em&gt;Nodes&lt;/em&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rm /var/lib/kubelet/pki/kubelet.{crt,key}
systemctl restart kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et pour finir, on peut redémarrer tous les composants du control plane restant avec des commandes &lt;em&gt;kubectl&lt;/em&gt; depuis un master (ça devrait remarcher).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/bin/kubectl --namespace kube-system delete pods --selector component=kube-apiserver
/usr/bin/kubectl --namespace kube-system delete pods --selector component=kube-controller-manager
/usr/bin/kubectl --namespace kube-system delete pods --selector component=kube-scheduler
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notre control plane est de nouveau opérationnel 🎉.&lt;/p&gt;
&lt;h2 id="the-tokens-sleep-tonight"&gt;The tokens sleep tonight
&lt;/h2&gt;&lt;p&gt;Maintenant que notre control plane fonctionne avec nos nouveaux certificats, si vous regardez ce qui se passe dans les logs de l’API server, vous remarquerez qu’il y aura beaucoup de messages pas super explicites à propos de tokens pourris.&lt;/p&gt;
&lt;p&gt;Si vous vous souvenez bien, on avait dit qu’il allait falloir rafraîchir tous nos tokens, qui sont tous invalidés depuis qu’on a redémarré l’API server. Cette partie est heureusement gérée par le &lt;em&gt;Kubernetes Controller Manager&lt;/em&gt; mais il va falloir lui donner un petit coup de pouce : on va supprimer les tokens contenus dans les &lt;em&gt;Secrets&lt;/em&gt; de type &lt;em&gt;service-account-token&lt;/em&gt; relatifs à chaque &lt;em&gt;ServiceAccount&lt;/em&gt; grâce à cette boucle :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for ns in `kubectl get ns | grep Active | awk &amp;#39;{ print $1 }&amp;#39;`; do
for token in `/usr/bin/kubectl get secrets --namespace $ns --field-selector type=kubernetes.io/service-account-token -o name`; do
/usr/bin/kubectl get $token --namespace $ns -o yaml | /bin/sed &amp;#39;/token: /d&amp;#39; | /usr/bin/kubectl replace -f - ;
done
done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et nous pouvons donc commencer à redémarrer nos applications.&lt;/p&gt;
&lt;h2 id="now-the-applications"&gt;Now, the applications
&lt;/h2&gt;&lt;p&gt;Je vous conseille de redémarrer &lt;em&gt;coredns&lt;/em&gt; en premier (ou &lt;em&gt;kubedns&lt;/em&gt;, selon votre installation). Sans ça, la résolution de nom à l’intérieur de votre cluster Kubernetes va commencer à échouer (et ça, ça craint).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/bin/kubectl --namespace kube-system rollout restart deployment.apps/coredns
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mais vous devriez aussi redémarrer tous les applications qui vous semblent importantes pour le fonctionnement du cluster, comme notamment (mais pas uniquement) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;kube-proxy&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;les &lt;em&gt;Ingress controllers&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;prometheus&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;toute autre application de monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enfin, redémarrer toutes les applications qui nécessitent un accès à l’API server pour fonctionner correctement. Pour les autres, vous avez le temps de le faire plus tard.&lt;/p&gt;
&lt;h2 id="wrapping-this-up"&gt;Wrapping this up
&lt;/h2&gt;&lt;p&gt;Le CA de votre cluster Kubernetes a été renouvelé. Vous devriez pouvoir de nouveau dormir tranquilles pour quelques mois 😊.&lt;/p&gt;
&lt;p&gt;Renouveler vos CA n’est pas une tâche triviale dans Kubernetes, mais j’espère vous avoir montré que ce n’est pas impossible et que dans la plupart des cas et en le planifiant bien, il est possible de le faire sans interruption.&lt;/p&gt;
&lt;p&gt;Toutes les tâches décrites dans cet article ont été automatisées dans des playbooks Ansible, que nous avons joué sur des environnements moins critiques autant de fois que nécessaire jusqu’à ce que nous ayons été sûr de leur exécution.&lt;/p&gt;
&lt;p&gt;En décembre dernier, l’intervention a été planifiée sur les clusters de production. Toute la procédure a été jouée en quelques minutes, aucun impact utilisateur n’a été détecté et les services qui nécessitaient un accès à l’API n’ont pas été perturbées plus que prévu.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2021/02/iloveit.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/04-certificate-authority.md" target="_blank" rel="noopener"
&gt;Github — Kelsey Hightower’s “Kubernetes the hard way”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Postmortems d’expiration de certificats CA&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://vadosware.io/post/2019-12-k8s-cert-expiration-outage/" target="_blank" rel="noopener"
&gt;Vadosware — 2019–12 K8s certificate expiration outage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Construire et détruire des clusters Kubernetes à la volée&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=1xHmCrd8Qn8" target="_blank" rel="noopener"
&gt;Youtube — Continuously Deliver your Kubernetes Infrastructure — Mikkel Larsen, Zalando SE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Documentation officielle de Kubernetes a propos des certificats&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/setup/best-practices/certificates/#single-root-ca" target="_blank" rel="noopener"
&gt;Kubernetes — Single Root CA best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/tasks/tls/manual-rotation-of-ca-certificates/" target="_blank" rel="noopener"
&gt;Kubernetes — Manual rotation of CA certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/#manual-certificate-renewal" target="_blank" rel="noopener"
&gt;Kubeadm — Manual certificate renewal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hashicorp Vault&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hashicorp — Build Your Own Certificate Authority (CA) (lien mort)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.vaultproject.io/api-docs/secret/pki#allow_subdomains" target="_blank" rel="noopener"
&gt;Hashicorp Vault — PKI Secrets Engine (API)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.digitalocean.com/blog/vault-and-kubernetes/" target="_blank" rel="noopener"
&gt;Digital Ocean — Using Vault as a Certificate Authority for Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=k8FXTeFCp90&amp;amp;feature=youtu.be" target="_blank" rel="noopener"
&gt;Youtube — Streamline Certificate Management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>kubectl tips and tricks 3</title><link>https://blog.zwindler.fr/2020/06/22/kubectl-tips-and-tricks-3/</link><pubDate>Mon, 22 Jun 2020 06:35:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/06/22/kubectl-tips-and-tricks-3/</guid><description>&lt;img src="https://blog.zwindler.fr/2019/10/kubectl2.webp" alt="Featured image of post kubectl tips and tricks 3" /&gt;&lt;h2 id="déjà-le-numéro-3-pour-les-kubectl-tips-and-tricks-"&gt;Déjà le numéro 3 pour les &lt;em&gt;kubectl tips and tricks&lt;/em&gt; !
&lt;/h2&gt;&lt;p&gt;Vous le savez peut être car j’en parle abondamment, mais j’utilise Kubernetes quotidiennement, en particulier kubectl` ! Certes il y a des UI sympas pour améliorer l’expérience utilisateur de la ligne de commande avec Kubernetes, mais j’aime bien savoir exactement ce que je fais et souvent je préfère reste au plus proche de l’outil (c’est personnel). Je collecte donc de petites astuces qu’on ne trouve pas toujours quand on débute dans kube, que je vous partage ici.&lt;/p&gt;
&lt;p&gt;Note: numéro 3 signifie bien entendu un n°1 et un n°2, que vous pourrez retrouver ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&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;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2020/01/20/kubectl-tips-and-tricks-n2/" &gt;kubectl tips and tricks n°2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et aussi dans la même veine :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&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;kubectx et kubens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2020/03/23/supprimer-un-namespace-bloque-a-terminating/" &gt;Supprimer un namespace bloqué à Terminating&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quest-ce-qui-bouffe-mes-ressources-"&gt;Qu’est ce qui bouffe mes ressources !
&lt;/h2&gt;&lt;p&gt;Un inconditionnel lorsqu’on héberge des applications (et encore plus quand elles cohabitent) est savoir laquelle bouffe toutes les ressources et bloque les autres (ça marche aussi dans la vraie vie, en coloc) !&lt;/p&gt;
&lt;p&gt;L’idéal est bien entendu de pouvoir compter sur une supervision complète (allez voir cet article sur &lt;a class="link" href="https://blog.zwindler.fr/2020/04/13/decouvrir-prometheus-et-grafana-par-lexemple/" &gt;Prometheus et Grafana&lt;/a&gt; si vous voulez en savoir plus), mais des fois, pour aller vite (ou si votre cluster est vraiment par terre et que Prom répond plus, avoir des outils intégrés permet de gagner du temps dans l’analyse du problème.&lt;/p&gt;
&lt;p&gt;Heureusement pour nous, kubectl` intègre une commande &amp;ldquo;top&amp;rdquo;, qui, comme sa commande homonyme sous Linux, va nous permettre d’afficher les consommations de CPU et de RAM des objets dans notre cluster.&lt;/p&gt;
&lt;p&gt;Il existe deux modes pour ce &amp;ldquo;top&amp;rdquo;. Le premier permet de lister la consommation des nodes :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
zwindlerzzzz01-11111111-vmss000000 118m 3% 1274Mi 10%
zwindlerzzzz01-11111111-vmss000001 119m 3% 1777Mi 14%
zwindlerzzzz01-11111111-vmss000002 320m 8% 2611Mi 20%
zwindlermntr01-11111111-vmss000000 113m 2% 1653Mi 6%
zwindlerzone01-11111111-vmss000000 200m 5% 1559Mi 12%
zwindlerzone01-11111111-vmss000001 215m 5% 1510Mi 11%
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On a à la fois les infos en terme de CPU (en cores ou millicores et en pourcentage total) ainsi que la consommation RAM (en Mio et en %age total de ce qu’il y a sur la machine).&lt;/p&gt;
&lt;p&gt;Le second permet de lister les pods.&lt;/p&gt;
&lt;p&gt;Un point d’attention cependant : contrairement aux nodes, qui sont des objets visibles dans tous les namespaces Kubernetes, les pods sont liés à un namespace particulier. Il sera donc nécessaire de spécifier le namespace qui vous intéresse ou alors d’ajouter un &lt;code&gt;--all-namespaces&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl top pod --all-namespaces
NAMESPACE NAME CPU(cores) MEMORY(bytes)
default appli1-aaaaa 1m 36Mi
default appli2-aaaaa 1m 36Mi
default appli3-aaaaa 1m 36Mi
ingress traefik-v2-aaaaa-aaaaa 3m 17Mi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;[Edit] Suite à une remarque de Thibault Le Reste, je me suis rappelé de 2 options dont je n’avais pas parlé initialement dans l’article &lt;code&gt;--sort-by=cpu&lt;/code&gt; et &lt;code&gt;--sort-by=memory&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A y repenser, je me suis souvenu pourquoi je n’en ai par parlé&amp;hellip; c’est parce que cette option ne marchait pas quand j’ai écris l’article ;-)&lt;/p&gt;
&lt;p&gt;En fait il y a un bug, fixé dans la version 1.18 de &lt;code&gt;kubectl&lt;/code&gt; (&lt;a class="link" href="https://github.com/kubernetes/kubernetes/issues/81270" target="_blank" rel="noopener"
&gt;issue Github 81270&lt;/a&gt;), donc si vous êtes à jour, vous pouvez utiliser ces deux options !&lt;/p&gt;
&lt;h2 id="logs"&gt;Logs
&lt;/h2&gt;&lt;p&gt;Un autre must dans l’hébergement d’applications, c’est comprendre pourquoi une application plante. Et il peut y avoir tellement de causes (pas seulement liées à Kubernetes) qu’il est important de garder une vision complète de toutes les sources de debugging à votre portée.&lt;/p&gt;
&lt;p&gt;D’abord, l’application est peut être juste mal configurée. On pourrait simplement se connecter dessus mais il y a fort à craindre que le pod se kill tout seul le temps que vous tentiez de vous connecter.&lt;/p&gt;
&lt;p&gt;Heureusement pour nous, la plupart des images sont pensées de telle sorte que les logs importants sont envoyés sur la sortie standard, sortie que nous pouvons récupérer avec la commande &amp;ldquo;logs&amp;rdquo;, que vous connaissez sûrement.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --namespace=monitoring logs thanos-query-7bc9986f59-c7njv
level=info ts=2020-05-06T15:23:35.600327763Z caller=main.go:168 msg=&amp;#34;Tracing will be disabled&amp;#34;
level=info ts=2020-05-06T15:23:35.656544832Z caller=main.go:288 component=query msg=&amp;#34;disabled TLS, key and cert must be set to enable&amp;#34;
level=info ts=2020-05-06T15:23:35.656572032Z caller=query.go:460 msg=&amp;#34;starting query node&amp;#34;
level=info ts=2020-05-06T15:23:35.956571601Z caller=query.go:430 msg=&amp;#34;Listening for query and metrics&amp;#34; address=0.0.0.0:10902
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cependant, ce que vous ignorez peut être c’est qu’il existe plusieurs flags très utiles qui permettent de reproduire les fonctions indispensable de tout admin linux qui lit des logs.&lt;/p&gt;
&lt;p&gt;Par exemple, on peut faire l’équivalent d’un &amp;ldquo;tail -500&amp;rdquo; pour lister les 500 dernières lignes de log uniquement (très pratique si vous en avez des tartines) en ajoutant simplement le flag &lt;code&gt;--tail=500&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On peut aussi faire l’équivalent du &amp;ldquo;tail -f&amp;rdquo; (suivre les lignes qui vont apparaître a posteriori en temps réel) avec le flag &lt;code&gt;-f&lt;/code&gt; (tout simplement)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --namespace=monitoring logs thanos-query-7bc9986f59-c7njv --tail=1 -f
level=info ts=2020-05-06T15:26:18.048901191Z caller=storeset.go:266 component=storeset msg=&amp;#34;adding new store to query storeset&amp;#34; address=prom-thanos-sidecar-zwindlerk8s.monitoring.svc.cluster.local:10901
[et là le prompt attend jusqu&amp;#39;à ce qu&amp;#39;une nouvelle ligne apparaisse ou que vous Ctrl-C]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans le cas d’un plantage en boucle, il arrive qu’on ait pas eu le temps de voir la trace du conteneur précédent avant que Kube en lance un nouveau. Vous pouvez accéder aux logs du conteneur précédent avec le &lt;code&gt;-p&lt;/code&gt; !&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl logs pod-qui-crashe-en-boucle -p
Content root path: /app
Now listening on: http://[::]:80
Application started. Press Ctrl+C to shut down.
Application is shutting down...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans le cas où vous avez besoin de voir rapidement la date a laquelle a été écrite une ligne de log et que vous n’avez pas l’information dans le log lui même, sachez qu’il existe un &lt;code&gt;--timestamps&lt;/code&gt;.
Lorsque vous ajoutez ce flag, il va &lt;em&gt;preppend&lt;/em&gt; un timestamp devant chaque ligne.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --context=k8s11-euw-dev --namespace=cutting-room logs scanner-parameters-7bfd6b656d-cl2gl --timestamps
2020-06-23T08:00:08.578320749Z {&amp;#34;logType&amp;#34;:&amp;#34;Debug&amp;#34;,&amp;#34;timestamp&amp;#34;:&amp;#34;2020-06-23T08:00:08.575Z&amp;#34;,&amp;#34;level&amp;#34;:&amp;#34;Info&amp;#34;,[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le mieux étant bien sûr d’externaliser toutes les lignes de log dans un système centralisé comme fluentd ou splunk pour faciliter les recherches, bien entendu&amp;hellip;&lt;/p&gt;
&lt;p&gt;Et enfin, un dernier flag très très cool : vous avez la possibilité de n’afficher que les messages les plus récents, mais sur un temps donné plutôt que sur un nombre de ligne de log. &lt;code&gt;--since=5m&lt;/code&gt; vous affichera les logs des 5 dernières minutes uniquement !&lt;/p&gt;
&lt;h2 id="events"&gt;Events
&lt;/h2&gt;&lt;p&gt;Je pense qu’on a fait le tour pour ce qui était des logs applicatif avec kubectl. Cependant, il existe une autre catégorie de logs dans Kubernetes : les Events. Les Events, c’est un peu les logs interne de l’API server. Ca va vous donner tout un tas d’informations sur ce qui se passe sur vos objets. Un Pod est créé, un Pod meurt car il répond pas à la Liveness Probe, une Image est pull&amp;hellip; la tuyauterie technique de kube en somme.&lt;/p&gt;
&lt;p&gt;Les Events sont des objets Kubernetes à part entière, comme les Pods, les Nodes, etc. Ils sont liés à un namespace et on les liste avec un &lt;code&gt;kubectl get events&lt;/code&gt;, on peut obtenir les informations complètes d’un Event avec un &lt;code&gt;kubectl describe events&lt;/code&gt;, etc.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl --namespace=kube-system get events
LAST SEEN TYPE REASON OBJECT MESSAGE
40s Warning Unhealthy pod/omsagent-rs-758cbf9987-fb5zf Liveness probe failed:
30m Normal Killing pod/omsagent-rs-758cbf9987-fb5zf Container omsagent failed liveness probe, will be restarted
10m Warning BackOff pod/omsagent-rs-758cbf9987-fb5zf Back-off restarting failed container
9s Warning Unhealthy pod/omsagent-v7w8w Liveness probe failed:
25m Normal Killing pod/omsagent-v7w8w Container omsagent failed liveness probe, will be restarted
5m45s Warning BackOff pod/omsagent-v7w8w Back-off restarting failed container
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mais&amp;hellip; vous voyez pas un truc chelou ?&lt;/p&gt;
&lt;p&gt;C’est trié N’IMPORTE COMMENT !!!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2020/05/rage.gif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Le mieux dans cette histoire, c’est que c’est &amp;ldquo;by design&amp;rdquo;. Donc vous allez devoir garder cette astuce là sous le coude car je l’utilise à peu près tout le temps.&lt;/p&gt;
&lt;p&gt;Pour modifier le comportement par défaut de kubectl get events pour que les événements soient triés par date de dernière occurrence (c’est souvent ce qu’on veut), retenez donc que vous allez devoir ajouter à chaque fois &lt;code&gt;--sort-by='{.lastTimestamp}'&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get events --sort-by=&amp;#39;{.lastTimestamp}&amp;#39;
LAST SEEN TYPE REASON OBJECT MESSAGE
36m Normal Killing pod/omsagent-rs-758cbf9987-fb5zf Container omsagent failed liveness probe, will be restarted
16m Warning BackOff pod/omsagent-rs-758cbf9987-fb5zf Back-off restarting failed container
11m Warning BackOff pod/omsagent-v7w8w Back-off restarting failed container
6m27s Warning Unhealthy pod/omsagent-rs-758cbf9987-fb5zf Liveness probe failed:
5m56s Warning Unhealthy pod/omsagent-v7w8w Liveness probe failed:
85s Normal Pulled pod/omsagent-rs-758cbf9987-fb5zf Container image &amp;#34;mcr.microsoft.com/azuremonitor/containerinsights/ciprod:ciprod03022020&amp;#34; already present on machine
56s Normal Killing pod/omsagent-v7w8w Container omsagent failed liveness probe, will be restarted
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Voilà, j’arrête ici car j’en ai plein d’autres mais ça commence à faire beaucoup. La prochaine fois je parlerai très certainement de la façon dont vous allez pouvoir customiser les colonnes que vous voulez voir afficher, ainsi que des filtres.&lt;/p&gt;
&lt;p&gt;Mais en attendant, amusez vous bien avec ça ;-)&lt;/p&gt;</description></item><item><title>Supprimer un namespace bloqué à Terminating</title><link>https://blog.zwindler.fr/2020/03/23/supprimer-un-namespace-bloque-a-terminating/</link><pubDate>Mon, 23 Mar 2020 07:15:00 +0000</pubDate><guid>https://blog.zwindler.fr/2020/03/23/supprimer-un-namespace-bloque-a-terminating/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/terminated.webp" alt="Featured image of post Supprimer un namespace bloqué à Terminating" /&gt;&lt;h2 id="forcer-la-suppression-dun-namespace-bloqué-à-terminating-dans-kubernetes"&gt;Forcer la suppression d’un namespace bloqué à &amp;ldquo;Terminating&amp;rdquo; dans Kubernetes
&lt;/h2&gt;&lt;p&gt;Il y a quelques mois, j’ai eu des soucis pour supprimer un namespace lorsque j’ai voulu démonter mon &lt;a class="link" href="https://blog.zwindler.fr/2019/09/10/du-ceph-dans-mon-kubernetes/" &gt;cluster Ceph (monté avec Rook)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On aurait pu croire que ça m’a énervé (bon ok, si un peu quand même) mais ça m’a permis de mettre la main sur plusieurs commandes sympa avec kubectl donc tout n’est pas perdu ;-).&lt;/p&gt;
&lt;p&gt;Voyez ce récit comme une checklist des choses à vérifier si jamais vous avez du mal à supprimer des objets dans Kubernetes !&lt;/p&gt;
&lt;p&gt;Note: dans la même veine, n’hésitez pas à aller voir les articles que j’ai écris sur &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=kubectl" &gt;kubectl, notamment les tips and tricks !&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="la-base"&gt;La base
&lt;/h2&gt;&lt;p&gt;Pensant avoir correctement supprimé les objets Ceph dans mon cluster, j’ai donc terminé le nettoyage par une suppression toute bête du namespace :&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 --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox delete ns rook-ceph
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sauf que, patatra, en essayant de vérifier qu’il était bien supprimé :&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 --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox get ns rook-ceph
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rook-ceph Terminating 88d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le namespace est toujours présent, et reste bloqué à l’état &amp;ldquo;Terminating&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Qu’à cela ne tienne, je tente de le re-supprimer. Ça ne marche pas :&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 --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox delete ns rook-ceph
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error from server &lt;span class="o"&gt;(&lt;/span&gt;Conflict&lt;span class="o"&gt;)&lt;/span&gt;: Operation cannot be fulfilled on namespaces &lt;span class="s2"&gt;&amp;#34;rook-ceph&amp;#34;&lt;/span&gt;: The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="forcer-la-suppression"&gt;Forcer la suppression
&lt;/h2&gt;&lt;p&gt;Une rapide recherche sur le net me conseille d’ajouter les flags &lt;code&gt;--force&lt;/code&gt;, à obligatoirement associer avec le flag &lt;code&gt;--grace-period=0&lt;/code&gt; (si vous ne le mettez pas, il vous dira de le mettre de toute façon&amp;hellip;)&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 --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox delete ns rook-ceph --force --grace-period&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warning: Immediate deletion does not &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; confirmation that the running resource has been terminated. The resource may &lt;span class="k"&gt;continue&lt;/span&gt; to run on the cluster indefinitely.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error from server &lt;span class="o"&gt;(&lt;/span&gt;Conflict&lt;span class="o"&gt;)&lt;/span&gt;: Operation cannot be fulfilled on namespaces &lt;span class="s2"&gt;&amp;#34;rook-ceph&amp;#34;&lt;/span&gt;: The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Flute !&lt;/p&gt;
&lt;h2 id="vérifier-quil-ne-reste-pas-des-objets-kubernetes-dans-le-namespace-ou-associés"&gt;Vérifier qu’il ne reste pas des objets Kubernetes, dans le namespace ou associés
&lt;/h2&gt;&lt;p&gt;Bon, généralement quand j’arrive pas à supprimer un namespace, c’est qu’il reste un PVC qui traine, lui même attaché à un PV. Mais là non plus, rien :&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 --namespace&lt;span class="o"&gt;=&lt;/span&gt;rook-ceph get pvc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;No resources found in rook-ceph namespace.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="vérifier-les-crd-aussi-"&gt;Vérifier les CRD aussi !
&lt;/h2&gt;&lt;p&gt;Un truc à vérifier aussi dans le cas de rook, c’est qu’il ne reste pas de CRD (CustomRessourceDefinition) que vous n’avez pas l’habitude de manipuler, qui seraient encore présentes et qui bloqueraient l’opération :&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;dgermain$ kubectl delete storageclass rook-ceph-block
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error from server &lt;span class="o"&gt;(&lt;/span&gt;NotFound&lt;span class="o"&gt;)&lt;/span&gt;: storageclasses.storage.k8s.io &lt;span class="s2"&gt;&amp;#34;rook-ceph-block&amp;#34;&lt;/span&gt; not found
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dgermain$ kubectl delete storageclass rook-cephfs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error from server &lt;span class="o"&gt;(&lt;/span&gt;NotFound&lt;span class="o"&gt;)&lt;/span&gt;: storageclasses.storage.k8s.io &lt;span class="s2"&gt;&amp;#34;rook-cephfs&amp;#34;&lt;/span&gt; not found
&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;dgermain$ kubectl --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox get crd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;volumes.rook.io 2019-08-19T09:46:08Z
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dgermain$ kubectl --context&lt;span class="o"&gt;=&lt;/span&gt;sandbox delete crd volumes.rook.io
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="utiliser-des-scripts-pour-débloquer-les-objets-en-terminating"&gt;Utiliser des scripts pour débloquer les objets en &amp;ldquo;Terminating&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Là ça commence à devenir pénible. Comme je ne suis évidemment pas le premier à avoir le problème, des gens ont écris des scripts pour faciliter la suppression d’objets bloqués au stade Terminating. Ces scripts prennent en charge la plupart des cas courants. Attention cependant à ce que vous faites avec (soyez sûrs de 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;dgermain:~/sources/knsk$ git clone https://github.com/thyarles/knsk/
&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;dgermain:~/sources/knsk$ kubectl config use-context sandbox
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Switched to context &lt;span class="s2"&gt;&amp;#34;sandbox&amp;#34;&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;dgermain:~/sources/knsk$ chmod +x knsk.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dgermain:~/sources/knsk$ ./knsk.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Deleting rook-ceph... &lt;span class="k"&gt;done&lt;/span&gt;!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="lister-tous-les-objets-du-cluster-qui-sappellent-rook-ceph"&gt;Lister tous les Objets du cluster qui s’appellent rook-ceph
&lt;/h2&gt;&lt;p&gt;Last but not least. La solution j’ai fini par la trouver en utilisant la commande suivante, qui permet de lister TOUS les types objets existants dans votre cluster :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl api-resources --verbs=list --namespaced -o name
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A partir de là, j’ai rajouté un petit xargs pour rechercher, dans tout le cluster, tous les objets s’appelant rook-ceph parmis tous les types d’objets qui existent. Et là surprise :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get -n rook-ceph
No resources found in rook-ceph namespace.
No resources found in rook-ceph namespace.
No resources found in rook-ceph namespace.
[...]
No resources found in rook-ceph namespace.
NAME DATADIRHOSTPATH MONCOUNT AGE STATE HEALTH
rook-ceph /var/lib/rook 1 88d Created HEALTH_OK
No resources found in rook-ceph namespace.
[...]
No resources found in rook-ceph namespace.
Error from server (NotAcceptable): the server was unable to respond with a content type that the client supports (get pods.metrics.k8s.io)
No resources found in rook-ceph namespace.
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OUPS ! Il restait un CRD &amp;ldquo;cephcluster&amp;rdquo; que j’avais oublié de supprimer ! Sauf que cet objet n’apparaissait pas avec une requête d’affichage classique.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl -n rook-ceph get cephcluster
NAME DATADIRHOSTPATH MONCOUNT AGE STATE HEALTH
rook-ceph /var/lib/rook 1 88d Created HEALTH_OK
kubectl -n rook-ceph delete cephcluster rook-ceph
cephcluster.ceph.rook.io &amp;#34;rook-ceph&amp;#34; deleted
&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;Malheureusement, ce n’est pas totalement terminé ! Notre namespace n’a plus d’objets qui bloquent sa suppression. Pour autant, il est encore bloqué dans l’état Terminating.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl -n rook-ceph delete cephcluster rook-ceph
cephcluster.ceph.rook.io &amp;#34;rook-ceph&amp;#34; deleted
^C
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans ce cas de figure, on peut soit relancer le script knsk, soit, à la main, patcher l’objet pour vider la metadata &amp;ldquo;finalizers&amp;rdquo; et débloquer le processus de suppression. Ca revient au même, mais je vous le met pour que vous compreniez ce que vous faites :&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;dgermain:~/sources/knsk$ kubectl -n rook-ceph patch cephclusters.ceph.rook.io rook-ceph -p &lt;span class="s1"&gt;&amp;#39;{&amp;#34;metadata&amp;#34;:{&amp;#34;finalizers&amp;#34;: []}}&amp;#39;&lt;/span&gt; --type&lt;span class="o"&gt;=&lt;/span&gt;merge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cephcluster.ceph.rook.io/rook-ceph patched
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et maintenant, votre namespace est supprimé !&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/@newtondev/how-to-fix-kubernetes-namespace-deleting-stuck-in-terminating-state-5ed75792647e" target="_blank" rel="noopener"
&gt;How to fix Kubernetes namespace deleting stuck in Terminating State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/thyarles/knsk/blob/master/knsk.sh" target="_blank" rel="noopener"
&gt;github.com/thyarles/knsk : Kubernetes namespace killer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://rook.io/docs/rook/latest/Getting-Started/ceph-teardown/" target="_blank" rel="noopener"
&gt;Documentation Ceph : Ceph Teardown&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>