<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Cloud Ops Chronicles</title><link href="https://www.ops-chronicles.cloud/fr/" rel="alternate"/><link href="https://www.ops-chronicles.cloud/feeds/all.atom.xml" rel="self"/><id>https://www.ops-chronicles.cloud/fr/</id><updated>2026-05-14T00:00:00+02:00</updated><entry><title>Le Workflow "AI-First" : Quand votre IDE ne démarre que pour valider l'IA</title><link href="https://www.ops-chronicles.cloud/fr/workstation-cloudrun.html" rel="alternate"/><published>2026-05-14T00:00:00+02:00</published><updated>2026-05-14T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-05-14:/fr/workstation-cloudrun.html</id><summary type="html">&lt;p&gt;Une Workstation VS Code reproductible sur Cloud Run, propulsée par Nix, conçue pour les boucles de collaboration Humain-IA — avec un coût de 0,00 $ au repos.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#lere-du-developpeur-validateur"&gt;L&amp;rsquo;ère du développeur-validateur&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#le-nouveau-cycle-de-vie-de-la-feature-a-la-release"&gt;Le nouveau cycle de vie : de la Feature à la Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#pourquoi-nix-larme-secrete-du-determinisme"&gt;Pourquoi Nix ? L&amp;rsquo;arme secrète du déterminisme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#les-dessous-de-larchitecture-abattre-les-murs-pour-les-workstations-serverless"&gt;Les dessous de l&amp;rsquo;architecture : abattre les murs pour les workstations Serverless&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#mur-n1-le-terminal-qui-refuse-de-souvrir"&gt;Mur n°1 — Le terminal qui refuse de s&amp;rsquo;ouvrir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mur-n2-les-websockets-vs-la-couche-proxy"&gt;Mur n°2 — Les WebSockets vs. la couche proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mur-n3-le-stockage-partage-humain-ia"&gt;Mur n°3 — Le stockage partagé Humain / IA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mur-n4-tourner-en-non-root-avec-nix-securite"&gt;Mur n°4 — Tourner en non-root avec Nix (Sécurité)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#le-nerf-de-la-guerre-pourquoi-le-scale-to-zero-change-tout-pour-le-budget"&gt;Le nerf de la guerre : Pourquoi le Scale-to-Zero change tout pour le budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#architecture-du-projet-et-pipeline-de-build"&gt;Architecture du projet et pipeline de build&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#le-conteneur-de-base"&gt;Le conteneur de base&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#la-forge-lagent-ia-en-action"&gt;La forge : L&amp;rsquo;Agent IA en action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#pour-demarrer"&gt;Pour démarrer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Une Workstation VS Code reproductible sur Cloud Run, propulsée par Nix, conçue pour les boucles de collaboration Humain-IA — avec un coût de 0,00 $ au repos.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;em&gt;Source du projet : &lt;a href="https://gitlab.com/matgou/workstation-nix"&gt;gitlab.com/matgou/workstation-nix&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="lere-du-developpeur-validateur"&gt;L&amp;rsquo;ère du développeur-validateur&lt;a class="headerlink" href="#lere-du-developpeur-validateur" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Il y a encore peu de temps, nos environnements de développement vivaient sur nos machines, lourds et personnalisés à l&amp;rsquo;excès. Puis sont arrivés les IDE Cloud (GitHub Codespaces, Google Cloud Workstations), pensés pour une ère bien précise : celle de développeurs humains codant activement 8 heures par jour en utilisant la puissance de serveurs déportés.&lt;/p&gt;
&lt;p&gt;Mais l&amp;rsquo;émergence de l&amp;rsquo;IA générative bouleverse aussi cette donne. Aujourd&amp;rsquo;hui, face à la génération de code, on a besoin de GPU : locaux (pas chers), distants (chers) et, en plus, deux visions s&amp;rsquo;affrontent et on verra bien laquelle perdurera :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;L&amp;rsquo;école du &amp;ldquo;les IA seront bientôt 100% autonomes&amp;rdquo;&lt;/strong&gt; : Le développeur ne maintient plus que des spécifications en Markdown, l&amp;rsquo;IA fait tout le reste, de bout en bout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&amp;rsquo;école du &amp;ldquo;L&amp;rsquo;IA prépare, l&amp;rsquo;humain valide&amp;rdquo;&lt;/strong&gt; : L&amp;rsquo;IA abat le gros du travail, mais le développeur aura toujours un rôle de garant du code produit. Il sera amené à &amp;ldquo;mettre les mains dedans&amp;rdquo; pour tester, valider et ajuster.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Soyons pragmatiques : la première école relève encore (pour l&amp;rsquo;instant) de la science-fiction pour la majorité des projets complexes ou critiques en entreprise. Et je suis d&amp;rsquo;avis que nous avons pour l&amp;rsquo;instant toujours besoin de mettre les mains dans le cambouis pour auditer et valider le code généré. Cependant, je constate que nos outils n&amp;rsquo;ont pas suivi, l&amp;rsquo;adoption des EDI déportés est encore trop anecdotique. Et payer un environnement permanent alors que l&amp;rsquo;humain n&amp;rsquo;intervient plus qu&amp;rsquo;en pointillé n&amp;rsquo;a aucun sens économique — et même écologique.&lt;/p&gt;
&lt;p&gt;Je vous propose ici une réflexion autour des nouveaux EDI déportés et souhaite montrer qu&amp;rsquo;ils sont accessibles à tous avec, comme exemple, la mise en place d&amp;rsquo;une &lt;strong&gt;Workstation-CloudRun&lt;/strong&gt;. Ces derniers, bien que n&amp;rsquo;ayant pas été construits précisément pour ce monde de symbiose entre le développeur et l&amp;rsquo;agent IA, s&amp;rsquo;y adaptent pourtant parfaitement. Aujourd&amp;rsquo;hui chacun peut disposer d&amp;rsquo;un EDI surpuissant, avec des GPUs, sur mesure, disponible instantanément, qui ne vit que le temps exact où nous avons besoin de réviser le code (et pas pendant que l&amp;rsquo;IA travaille).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="le-nouveau-cycle-de-vie-de-la-feature-a-la-release"&gt;Le nouveau cycle de vie : de la Feature à la Release&lt;a class="headerlink" href="#le-nouveau-cycle-de-vie-de-la-feature-a-la-release" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour bien comprendre la nouvelle dynamique du développement piloté par les agents IA, il faut repenser notre pipeline de livraison. L&amp;rsquo;humain n&amp;rsquo;est plus à l&amp;rsquo;origine du code, il en devient le validateur final.&lt;/p&gt;
&lt;p&gt;Voici une vision d&amp;rsquo;un workflow moderne :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Les spécifications&lt;/strong&gt; d&amp;rsquo;un ticket ou d&amp;rsquo;une modification sont communiquées. (Je ne vais pas détailler ici comment écrire et faire valider de bonnes spécifications pour que l&amp;rsquo;IA puisse les traiter.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&amp;rsquo;IA travaille dans l&amp;rsquo;ombre&lt;/strong&gt; : Un agent IA (Gemini, Claude, ou un pipeline CI autonome) échafaude un projet, écrit une feature ou résout un bug.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le point de convergence&lt;/strong&gt; : L&amp;rsquo;agent dépose tout ce code directement dans un &lt;strong&gt;grove&lt;/strong&gt; — un bucket Google Cloud Storage (GCS) servant d&amp;rsquo;espace de travail partagé. J&amp;rsquo;ai emprunté le terme &amp;ldquo;grove&amp;rdquo; au framework &lt;a href="https://googlecloudplatform.github.io/scion/concepts/#grove"&gt;Google Scion&lt;/a&gt;. &lt;em&gt;(Coût : 0 $, le stockage objet étant quasi gratuit).&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le passage de relais (Hand-off)&lt;/strong&gt; : L&amp;rsquo;IA vous notifie que le code est prêt à être validé ou complété par l&amp;rsquo;humain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&amp;rsquo;Humain prend la main&lt;/strong&gt; : Vous cliquez sur une URL Cloud Run. En quelques secondes, une instance VS Code démarre dans la Workstation Nix. Le code généré par l&amp;rsquo;IA est déjà là, monté instantanément. Dans cette workstation, vous pouvez modifier, lancer, déboguer le code, ou même le vibecoder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale to Zero&lt;/strong&gt; : Une fois la PR validée, vous fermez l&amp;rsquo;onglet. L&amp;rsquo;instance Cloud Run s&amp;rsquo;éteint. Vous ne payez plus.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voici comment se matérialise ce cycle, de la requête initiale jusqu&amp;rsquo;à la release :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;flowchart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LR&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🎯 Feature Request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🤖 Agent IA&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;git clone + code&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;☁️ Grove (GCS)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;💻 Workstation Nix&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GCS FUSE mount&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;spawn&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;👨‍💻 Humain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;review, test, fix&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;git merge&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🚀 Git Release&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fbbc04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Le gros avantage d&amp;rsquo;un workflow comme celui-ci est entre autres le principe du &lt;strong&gt;Scale to Zero&lt;/strong&gt; : c&amp;rsquo;est-à-dire que le &lt;strong&gt;coût total au repos : 0,00 $&lt;/strong&gt; — En effet, le grove (GCS) ne coûte que quelques centimes de stockage. La Workstation ne consomme des ressources &lt;em&gt;que&lt;/em&gt; pendant la validation humaine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Des IA qui manipulent du code, ça semble maintenant classique, mais VS Code dans Cloud Run, c&amp;rsquo;est un concept. Pour le rendre fonctionnel et reproductible, il faut parfois contourner certaines limites. Ce projet est un bon exemple de comment repousser les limites de cette technologie.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Workstation Nix en action sur Cloud Run - Exécution de Nix en direct" src="/images/2026-Workstation-Cloudrun/CaptureEDI.png" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="pourquoi-nix-larme-secrete-du-determinisme"&gt;Pourquoi Nix ? L&amp;rsquo;arme secrète du déterminisme&lt;a class="headerlink" href="#pourquoi-nix-larme-secrete-du-determinisme" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dans ce projet, il faut garantir que l&amp;rsquo;environnement dans lequel s&amp;rsquo;exécute le code est reproductible et qu&amp;rsquo;il disposera de tous les outils nécessaires aux suites de tests et au développement.&lt;/p&gt;
&lt;p&gt;Un des défis est donc que l&amp;rsquo;environnement soit reproductible mais non figé (par exemple, si on compile un conteneur Docker, on embarque toutes les dépendances de manière statique mais aussi les failles de sécurité).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est ici que &lt;strong&gt;Nix&lt;/strong&gt; entre en jeu en remplaçant l&amp;rsquo;approche impérative par une approche purement fonctionnelle. Chaque dépendance est managée as code et embarque son arbre de dépendances complet. &amp;ldquo;Le package.json/package-lock de la workstation&amp;rdquo; en quelque sorte.&lt;/p&gt;
&lt;p&gt;Voici pourquoi Nix est devenu indispensable pour cette architecture :
- &lt;strong&gt;Reproductibilité bit-à-bit&lt;/strong&gt; : Le même fichier &lt;code&gt;flake.lock&lt;/code&gt; (généré par l&amp;rsquo;IA ou le template) produit toujours exactement la même image.
- &lt;strong&gt;Composition atomique&lt;/strong&gt; : Ajouter un outil se résume à une ligne.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ss"&gt;coreTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; pkgs&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  bashInteractive coreutils curl git jq neovim nix ripgrep
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Chaque binaire — de &lt;code&gt;bash&lt;/code&gt; à &lt;code&gt;code-server&lt;/code&gt; — est tiré du Nix Store avec sa version exacte. Nix génère ensuite l&amp;rsquo;image Docker finale pour nous via &lt;code&gt;pkgs.dockerTools.buildLayeredImage&lt;/code&gt;, sans même avoir besoin d&amp;rsquo;un daemon Docker complexe.
- &lt;strong&gt;Pas d&amp;rsquo;artefacts à stocker&lt;/strong&gt; : Seul le fichier de code &lt;code&gt;flake.nix&lt;/code&gt; est à stocker. La beauté de la chose, c&amp;rsquo;est que si l&amp;rsquo;on change de machine, on repart avec la même configuration.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est exactement le choix qui a été fait par &lt;a href="https://devenv.sh/"&gt;devenv.sh&lt;/a&gt;, un projet open source de &lt;a href="https://github.com/cachix/devenv"&gt;Cachix&lt;/a&gt; qui standardise la définition d&amp;rsquo;environnements de développement au-dessus de Nix. Son principe : décrire son environnement dans un simple fichier &lt;code&gt;devenv.nix&lt;/code&gt; avec une syntaxe déclarative — langages, paquets, services, scripts — et laisser Nix matérialiser le tout de façon reproductible. Activating Python, Rust ou PostgreSQL se résume à une ligne (&lt;code&gt;languages.python.enable = true;&lt;/code&gt;), l&amp;rsquo;activation d&amp;rsquo;un environnement se fait en moins de 100 ms grâce au cache, et le tout reste décorable via des profils et des imports.&lt;/p&gt;
&lt;p&gt;Notre Workstation-CloudRun &lt;strong&gt;s&amp;rsquo;intègre naturellement avec le standard devenv&lt;/strong&gt;. Une fois connecté à l&amp;rsquo;IDE, le développeur peut simplement lancer &lt;code&gt;devenv shell&lt;/code&gt; dans le terminal VS Code pour activer l&amp;rsquo;environnement déclaré dans le dépôt du projet hébergé sur le grove. Les outils, les versions, les services — tout est déjà défini &lt;em&gt;as code&lt;/em&gt; par l&amp;rsquo;équipe ou par l&amp;rsquo;IA. Le développeur n&amp;rsquo;a rien à installer manuellement : l&amp;rsquo;environnement de développement est aussi éphémère et reproductible que la workstation elle-même.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="les-dessous-de-larchitecture-abattre-les-murs-pour-les-workstations-serverless"&gt;Les dessous de l&amp;rsquo;architecture : abattre les murs pour les workstations Serverless&lt;a class="headerlink" href="#les-dessous-de-larchitecture-abattre-les-murs-pour-les-workstations-serverless" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sur le papier, faire tourner un IDE web comme VS Code sur Cloud Run semble facile. Nix propose un système de build permettant de créer des conteneurs. Mais dans la pratique (et particulièrement d&amp;rsquo;un point de vue DevSecOps), les plateformes Serverless ne sont &lt;strong&gt;pas du tout&lt;/strong&gt; conçues pour héberger des environnements stateful avec des accès terminaux complexes.&lt;/p&gt;
&lt;p&gt;Pour transformer cette vision en réalité, j&amp;rsquo;ai dû affronter et contourner quelques limites techniques majeures de l&amp;rsquo;écosystème cloud actuel. Je vous propose un petit retour d&amp;rsquo;expérience sur les problèmes rencontrés :&lt;/p&gt;
&lt;h3 id="mur-n1-le-terminal-qui-refuse-de-souvrir"&gt;Mur n°1 — Le terminal qui refuse de s&amp;rsquo;ouvrir&lt;a class="headerlink" href="#mur-n1-le-terminal-qui-refuse-de-souvrir" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nous allons utiliser le terminal de VS Code pour interagir avec l&amp;rsquo;environnement. Mais manipuler un terminal oblige à interagir avec le noyau du système.&lt;/p&gt;
&lt;p&gt;Le noyau gVisor par défaut (Gen1) de Cloud Run ne supporte pas les pseudo-terminaux (&lt;code&gt;/dev/ptmx&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La solution&lt;/strong&gt; : Passer au moteur d&amp;rsquo;exécution Cloud Run de seconde génération (&lt;code&gt;--execution-environment gen2&lt;/code&gt;). Cela exécute le conteneur dans une véritable micro-VM Linux. Nous montons ensuite &lt;code&gt;devpts&lt;/code&gt; manuellement au démarrage pour débloquer les terminaux VS Code.&lt;/p&gt;
&lt;h3 id="mur-n2-les-websockets-vs-la-couche-proxy"&gt;Mur n°2 — Les WebSockets vs. la couche proxy&lt;a class="headerlink" href="#mur-n2-les-websockets-vs-la-couche-proxy" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;La commande &lt;code&gt;gcloud run services proxy&lt;/code&gt;, qui permet d&amp;rsquo;accéder à un service Cloud Run via un tunnel local, &lt;strong&gt;ne supporte pas les connexions WebSocket&lt;/strong&gt;. Or, VS Code (code-server) repose massivement sur les WebSockets pour le terminal et l&amp;rsquo;édition en temps réel. Résultat : des freezes aléatoires du terminal et des déconnexions en pleine session. Ce problème bloque de facto l&amp;rsquo;utilisation d&amp;rsquo;IAP (Identity-Aware Proxy) comme couche d&amp;rsquo;authentification, puisque le proxy en est le point d&amp;rsquo;entrée.&lt;/p&gt;
&lt;p&gt;Une alternative aurait été de placer un &lt;strong&gt;Load Balancer&lt;/strong&gt; avec IAP devant Cloud Run — solution propre mais qui introduit un coût fixe permanent (~18 $/mois pour la règle de forwarding), ce qui va à l&amp;rsquo;encontre de notre philosophie &lt;strong&gt;Scale-to-Zero à coût nul au repos&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La solution pragmatique&lt;/strong&gt; : Exposer Cloud Run directement (avec son URL HTTPS native) et déléguer la sécurité au mécanisme natif &lt;code&gt;--auth password&lt;/code&gt; de &lt;code&gt;code-server&lt;/code&gt;, qui est pleinement compatible WebSocket. Le timeout de l&amp;rsquo;instance Cloud Run est également poussé à son &lt;strong&gt;maximum légal (3600 secondes)&lt;/strong&gt; pour garantir une session ininterrompue, et le nombre maximum d&amp;rsquo;instances est verrouillé à &lt;strong&gt;1&lt;/strong&gt; (&lt;code&gt;--max-instances 1&lt;/code&gt;) pour garantir une session unique et maîtriser les coûts.&lt;/p&gt;
&lt;h3 id="mur-n3-le-stockage-partage-humain-ia"&gt;Mur n°3 — Le stockage partagé Humain / IA&lt;a class="headerlink" href="#mur-n3-le-stockage-partage-humain-ia" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;À chaque scale-to-zero, le conteneur disparaît. Il nous fallait un espace persistant où l&amp;rsquo;IA peut déposer le code et où la workstation peut modifier et le récupérer. Puis le reprendre en cas de stop/start du conteneur. Le but étant de pouvoir travailler sur un projet, de l&amp;rsquo;abandonner et de le reprendre plus tard sans perdre son travail. Idéal pour les périodes d&amp;rsquo;essai.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La solution : GCS FUSE et le mirage des permissions POSIX :&lt;/strong&gt;
Un bucket Cloud Storage monté via GCS FUSE semblait la solution évidente. Cependant, un nouveau mur est apparu : GCS n&amp;rsquo;est pas un système de fichiers POSIX et ne gère pas nativement les changements de permissions (les fameux &lt;code&gt;chmod&lt;/code&gt; et &lt;code&gt;chown&lt;/code&gt;).
Conséquence ? Des outils modernes comme &lt;code&gt;devenv&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt; ou &lt;code&gt;git&lt;/code&gt;, qui tentent de sécuriser leurs fichiers temporaires ou environnements virtuels, plantaient lamentablement avec une erreur fatale : &lt;code&gt;Operation not permitted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour contourner cette limitation stricte sans sacrifier la persistance temps réel, nous avons &amp;ldquo;trompé&amp;rdquo; l&amp;rsquo;outil de montage FUSE en forçant une simulation totale des permissions pour notre utilisateur exclusif. Voici la configuration de montage Cloud Run optimisée pour l&amp;rsquo;IDE (quelques options de performances ont aussi été ajoutées) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;implicit-dirs
stat-cache-max-size-mb=32        # Alloue 32 Mo en RAM pour le cache des métadonnées
metadata-cache-ttl-secs=300      # Garde les métadonnées en cache pendant 5 minutes
enable-streaming-writes=true     # Écritures en streaming direct
client-protocol=http1            # (Performance) Forcer HTTP/1.1 pour éviter le Head-of-Line blocking HTTP/2
max-conns-per-host=100           # (Performance) Massifier les I/O parallèles
cache-dir=cr-volume:cache        # Indique à Cloud Run d&amp;#39;utiliser notre volume in-memory nommé &amp;quot;cache&amp;quot;
file-cache-max-size-mb=2048      # Alloue 2Go de RAM pour ce cache
uid=1000                         # Force l&amp;#39;appartenance à l&amp;#39;utilisateur VS Code
gid=1000
file-mode=0777                   # Simule une permissivité totale
dir-mode=0777
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;L&amp;rsquo;Asynchronisme et la gestion des petits fichiers :&lt;/strong&gt;
Créer un environnement virtuel (ex: &lt;code&gt;python -m venv&lt;/code&gt;) implique la création de milliers de petits fichiers. Avec un montage réseau classique et le protocole HTTP/2 par défaut de GCS FUSE, toutes ces requêtes s&amp;rsquo;empilent de manière synchrone sur une seule connexion TCP. 
Nous forçons donc le protocole HTTP/1.1 (&lt;code&gt;client-protocol=http1&lt;/code&gt;), ouvrons un pool de 100 connexions simultanées pour paralléliser massivement les écritures. De plus, nous conservons un cache local en RAM (&lt;code&gt;cache-dir=cr-volume:cache&lt;/code&gt;) de 2 Go pour accélérer considérablement la lecture du code source existant.&lt;/p&gt;
&lt;p&gt;Cependant, il faut être conscient des limites du Serverless : même avec cette parallélisation et ces caches agressifs, la latence réseau synchrone incompressible de Google Cloud Storage reste un goulot d&amp;rsquo;étranglement pour la création pure d&amp;rsquo;arborescences massives. Voici l&amp;rsquo;évolution des performances mesurées pour la création d&amp;rsquo;un environnement Python (&lt;code&gt;devenv shell&lt;/code&gt;) sur notre architecture Cloud Run :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Configuration GCS FUSE&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Temps de création du &lt;code&gt;venv&lt;/code&gt;&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Bilan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Standard (HTTP/2 par défaut)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 9 min 42 s&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Inutilisable au quotidien&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Optimisé (HTTP/1.1 + max-conns=100 + Caches RAM)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 8 min 44 s&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Mieux, mais la latence synchrone bride le réseau&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Optimisé + Déport &lt;code&gt;.devenv&lt;/code&gt; en RAM (Symlink)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 45 secondes&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Performances quasi locales !&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;L&amp;rsquo;ultime parade (implémentée via un wrapper transparent au sein même de l&amp;rsquo;image Nix) consiste à &lt;strong&gt;déporter le dossier d&amp;rsquo;état local &lt;code&gt;.devenv&lt;/code&gt; dans la RAM (&lt;code&gt;tmpfs&lt;/code&gt;)&lt;/strong&gt; à l&amp;rsquo;aide d&amp;rsquo;un lien symbolique dynamique. Puisque le &lt;code&gt;/nix/store&lt;/code&gt; est recréé à chaque démarrage de l&amp;rsquo;instance, le dossier d&amp;rsquo;état de l&amp;rsquo;environnement est par essence éphémère. En le générant en RAM, la création de milliers de fichiers devient atomique et immédiate. 
Le développeur conserve ainsi un environnement persistant pour son code via GCS FUSE (bénéficiant des options de cache pour la réactivité de lecture), tout en esquivant totalement la latence GCS pour la création de l&amp;rsquo;outillage lourd.&lt;/p&gt;
&lt;p&gt;Grâce aux drapeaux &lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;gid&lt;/code&gt; et &lt;code&gt;mode&lt;/code&gt;, GCS FUSE considère de plus que l&amp;rsquo;utilisateur possède déjà tous les droits. Lorsqu&amp;rsquo;un outil demande un &lt;code&gt;chmod&lt;/code&gt;, GCS FUSE renvoie un code de succès factice au lieu de crasher. Le développeur obtient ainsi un environnement de développement persistant, tout en évitant les crashs liés aux exigences POSIX.&lt;/p&gt;
&lt;h3 id="mur-n4-tourner-en-non-root-avec-nix-securite"&gt;Mur n°4 — Tourner en non-root avec Nix (Sécurité)&lt;a class="headerlink" href="#mur-n4-tourner-en-non-root-avec-nix-securite" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Faire tourner un IDE en &lt;code&gt;root&lt;/code&gt; complet est une hérésie de sécurité. Mais Nix a besoin d&amp;rsquo;écrire dans &lt;code&gt;/nix/store&lt;/code&gt; pour installer des paquets. La solution naïve — donner l&amp;rsquo;ownership du store entier à l&amp;rsquo;utilisateur &lt;code&gt;coder&lt;/code&gt; (mode single-user) — est un anti-pattern de sécurité : l&amp;rsquo;utilisateur pourrait alors modifier n&amp;rsquo;importe quel binaire du système.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La solution&lt;/strong&gt; : Utiliser le &lt;strong&gt;mode multi-user de Nix&lt;/strong&gt; avec &lt;code&gt;nix-daemon&lt;/code&gt;. Le conteneur démarre en root, lance le daemon Nix en arrière-plan (qui reste root et gère exclusivement le store), puis &lt;strong&gt;abandonne irrévocablement ses privilèges&lt;/strong&gt; via &lt;code&gt;gosu&lt;/code&gt; avant de lancer l&amp;rsquo;IDE sous l&amp;rsquo;UID 1000. Ainsi, le &lt;code&gt;/nix/store&lt;/code&gt; reste en &lt;strong&gt;lecture seule&lt;/strong&gt; pour l&amp;rsquo;utilisateur &lt;code&gt;coder&lt;/code&gt; — il peut installer de nouveaux paquets via le daemon (qui vérifie et écrit pour lui), mais ne peut jamais altérer les binaires existants. C&amp;rsquo;est la même architecture que celle utilisée sur les machines NixOS de production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quelques petites optimisations supplémentaires qui concernent Nix :&lt;/strong&gt;
- &lt;strong&gt;Bypass du disque réseau :&lt;/strong&gt; Nous forçons &lt;code&gt;export TMPDIR=/tmp/cache&lt;/code&gt; pour orienter les builds vers notre volume &lt;em&gt;in-memory&lt;/em&gt; ultra-rapide (RAM-disk de 4 Go monté spécialement pour l&amp;rsquo;occasion).
- &lt;strong&gt;Mise au pas de Nix :&lt;/strong&gt; Nous bloquons la parallélisation sauvage en forçant &lt;code&gt;max-jobs = 4&lt;/code&gt; et &lt;code&gt;cores = 2&lt;/code&gt; dans la configuration de Nix pour respecter les vCPU réellement alloués au conteneur.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="le-nerf-de-la-guerre-pourquoi-le-scale-to-zero-change-tout-pour-le-budget"&gt;Le nerf de la guerre : Pourquoi le Scale-to-Zero change tout pour le budget&lt;a class="headerlink" href="#le-nerf-de-la-guerre-pourquoi-le-scale-to-zero-change-tout-pour-le-budget" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Maintenant que nous avons réussi à packager Nix et VS Code dans un conteneur, nous pouvons l&amp;rsquo;utiliser avec une approche Cloud Run. Voici ce que ça change en termes de coûts.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;approche Serverless brille particulièrement dans ce workflow hybride, car il permet de ne payer que ce qu&amp;rsquo;on consomme. Pour illustrer cela, prenons un scénario réaliste d&amp;rsquo;entreprise : un mois de travail standard (environ 160 heures) pour un développeur alternant entre validation IA et code manuel.&lt;/p&gt;
&lt;p&gt;Dans un modèle cloud classique, votre environnement tourne (et vous facture) pendant ces 160 heures, que vous soyez en train de taper du code, en réunion, ou d&amp;rsquo;attendre qu&amp;rsquo;une IA finisse son travail. Regardons l&amp;rsquo;impact financier de notre approche face aux ténors du marché :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google Cloud Workstations&lt;/th&gt;
&lt;th&gt;GitHub Codespaces (4-core)&lt;/th&gt;
&lt;th&gt;Notre Cloud Run Workstation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coût mensuel (160h)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~192 $&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~61 $&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~28 $&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coût d&amp;rsquo;attente (idle)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~171 $&lt;/strong&gt; (Control plane 24/7)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3,50 $&lt;/strong&gt; (Stockage persistant)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 $&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pénalité Asynchrone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;L&amp;rsquo;instance tourne à vide&lt;/td&gt;
&lt;td&gt;Le stockage est facturé&lt;/td&gt;
&lt;td&gt;Seul le bucket GCS est facturé (&amp;lt; 1$)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Comment lire ce tableau ?&lt;/strong&gt; La différence radicale (28$ contre 192$) s&amp;rsquo;explique par la suppression pure et simple du &lt;strong&gt;coût d&amp;rsquo;attente&lt;/strong&gt;. Si l&amp;rsquo;IA prépare une grosse feature le lundi, mais que vous ne trouvez le temps de la réviser que le jeudi pendant 2 heures, &lt;strong&gt;Cloud Run ne vous aura coûté que 0,40 $&lt;/strong&gt; sur toute la semaine. Les autres solutions vous facturent des frais fixes en continu.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="architecture-du-projet-et-pipeline-de-build"&gt;Architecture du projet et pipeline de build&lt;a class="headerlink" href="#architecture-du-projet-et-pipeline-de-build" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Une des étapes du projet est la création d&amp;rsquo;un conteneur de base qui sera utilisé par Cloud Run et l&amp;rsquo;autre partie du projet consiste en la création d&amp;rsquo;un catalogue GitLab permettant de préparer le code pour la reprise par la workstation.&lt;/p&gt;
&lt;h3 id="le-conteneur-de-base"&gt;Le conteneur de base&lt;a class="headerlink" href="#le-conteneur-de-base" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Le projet est volontairement minimaliste — trois dossiers, zéro framework superflu :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;workstation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;oss&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;# 🏗️ Golden Image Nix&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;#    Définition déclarative de l&amp;#39;image Docker&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="c1"&gt;#    Verrouillage des dépendances (reproductibilité)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trivyignore&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;#    CVE acceptées (revue mensuelle)&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="c1"&gt;# ☁️ Infrastructure as Code&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;#    APIs GCP, Artifact Registry, Secret Manager&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="c1"&gt;#    Service Account &amp;amp; rôles (moindre privilège)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;#    Variables du projet&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;#    Configuration du provider GCP&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cloudbuild&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;# 🔄 Pipeline CI/CD (Cloud Build)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Makefile&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="c1"&gt;# 🎯 Point d&amp;#39;entrée développeur (init/build/deploy)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;base-oss/flake.nix&lt;/code&gt;&lt;/strong&gt; est le cœur du projet : il définit de manière purement déclarative tous les outils embarqués dans la Golden Image (bash, git, code-server, nix…) et génère l&amp;rsquo;image Docker via &lt;code&gt;pkgs.dockerTools.buildLayeredImage&lt;/code&gt; — sans Dockerfile, sans daemon Docker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;terraform/&lt;/code&gt;&lt;/strong&gt; provisionne l&amp;rsquo;infrastructure GCP nécessaire : activation des APIs, création de l&amp;rsquo;Artifact Registry pour stocker les images, du Secret Manager pour le mot de passe IDE, et du Service Account avec le principe du moindre privilège.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;cloudbuild.yaml&lt;/code&gt;&lt;/strong&gt; orchestre le pipeline CI/CD complet. Voici le flux de bout en bout, du &lt;code&gt;git push&lt;/code&gt; à l&amp;rsquo;image déployable :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;flowchart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LR&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;📦 git push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🔨 Nix Build\n(nixos/nix)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;image.tar.gz&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🐳 Docker Load\n&amp;amp; Tag&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🔍 Trivy Scan\n(CRITICAL/HIGH)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;✅ Pass&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;📤 Push\nArtifact Registry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;❌ Fail&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🚫 Build\nBloqué&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fbbc04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le pipeline se décompose en &lt;strong&gt;4 étapes&lt;/strong&gt; :
1. &lt;strong&gt;Nix Build&lt;/strong&gt; — Le conteneur &lt;code&gt;nixos/nix&lt;/code&gt; évalue &lt;code&gt;flake.nix&lt;/code&gt; et produit une archive Docker layered (&lt;code&gt;image.tar.gz&lt;/code&gt;). Aucune dépendance implicite : tout est dans le lockfile.
2. &lt;strong&gt;Docker Tag&lt;/strong&gt; — L&amp;rsquo;image est chargée puis taguée avec &lt;code&gt;latest&lt;/code&gt; et le short SHA du commit pour la traçabilité.
3. &lt;strong&gt;Trivy Scan&lt;/strong&gt; — Scan de sécurité automatique. Si des CVE critiques non acceptées sont détectées, &lt;strong&gt;le build est bloqué&lt;/strong&gt; (exit code 1). Seules les CVE documentées dans &lt;code&gt;.trivyignore&lt;/code&gt; sont autorisées.
4. &lt;strong&gt;Push&lt;/strong&gt; — L&amp;rsquo;image validée est poussée vers Artifact Registry, prête à être déployée sur Cloud Run.&lt;/p&gt;
&lt;p&gt;Le tout s&amp;rsquo;exécute via un simple &lt;code&gt;make build&lt;/code&gt; côté développeur — Cloud Build s&amp;rsquo;occupe du reste.&lt;/p&gt;
&lt;h3 id="la-forge-lagent-ia-en-action"&gt;La forge : L&amp;rsquo;Agent IA en action&lt;a class="headerlink" href="#la-forge-lagent-ia-en-action" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Une workstation à la demande prend tout son sens lorsque le travail préparatoire a été mâché par une intelligence artificielle. Pour faire le pont entre une &amp;ldquo;Issue&amp;rdquo; métier (le besoin) et l&amp;rsquo;environnement Cloud Run (la validation), nous avons besoin d&amp;rsquo;une &lt;strong&gt;forge automatisée&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Plutôt que d&amp;rsquo;exécuter un agent IA en local sur la machine du développeur, nous déléguons cette tâche à la CI/CD (ici GitLab CI), via un composant réutilisable (CI/CD Catalog). Voici comment fonctionne ce pipeline asynchrone :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Déclenchement (Trigger) :&lt;/strong&gt; Ce job CI est conçu pour scruter les issues portant un label spécifique (ex: &lt;code&gt;agent::run&lt;/code&gt;). Il peut être déclenché &lt;strong&gt;manuellement&lt;/strong&gt; par un développeur (via l&amp;rsquo;interface GitLab) ou planifié de manière régulière via un &lt;strong&gt;CRON job&lt;/strong&gt; (Scheduled Pipeline) qui va parcourir la liste d&amp;rsquo;attente de façon autonome (par exemple, toutes les nuits).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exécution du Gemini CLI :&lt;/strong&gt; Le job CI instancie un environnement léger contenant un outil en ligne de commande (&lt;code&gt;gemini-cli&lt;/code&gt;) connecté à l&amp;rsquo;API Google Gemini. Ce CLI dispose d&amp;rsquo;outils (Function Calling / Tools) lui permettant de lire l&amp;rsquo;arborescence du projet, examiner le code existant et écrire de nouveaux fichiers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-coding de la feature :&lt;/strong&gt; L&amp;rsquo;agent IA analyse la demande contenue dans le ticket. Il crée une nouvelle branche depuis &lt;code&gt;main&lt;/code&gt;, échafaude le code (scaffolding), écrit les tests de base et implémente la fonctionnalité demandée.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Injection de l&amp;rsquo;environnement (&lt;code&gt;devenv.nix&lt;/code&gt;) :&lt;/strong&gt; C&amp;rsquo;est ici que la magie opère. L&amp;rsquo;IA sait de quels outils elle a besoin pour la feature (une nouvelle base Redis, l&amp;rsquo;ajout d&amp;rsquo;un compilateur Rust, un package Python spécifique, etc.). Elle va &lt;strong&gt;générer ou mettre à jour statiquement le fichier &lt;code&gt;devenv.nix&lt;/code&gt;&lt;/strong&gt; à la racine du projet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge Request et Hand-off :&lt;/strong&gt; Le code généré est poussé sur GitLab et une Merge Request est automatiquement ouverte. Le développeur reçoit une notification : le terrain est préparé.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Le passage de relais :&lt;/strong&gt;
Lorsque le développeur humain clique sur le lien pour démarrer sa Workstation Nix (Cloud Run), il bascule directement sur la branche de l&amp;rsquo;agent. La Workstation détecte automatiquement le fichier &lt;code&gt;.vscode/extensions.json&lt;/code&gt; généré par l&amp;rsquo;IA et installe silencieusement les extensions recommandées en arrière-plan pendant le démarrage. Grâce au fichier &lt;code&gt;devenv.nix&lt;/code&gt; fraîchement injecté, il lui suffit de taper &lt;code&gt;devenv shell&lt;/code&gt; dans le terminal. Instantanément, toutes les dépendances requises par la nouvelle feature sont téléchargées et configurées via Nix, de manière parfaitement isolée et reproductible.&lt;/p&gt;
&lt;p&gt;Le développeur n&amp;rsquo;a plus qu&amp;rsquo;à relire le code généré par l&amp;rsquo;IA, l&amp;rsquo;exécuter dans l&amp;rsquo;environnement préparé pour lui, ajuster les détails métier complexes et valider la Merge Request. Le cycle est bouclé.&lt;/p&gt;
&lt;p&gt;Je ne vais pas détailler toute l&amp;rsquo;intégration de l&amp;rsquo;IA dans le projet, ce billet se concentrant plutôt sur l&amp;rsquo;idée de Workstation dans Cloud Run. Cependant, voici quelques indications sur le workflow que j&amp;rsquo;imagine.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Exemple d&amp;rsquo;intégration dans &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; :&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour activer ce workflow dans n&amp;rsquo;importe quel projet, il suffit d&amp;rsquo;inclure le composant d&amp;rsquo;agent IA depuis le catalogue CI/CD GitLab :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;CI_SERVER_HOST&lt;/span&gt;&lt;span class="p p-Indicator"&gt;}}&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/mon-orga/catalog/gemini-agent@1.0.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;trigger_label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;agent::run&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gemini_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gemini-2.5-pro&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce simple composant suffit à transformer votre GitLab CI en une véritable équipe de développement asynchrone, prête à préparer le terrain pour vos Workstations Nix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sous le capot : Le System Prompt de l&amp;rsquo;Agent&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour que cet agent soit réellement autonome, voici un exemple de prompt système. Il définit clairement le rôle de l&amp;rsquo;agent, le concept de &amp;ldquo;Grove&amp;rdquo;, et insiste fortement sur la reproductibilité.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Voir le System Prompt de l'Agent Gemini&lt;/summary&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Tu es un Agent IA de Développement Autonome intégré dans une pipeline GitLab CI/CD. 
Ton rôle est de préparer le terrain pour les développeurs humains en pré-codant des fonctionnalités et en provisionnant des environnements de développement (Workstations) entièrement prêts à l&amp;#39;emploi.

Voici ta mission. Tu dois exécuter ces étapes de manière séquentielle et autonome :

1. RECHERCHE DES ISSUES :
   - Utilise l&amp;#39;API GitLab pour lister toutes les issues ouvertes du dépôt courant portant le label &amp;quot;{{trigger_label}}&amp;quot;.
   - Si aucune issue n&amp;#39;est trouvée, termine ton exécution avec succès.

2. POUR CHAQUE ISSUE TROUVÉE, exécute le workflow suivant :

   A. ANALYSE ET PRÉPARATION :
      - Lis attentivement la description de l&amp;#39;issue pour comprendre le besoin technique.
      - Crée une nouvelle branche Git au nom explicite (ex: `feature/issue-&amp;lt;ID&amp;gt;-&amp;lt;nom-court&amp;gt;`).

   B. CRÉATION DU GROVE (ESPACE DE TRAVAIL) :
      - Provisionne un nouvel espace persistant (un &amp;quot;Grove&amp;quot;) sur Google Cloud Storage (GCS) dédié à cette issue.
      - Initialise ce grove en y clonant le code source de la branche nouvellement créée.

   C. PRE-CODING &amp;amp; DEPENDANCES :
      - Implémente la fonctionnalité demandée ou corrige le bug.
      - Ajoute des tests unitaires basiques pour prouver le fonctionnement de ton code.
      - CRITIQUE : Analyse les dépendances nécessaires à ton code (Node.js, Python, PostgreSQL, etc.). Tu DOIS générer ou mettre à jour le fichier `devenv.nix` à la racine du projet pour y déclarer ces dépendances, afin que l&amp;#39;environnement soit reproductible pour l&amp;#39;humain.
      - CONFIGURATION IDE : Si le projet utilise Python, génère un fichier `.vscode/extensions.json` recommandant les extensions &amp;quot;ms-python.python&amp;quot; et &amp;quot;ms-python.vscode-pylance&amp;quot;, ainsi qu&amp;#39;un fichier `.vscode/settings.json` configurant `python.defaultInterpreterPath` vers l&amp;#39;exécutable du virtualenv géré par devenv (c&amp;#39;est-à-dire `.devenv/state/venv/bin/python`).
      - Commite tes changements et pousse la branche sur GitLab. Ouvre une Merge Request (WIP/Draft).

   D. DEPLOIEMENT DE LA WORKSTATION :
      - Utilise les scripts du dépôt (ex: `make deploy`) pour déployer une instance Cloud Run Workstation éphémère.
      - Configure cette Workstation pour qu&amp;#39;elle monte le Grove GCS que tu as créé à l&amp;#39;étape B.
      - Récupère l&amp;#39;URL d&amp;#39;accès HTTPS générée par Cloud Run et le mot de passe.

   E. HAND-OFF (PASSAGE DE RELAIS) :
      - Publie un commentaire détaillé sur l&amp;#39;Issue GitLab initiale contenant :
        1. Le résumé de tes choix techniques.
        2. Le lien vers la Merge Request.
        3. Le lien d&amp;#39;accès direct vers la Workstation Cloud Run (avec la commande `devenv shell` suggérée).
        4. Les instructions de connexion.
      - Retire le label &amp;quot;{{trigger_label}}&amp;quot; et ajoute &amp;quot;ready-for-review&amp;quot;.

RÈGLES STRICTES :
- Ne demande pas d&amp;#39;assistance humaine en cours de processus. Si tu rencontres une erreur, documente-la et passe à l&amp;#39;issue suivante.
- Le fichier `devenv.nix` est la source de vérité de l&amp;#39;infrastructure logicielle.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/details&gt;

&lt;hr /&gt;
&lt;h2 id="pour-demarrer"&gt;Pour démarrer&lt;a class="headerlink" href="#pour-demarrer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;ensemble de ce socle d&amp;rsquo;infrastructure est &lt;strong&gt;open source&lt;/strong&gt;. Trois commandes suffisent pour déployer votre propre environnement de révision &amp;ldquo;Scale-to-Zero&amp;rdquo; :&lt;/p&gt;
&lt;p&gt;Une fois déployée, ouvrez l&amp;rsquo;URL affichée, entrez votre mot de passe, et vous voilà dans l&amp;rsquo;environnement de révision idéal — prêt à collaborer avec votre agent IA. Quand c&amp;rsquo;est fini, fermez simplement l&amp;rsquo;onglet. &lt;strong&gt;Zéro gaspillage, sécurité maximale.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;em&gt;Source du projet : &lt;a href="https://gitlab.com/matgou/workstation-nix"&gt;gitlab.com/matgou/workstation-nix&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="devops"/><category term="gcp"/><category term="ai"/><category term="genai"/><category term="cloudrun"/><category term="nix"/><category term="serverless"/></entry><entry><title>The "AI-First" Workflow: When Your IDE Only Starts to Validate AI</title><link href="https://www.ops-chronicles.cloud/workstation-cloudrun.html" rel="alternate"/><published>2026-05-14T00:00:00+02:00</published><updated>2026-05-14T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-05-14:/workstation-cloudrun.html</id><summary type="html">&lt;p&gt;A reproducible VS Code Workstation on Cloud Run, powered by Nix, designed for Human-AI collaboration loops — with a $0.00 idle cost.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-developer-validator-era"&gt;The Developer-Validator Era&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-new-lifecycle-from-feature-to-release"&gt;The New Lifecycle: From Feature to Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#why-nix-the-secret-weapon-of-determinism"&gt;Why Nix? The Secret Weapon of Determinism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#under-the-hood-breaking-down-walls-for-serverless-workstations"&gt;Under the Hood: Breaking Down Walls for Serverless Workstations&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#wall-1-the-terminal-that-refuses-to-open"&gt;Wall #1 — The Terminal that Refuses to Open&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#wall-2-websockets-vs-the-proxy-layer"&gt;Wall #2 — WebSockets vs. the Proxy Layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#wall-3-shared-human-ai-storage"&gt;Wall #3 — Shared Human / AI Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#wall-4-running-non-root-with-nix-security"&gt;Wall #4 — Running Non-Root with Nix (Security)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-sinews-of-war-why-scale-to-zero-changes-everything-for-the-budget"&gt;The Sinews of War: Why Scale-to-Zero Changes Everything for the Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#project-architecture-and-build-pipeline"&gt;Project Architecture and Build Pipeline&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-base-container"&gt;The Base Container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-forge-the-ai-agent-in-action"&gt;The Forge: The AI Agent in Action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#to-get-started"&gt;To Get Started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;🔗 &lt;em&gt;Project source: &lt;a href="https://gitlab.com/matgou/workstation-nix"&gt;gitlab.com/matgou/workstation-nix&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A reproducible VS Code Workstation on Cloud Run, powered by Nix, designed for Human-AI collaboration loops — with a $0.00 idle cost.&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="the-developer-validator-era"&gt;The Developer-Validator Era&lt;a class="headerlink" href="#the-developer-validator-era" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not long ago, our development environments lived on our machines, heavy and overly customized. Then came Cloud IDEs (GitHub Codespaces, Google Cloud Workstations), designed for a very specific era: one where human developers code actively for 8 hours a day using the power of remote servers.&lt;/p&gt;
&lt;p&gt;But the emergence of generative AI is also disrupting this paradigm. Today, for code generation, we need GPUs: local (cheap), remote (expensive) and, moreover, two visions clash and we will see which one prevails:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The &amp;ldquo;AIs will soon be 100% autonomous&amp;rdquo; school&lt;/strong&gt;: The developer only maintains specifications in Markdown, the AI does the rest, end-to-end.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &amp;ldquo;AI prepares, human validates&amp;rdquo; school&lt;/strong&gt;: The AI does the bulk of the work, but the developer will always have a role as the guarantor of the produced code. They will have to &amp;ldquo;get their hands dirty&amp;rdquo; to test, validate, and adjust.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s be pragmatic: the first school is still (for now) science fiction for the majority of complex or critical enterprise projects. And I am of the opinion that for now, we still need to get our hands dirty to audit and validate the generated code. However, I notice that our tools have not kept up; the adoption of remote IDEs is still too anecdotal. And paying for a permanent environment when the human only intervenes intermittently makes no economic sense — or even ecological sense.&lt;/p&gt;
&lt;p&gt;I propose here a reflection on new remote IDEs and want to show that they are accessible to all with, as an example, the implementation of a &lt;strong&gt;CloudRun-Workstation&lt;/strong&gt;. These, although not built precisely for this world of symbiosis between the developer and the AI agent, adapt perfectly to it. Today everyone can have a super-powerful IDE, with GPUs, custom-made, available instantly, which only lives the exact time we need to review the code (and not while the AI is working).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="the-new-lifecycle-from-feature-to-release"&gt;The New Lifecycle: From Feature to Release&lt;a class="headerlink" href="#the-new-lifecycle-from-feature-to-release" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To fully understand the new dynamic of AI-agent-driven development, we must rethink our delivery pipeline. The human is no longer the originator of the code, they become the final validator.&lt;/p&gt;
&lt;p&gt;Here is a vision of a modern workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The specifications&lt;/strong&gt; of a ticket or a modification are communicated. (I won&amp;rsquo;t detail here how to write and validate good specifications for the AI to process.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The AI works in the shadows&lt;/strong&gt;: An AI agent (Gemini, Claude, or an autonomous CI pipeline) scaffolds a project, writes a feature, or fixes a bug.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The convergence point&lt;/strong&gt;: The agent deposits all this code directly into a &lt;strong&gt;grove&lt;/strong&gt; — a Google Cloud Storage (GCS) bucket serving as a shared workspace. I borrowed the term &amp;ldquo;grove&amp;rdquo; from the &lt;a href="https://googlecloudplatform.github.io/scion/concepts/#grove"&gt;Google Scion&lt;/a&gt; framework. &lt;em&gt;(Cost: $0, object storage being almost free).&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The hand-off&lt;/strong&gt;: The AI notifies you that the code is ready to be validated or completed by the human.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Human takes over&lt;/strong&gt;: You click on a Cloud Run URL. In seconds, a VS Code instance starts in the Nix Workstation. The AI-generated code is already there, mounted instantly. In this workstation, you can modify, run, debug the code, or even vibe-code it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale to Zero&lt;/strong&gt;: Once the PR is validated, you close the tab. The Cloud Run instance shuts down. You stop paying.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is how this cycle materializes, from the initial request to the release:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;flowchart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LR&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🎯 Feature Request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🤖 AI Agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;git clone + code&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;☁️ Grove (GCS)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;💻 Nix Workstation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GCS FUSE mount&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;spawn&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;👨‍💻 Human&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;review, test, fix&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;git merge&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🚀 Git Release&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fbbc04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The big advantage of a workflow like this is, among other things, the &lt;strong&gt;Scale to Zero&lt;/strong&gt; principle: that is to say, the &lt;strong&gt;total idle cost: $0.00&lt;/strong&gt; — Indeed, the grove (GCS) only costs a few cents in storage. The Workstation consumes resources &lt;em&gt;only&lt;/em&gt; during human validation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AIs manipulating code seems standard now, but VS Code in Cloud Run is a concept. To make it functional and reproducible, we sometimes have to bypass certain limits. This project is a good example of how to push the boundaries of this technology.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Nix Workstation in action on Cloud Run - Live execution of Nix" src="/images/2026-Workstation-Cloudrun/CaptureEDI.png" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="why-nix-the-secret-weapon-of-determinism"&gt;Why Nix? The Secret Weapon of Determinism&lt;a class="headerlink" href="#why-nix-the-secret-weapon-of-determinism" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this project, we must ensure that the environment in which the code runs is reproducible and that it will have all the tools necessary for test suites and development.&lt;/p&gt;
&lt;p&gt;One of the challenges is therefore that the environment is reproducible but not frozen (for example, if we compile a Docker container, we embed all the dependencies statically but also the security flaws).&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;Nix&lt;/strong&gt; comes in by replacing the imperative approach with a purely functional approach. Each dependency is managed as code and embeds its complete dependency tree. &amp;ldquo;The package.json/package-lock of the workstation&amp;rdquo; in a way.&lt;/p&gt;
&lt;p&gt;Here is why Nix has become indispensable for this architecture:
- &lt;strong&gt;Bit-for-bit reproducibility&lt;/strong&gt;: The exact same &lt;code&gt;flake.lock&lt;/code&gt; file (generated by the AI or the template) always produces exactly the same image.
- &lt;strong&gt;Atomic composition&lt;/strong&gt;: Adding a tool comes down to one line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ss"&gt;coreTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; pkgs&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  bashInteractive coreutils curl git jq neovim nix ripgrep
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Every binary — from &lt;code&gt;bash&lt;/code&gt; to &lt;code&gt;code-server&lt;/code&gt; — is pulled from the Nix Store with its exact version. Nix then generates the final Docker image for us via &lt;code&gt;pkgs.dockerTools.buildLayeredImage&lt;/code&gt;, without even needing a complex Docker daemon.
- &lt;strong&gt;No artifacts to store&lt;/strong&gt;: Only the &lt;code&gt;flake.nix&lt;/code&gt; code file needs to be stored. The beauty of it is that if you change machines, you start again with the same configuration.&lt;/p&gt;
&lt;p&gt;This is exactly the choice made by &lt;a href="https://devenv.sh/"&gt;devenv.sh&lt;/a&gt;, an open source project by &lt;a href="https://github.com/cachix/devenv"&gt;Cachix&lt;/a&gt; that standardizes the definition of development environments on top of Nix. Its principle: describe your environment in a simple &lt;code&gt;devenv.nix&lt;/code&gt; file with declarative syntax — languages, packages, services, scripts — and let Nix materialize everything reproducibly. Activating Python, Rust or PostgreSQL comes down to one line (&lt;code&gt;languages.python.enable = true;&lt;/code&gt;), activating an environment takes less than 100 ms thanks to the cache, and everything remains decoratable via profiles and imports.&lt;/p&gt;
&lt;p&gt;Our CloudRun-Workstation &lt;strong&gt;integrates naturally with the devenv standard&lt;/strong&gt;. Once connected to the IDE, the developer can simply launch &lt;code&gt;devenv shell&lt;/code&gt; in the VS Code terminal to activate the environment declared in the project repository hosted on the grove. The tools, versions, services — everything is already defined &lt;em&gt;as code&lt;/em&gt; by the team or by the AI. The developer has nothing to install manually: the development environment is as ephemeral and reproducible as the workstation itself.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="under-the-hood-breaking-down-walls-for-serverless-workstations"&gt;Under the Hood: Breaking Down Walls for Serverless Workstations&lt;a class="headerlink" href="#under-the-hood-breaking-down-walls-for-serverless-workstations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On paper, running a web IDE like VS Code on Cloud Run seems easy. Nix offers a build system to create containers. But in practice (and particularly from a DevSecOps point of view), Serverless platforms are &lt;strong&gt;not at all&lt;/strong&gt; designed to host stateful environments with complex terminal accesses.&lt;/p&gt;
&lt;p&gt;To turn this vision into reality, I had to confront and bypass some major technical limits of the current cloud ecosystem. Here is a quick retrospective on the problems encountered:&lt;/p&gt;
&lt;h3 id="wall-1-the-terminal-that-refuses-to-open"&gt;Wall #1 — The Terminal that Refuses to Open&lt;a class="headerlink" href="#wall-1-the-terminal-that-refuses-to-open" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We will use the VS Code terminal to interact with the environment. But manipulating a terminal requires interacting with the system kernel.&lt;/p&gt;
&lt;p&gt;Cloud Run&amp;rsquo;s default gVisor kernel (Gen1) does not support pseudo-terminals (&lt;code&gt;/dev/ptmx&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: Switch to the second-generation Cloud Run execution engine (&lt;code&gt;--execution-environment gen2&lt;/code&gt;). This runs the container in a real Linux micro-VM. We then manually mount &lt;code&gt;devpts&lt;/code&gt; at startup to unblock the VS Code terminals.&lt;/p&gt;
&lt;h3 id="wall-2-websockets-vs-the-proxy-layer"&gt;Wall #2 — WebSockets vs. the Proxy Layer&lt;a class="headerlink" href="#wall-2-websockets-vs-the-proxy-layer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;gcloud run services proxy&lt;/code&gt; command, which allows accessing a Cloud Run service via a local tunnel, &lt;strong&gt;does not support WebSocket connections&lt;/strong&gt;. However, VS Code (code-server) relies heavily on WebSockets for the terminal and real-time editing. Result: random terminal freezes and disconnections mid-session. This problem de facto blocks the use of IAP (Identity-Aware Proxy) as an authentication layer, since the proxy is the entry point.&lt;/p&gt;
&lt;p&gt;An alternative would have been to place a &lt;strong&gt;Load Balancer&lt;/strong&gt; with IAP in front of Cloud Run — a clean solution but one that introduces a permanent fixed cost (~$18/month for the forwarding rule), which goes against our &lt;strong&gt;Scale-to-Zero philosophy with zero idle cost&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Pragmatic Solution&lt;/strong&gt;: Expose Cloud Run directly (with its native HTTPS URL) and delegate security to &lt;code&gt;code-server&lt;/code&gt;&amp;rsquo;s native &lt;code&gt;--auth password&lt;/code&gt; mechanism, which is fully WebSocket compatible. The Cloud Run instance timeout is also pushed to its &lt;strong&gt;legal maximum (3600 seconds)&lt;/strong&gt; to guarantee an uninterrupted session, and the maximum number of instances is locked to &lt;strong&gt;1&lt;/strong&gt; (&lt;code&gt;--max-instances 1&lt;/code&gt;) to guarantee a single session and control costs.&lt;/p&gt;
&lt;h3 id="wall-3-shared-human-ai-storage"&gt;Wall #3 — Shared Human / AI Storage&lt;a class="headerlink" href="#wall-3-shared-human-ai-storage" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At each scale-to-zero, the container disappears. We needed a persistent space where the AI can deposit the code and where the workstation can retrieve and modify it. Then pick it up in case of a stop/start of the container. The goal being to be able to work on a project, abandon it, and pick it up later without losing work. Ideal for testing periods.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Solution: GCS FUSE and the Mirage of POSIX Permissions:&lt;/strong&gt;
A Cloud Storage bucket mounted via GCS FUSE seemed the obvious solution. However, a new wall appeared: GCS is not a POSIX file system and does not natively handle permission changes (the famous &lt;code&gt;chmod&lt;/code&gt; and &lt;code&gt;chown&lt;/code&gt;).
Consequence? Modern tools like &lt;code&gt;devenv&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;git&lt;/code&gt;, which try to secure their temporary files or virtual environments, crashed miserably with a fatal error: &lt;code&gt;Operation not permitted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To bypass this strict limitation without sacrificing real-time persistence, we &amp;ldquo;tricked&amp;rdquo; the FUSE mounting tool by forcing a total simulation of permissions for our exclusive user. Here is the Cloud Run mount configuration optimized for the IDE (some performance options were also added):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;implicit-dirs
stat-cache-max-size-mb=32        # Allocates 32 MB in RAM for metadata cache
metadata-cache-ttl-secs=300      # Keeps metadata in cache for 5 minutes
enable-streaming-writes=true     # Direct streaming writes
client-protocol=http1            # (Performance) Force HTTP/1.1 to avoid HTTP/2 Head-of-Line blocking
max-conns-per-host=100           # (Performance) Massify parallel I/O
cache-dir=cr-volume:cache        # Tells Cloud Run to use our in-memory volume named &amp;quot;cache&amp;quot;
file-cache-max-size-mb=2048      # Allocates 2GB of RAM for this cache
uid=1000                         # Forces ownership to the VS Code user
gid=1000
file-mode=0777                   # Simulates total permissiveness
dir-mode=0777
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Asynchrony and the Management of Small Files:&lt;/strong&gt;
Creating a virtual environment (e.g., &lt;code&gt;python -m venv&lt;/code&gt;) involves the creation of thousands of small files. With a classic network mount and the default HTTP/2 protocol of GCS FUSE, all these requests pile up synchronously on a single TCP connection.
We therefore force the HTTP/1.1 protocol (&lt;code&gt;client-protocol=http1&lt;/code&gt;), open a pool of 100 simultaneous connections to massively parallelize the writes. Moreover, we keep a local RAM cache (&lt;code&gt;cache-dir=cr-volume:cache&lt;/code&gt;) of 2 GB to considerably accelerate the reading of the existing source code.&lt;/p&gt;
&lt;p&gt;However, one must be aware of the limits of Serverless: even with this parallelization and these aggressive caches, the incompressible synchronous network latency of Google Cloud Storage remains a bottleneck for the pure creation of massive tree structures. Here is the evolution of the performances measured for the creation of a Python environment (&lt;code&gt;devenv shell&lt;/code&gt;) on our Cloud Run architecture:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;GCS FUSE Configuration&lt;/th&gt;
&lt;th style="text-align: left;"&gt;&lt;code&gt;venv&lt;/code&gt; Creation Time&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Assessment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Standard (Default HTTP/2)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 9 min 42 s&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Unusable on a daily basis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Optimized (HTTP/1.1 + max-conns=100 + RAM Caches)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 8 min 44 s&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Better, but synchronous latency throttles the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Optimized + &lt;code&gt;.devenv&lt;/code&gt; Relocation to RAM (Symlink)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;~ 45 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Near-local performance!&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The ultimate workaround (implemented via a transparent wrapper within the Nix image itself) consists of &lt;strong&gt;relocating the local &lt;code&gt;.devenv&lt;/code&gt; state folder into RAM (&lt;code&gt;tmpfs&lt;/code&gt;)&lt;/strong&gt; using a dynamic symbolic link. Since the &lt;code&gt;/nix/store&lt;/code&gt; is recreated at each startup of the instance, the environment&amp;rsquo;s state folder is by essence ephemeral. By generating it in RAM, the creation of thousands of files becomes atomic and immediate.
The developer thus keeps a persistent environment for their code via GCS FUSE (benefiting from the cache options for reading responsiveness), while totally avoiding GCS latency for the creation of heavy tooling.&lt;/p&gt;
&lt;p&gt;Thanks to the &lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;gid&lt;/code&gt; and &lt;code&gt;mode&lt;/code&gt; flags, GCS FUSE also considers that the user already has all rights. When a tool requests a &lt;code&gt;chmod&lt;/code&gt;, GCS FUSE returns a dummy success code instead of crashing. The developer thus obtains a persistent development environment, while avoiding crashes related to POSIX requirements.&lt;/p&gt;
&lt;h3 id="wall-4-running-non-root-with-nix-security"&gt;Wall #4 — Running Non-Root with Nix (Security)&lt;a class="headerlink" href="#wall-4-running-non-root-with-nix-security" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Running an IDE as full &lt;code&gt;root&lt;/code&gt; is a security heresy. But Nix needs to write to &lt;code&gt;/nix/store&lt;/code&gt; to install packages. The naive solution — giving ownership of the entire store to the &lt;code&gt;coder&lt;/code&gt; user (single-user mode) — is an anti-pattern of security: the user could then modify any system binary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: Use the &lt;strong&gt;Nix multi-user mode&lt;/strong&gt; with &lt;code&gt;nix-daemon&lt;/code&gt;. The container starts as root, launches the Nix daemon in the background (which remains root and exclusively manages the store), then &lt;strong&gt;irrevocably drops its privileges&lt;/strong&gt; via &lt;code&gt;gosu&lt;/code&gt; before launching the IDE under UID 1000. Thus, the &lt;code&gt;/nix/store&lt;/code&gt; remains &lt;strong&gt;read-only&lt;/strong&gt; for the &lt;code&gt;coder&lt;/code&gt; user — they can install new packages via the daemon (which verifies and writes for them), but can never alter existing binaries. This is the same architecture used on production NixOS machines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some additional minor optimizations concerning Nix:&lt;/strong&gt;
- &lt;strong&gt;Network disk bypass:&lt;/strong&gt; We force &lt;code&gt;export TMPDIR=/tmp/cache&lt;/code&gt; to direct builds to our ultra-fast &lt;em&gt;in-memory&lt;/em&gt; volume (4 GB RAM-disk mounted specially for the occasion).
- &lt;strong&gt;Bringing Nix into line:&lt;/strong&gt; We block wild parallelization by forcing &lt;code&gt;max-jobs = 4&lt;/code&gt; and &lt;code&gt;cores = 2&lt;/code&gt; in the Nix configuration to respect the vCPUs actually allocated to the container.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="the-sinews-of-war-why-scale-to-zero-changes-everything-for-the-budget"&gt;The Sinews of War: Why Scale-to-Zero Changes Everything for the Budget&lt;a class="headerlink" href="#the-sinews-of-war-why-scale-to-zero-changes-everything-for-the-budget" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have managed to package Nix and VS Code in a container, we can use it with a Cloud Run approach. Here is what changes in terms of costs.&lt;/p&gt;
&lt;p&gt;The Serverless approach shines particularly in this hybrid workflow, because it allows paying only for what is consumed. To illustrate this, let&amp;rsquo;s take a realistic corporate scenario: a standard working month (about 160 hours) for a developer alternating between AI validation and manual coding.&lt;/p&gt;
&lt;p&gt;In a classic cloud model, your environment runs (and bills you) during these 160 hours, whether you are typing code, in a meeting, or waiting for an AI to finish its work. Let&amp;rsquo;s look at the financial impact of our approach against the market leaders:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google Cloud Workstations&lt;/th&gt;
&lt;th&gt;GitHub Codespaces (4-core)&lt;/th&gt;
&lt;th&gt;Our Cloud Run Workstation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly Cost (160h)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$192&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$61&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idle Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$171&lt;/strong&gt; (Control plane 24/7)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$3.50&lt;/strong&gt; (Persistent storage)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asynchronous Penalty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The instance runs idle&lt;/td&gt;
&lt;td&gt;Storage is billed&lt;/td&gt;
&lt;td&gt;Only the GCS bucket is billed (&amp;lt; $1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;How to read this table?&lt;/strong&gt; The radical difference ($28 vs $192) is explained by the pure and simple elimination of the &lt;strong&gt;idle cost&lt;/strong&gt;. If the AI prepares a large feature on Monday, but you only find time to review it on Thursday for 2 hours, &lt;strong&gt;Cloud Run will have only cost you $0.40&lt;/strong&gt; over the whole week. The other solutions bill you fixed fees continuously.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="project-architecture-and-build-pipeline"&gt;Project Architecture and Build Pipeline&lt;a class="headerlink" href="#project-architecture-and-build-pipeline" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the project steps is the creation of a base container that will be used by Cloud Run and the other part of the project consists of creating a GitLab catalog allowing the code to be prepared for resumption by the workstation.&lt;/p&gt;
&lt;h3 id="the-base-container"&gt;The Base Container&lt;a class="headerlink" href="#the-base-container" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The project is intentionally minimalist — three folders, zero superfluous frameworks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;workstation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;oss&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;# 🏗️ Golden Image Nix&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;#    Declarative definition of the Docker image&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="c1"&gt;#    Dependency locking (reproducibility)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trivyignore&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;#    Accepted CVEs (monthly review)&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="c1"&gt;# ☁️ Infrastructure as Code&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;#    GCP APIs, Artifact Registry, Secret Manager&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="c1"&gt;#    Service Account &amp;amp; roles (least privilege)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;#    Project variables&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;#    GCP provider configuration&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cloudbuild&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;# 🔄 CI/CD Pipeline (Cloud Build)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Makefile&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="c1"&gt;# 🎯 Developer entry point (init/build/deploy)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;base-oss/flake.nix&lt;/code&gt;&lt;/strong&gt; is the heart of the project: it declaratively defines all the tools embedded in the Golden Image (bash, git, code-server, nix…) and generates the Docker image via &lt;code&gt;pkgs.dockerTools.buildLayeredImage&lt;/code&gt; — without Dockerfile, without Docker daemon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;terraform/&lt;/code&gt;&lt;/strong&gt; provisions the necessary GCP infrastructure: API activation, creation of the Artifact Registry to store images, the Secret Manager for the IDE password, and the Service Account with the principle of least privilege.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;cloudbuild.yaml&lt;/code&gt;&lt;/strong&gt; orchestrates the complete CI/CD pipeline. Here is the end-to-end flow, from &lt;code&gt;git push&lt;/code&gt; to the deployable image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;flowchart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LR&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;📦 git push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🔨 Nix Build\n(nixos/nix)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;image.tar.gz&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🐳 Docker Load\n&amp;amp; Tag&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🔍 Trivy Scan\n(CRITICAL/HIGH)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;✅ Pass&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;📤 Push\nArtifact Registry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;|&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;❌ Fail&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🚫 Build\nBlocked&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;4285f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fbbc04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="n"&gt;a853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ea4335&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The pipeline breaks down into &lt;strong&gt;4 steps&lt;/strong&gt;:
1. &lt;strong&gt;Nix Build&lt;/strong&gt; — The &lt;code&gt;nixos/nix&lt;/code&gt; container evaluates &lt;code&gt;flake.nix&lt;/code&gt; and produces a layered Docker archive (&lt;code&gt;image.tar.gz&lt;/code&gt;). No implicit dependencies: everything is in the lockfile.
2. &lt;strong&gt;Docker Tag&lt;/strong&gt; — The image is loaded then tagged with &lt;code&gt;latest&lt;/code&gt; and the short commit SHA for traceability.
3. &lt;strong&gt;Trivy Scan&lt;/strong&gt; — Automatic security scan. If unaccepted critical CVEs are detected, &lt;strong&gt;the build is blocked&lt;/strong&gt; (exit code 1). Only CVEs documented in &lt;code&gt;.trivyignore&lt;/code&gt; are allowed.
4. &lt;strong&gt;Push&lt;/strong&gt; — The validated image is pushed to Artifact Registry, ready to be deployed on Cloud Run.&lt;/p&gt;
&lt;p&gt;Everything is executed via a simple &lt;code&gt;make build&lt;/code&gt; on the developer side — Cloud Build takes care of the rest.&lt;/p&gt;
&lt;h3 id="the-forge-the-ai-agent-in-action"&gt;The Forge: The AI Agent in Action&lt;a class="headerlink" href="#the-forge-the-ai-agent-in-action" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An on-demand workstation takes on its full meaning when the preparatory work has been chewed up by an artificial intelligence. To bridge the gap between a business &amp;ldquo;Issue&amp;rdquo; (the need) and the Cloud Run environment (the validation), we need an &lt;strong&gt;automated forge&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Rather than running an AI agent locally on the developer&amp;rsquo;s machine, we delegate this task to the CI/CD (here GitLab CI), via a reusable component (CI/CD Catalog). Here is how this asynchronous pipeline works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Trigger:&lt;/strong&gt; This CI job is designed to scan for issues bearing a specific label (e.g., &lt;code&gt;agent::run&lt;/code&gt;). It can be triggered &lt;strong&gt;manually&lt;/strong&gt; by a developer (via the GitLab interface) or scheduled regularly via a &lt;strong&gt;CRON job&lt;/strong&gt; (Scheduled Pipeline) that will autonomously go through the waiting list (for example, every night).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini CLI Execution:&lt;/strong&gt; The CI job instantiates a lightweight environment containing a command-line tool (&lt;code&gt;gemini-cli&lt;/code&gt;) connected to the Google Gemini API. This CLI has tools (Function Calling / Tools) allowing it to read the project tree, examine existing code, and write new files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature Pre-coding:&lt;/strong&gt; The AI agent analyzes the request contained in the ticket. It creates a new branch from &lt;code&gt;main&lt;/code&gt;, scaffolds the code, writes basic tests, and implements the requested functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment Injection (&lt;code&gt;devenv.nix&lt;/code&gt;):&lt;/strong&gt; This is where the magic happens. The AI knows what tools it needs for the feature (a new Redis database, the addition of a Rust compiler, a specific Python package, etc.). It will &lt;strong&gt;generate or statically update the &lt;code&gt;devenv.nix&lt;/code&gt; file&lt;/strong&gt; at the root of the project to declare these dependencies, so that the environment is reproducible for the human.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge Request and Hand-off:&lt;/strong&gt; The generated code is pushed to GitLab and a Merge Request is automatically opened. The developer receives a notification: the groundwork is laid.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The Hand-off:&lt;/strong&gt;
When the human developer clicks the link to start their Nix Workstation (Cloud Run), they switch directly to the agent&amp;rsquo;s branch. The Workstation automatically detects the &lt;code&gt;.vscode/extensions.json&lt;/code&gt; file generated by the AI and silently installs the recommended extensions in the background during startup. Thanks to the freshly injected &lt;code&gt;devenv.nix&lt;/code&gt; file, they just need to type &lt;code&gt;devenv shell&lt;/code&gt; in the terminal. Instantly, all dependencies required by the new feature are downloaded and configured via Nix, in a perfectly isolated and reproducible manner.&lt;/p&gt;
&lt;p&gt;The developer only has to read the code generated by the AI, execute it in the environment prepared for them, adjust complex business details, and validate the Merge Request. The cycle is complete.&lt;/p&gt;
&lt;p&gt;I will not detail the entire integration of AI in the project, this post focusing more on the idea of Workstation in Cloud Run. However, here are some indications on the workflow I imagine.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Example Integration in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To activate this workflow in any project, simply include the AI agent component from the GitLab CI/CD catalog:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;CI_SERVER_HOST&lt;/span&gt;&lt;span class="p p-Indicator"&gt;}}&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/my-org/catalog/gemini-agent@1.0.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;trigger_label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;agent::run&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gemini_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gemini-2.5-pro&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This simple component is enough to transform your GitLab CI into a true asynchronous development team, ready to lay the groundwork for your Nix Workstations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Under the Hood: The Agent&amp;rsquo;s System Prompt&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For this agent to be truly autonomous, here is an example of a system prompt. It clearly defines the role of the agent, the concept of &amp;ldquo;Grove&amp;rdquo;, and strongly insists on reproducibility.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;View the Gemini Agent System Prompt&lt;/summary&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;You are an Autonomous Development AI Agent integrated into a GitLab CI/CD pipeline.
Your role is to lay the groundwork for human developers by pre-coding features and provisioning fully ready-to-use development environments (Workstations).

Here is your mission. You must execute these steps sequentially and autonomously:

1. ISSUE SEARCH:
   - Use the GitLab API to list all open issues in the current repository bearing the label &amp;quot;{{trigger_label}}&amp;quot;.
   - If no issues are found, successfully terminate your execution.

2. FOR EACH ISSUE FOUND, execute the following workflow:

   A. ANALYSIS AND PREPARATION:
      - Carefully read the issue description to understand the technical need.
      - Create a new Git branch with an explicit name (e.g., `feature/issue-&amp;lt;ID&amp;gt;-&amp;lt;short-name&amp;gt;`).

   B. GROVE CREATION (WORKSPACE):
      - Provision a new persistent space (a &amp;quot;Grove&amp;quot;) on Google Cloud Storage (GCS) dedicated to this issue.
      - Initialize this grove by cloning the source code of the newly created branch into it.

   C. PRE-CODING &amp;amp; DEPENDENCIES:
      - Implement the requested functionality or fix the bug.
      - Add basic unit tests to prove the functioning of your code.
      - CRITICAL: Analyze the dependencies needed for your code (Node.js, Python, PostgreSQL, etc.). You MUST generate or update the `devenv.nix` file at the project root to declare these dependencies there, so that the environment is reproducible for the human.
      - IDE CONFIGURATION: If the project uses Python, generate a `.vscode/extensions.json` file recommending the &amp;quot;ms-python.python&amp;quot; and &amp;quot;ms-python.vscode-pylance&amp;quot; extensions, as well as a `.vscode/settings.json` file configuring `python.defaultInterpreterPath` to the executable of the virtualenv managed by devenv (i.e., `.devenv/state/venv/bin/python`).
      - Commit your changes and push the branch to GitLab. Open a Merge Request (WIP/Draft).

   D. WORKSTATION DEPLOYMENT:
      - Use the repository scripts (e.g., `make deploy`) to deploy an ephemeral Cloud Run Workstation instance.
      - Configure this Workstation to mount the GCS Grove you created in step B.
      - Retrieve the generated HTTPS access URL by Cloud Run and the password.

   E. HAND-OFF:
      - Post a detailed comment on the initial GitLab Issue containing:
        1. The summary of your technical choices.
        2. The link to the Merge Request.
        3. The direct access link to the Cloud Run Workstation (with the suggested `devenv shell` command).
        4. Connection instructions.
      - Remove the &amp;quot;{{trigger_label}}&amp;quot; label and add &amp;quot;ready-for-review&amp;quot;.

STRICT RULES:
- Do not request human assistance during the process. If you encounter an error, document it and move on to the next issue.
- The `devenv.nix` file is the source of truth for the software infrastructure.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;/details&gt;
&lt;hr /&gt;
&lt;h2 id="to-get-started"&gt;To Get Started&lt;a class="headerlink" href="#to-get-started" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This entire infrastructure base is &lt;strong&gt;open source&lt;/strong&gt;. Three commands are enough to deploy your own &amp;ldquo;Scale-to-Zero&amp;rdquo; review environment:&lt;/p&gt;
&lt;p&gt;Once deployed, open the displayed URL, enter your password, and there you are in the ideal review environment — ready to collaborate with your AI agent. When it&amp;rsquo;s finished, simply close the tab. &lt;strong&gt;Zero waste, maximum security.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;em&gt;Project source: &lt;a href="https://gitlab.com/matgou/workstation-nix"&gt;gitlab.com/matgou/workstation-nix&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="devops"/><category term="gcp"/><category term="ai"/><category term="genai"/><category term="cloudrun"/><category term="nix"/><category term="serverless"/></entry><entry><title>J'ai automatisé ma veille technologique avec un Podcast généré par IA (Gemini &amp; Cloud Run)</title><link href="https://www.ops-chronicles.cloud/fr/gcp-podcast-generator.html" rel="alternate"/><published>2026-01-27T00:00:00+01:00</published><updated>2026-01-27T00:00:00+01:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-01-27:/fr/gcp-podcast-generator.html</id><summary type="html">&lt;p&gt;Comment j&amp;rsquo;ai utilisé Gemini 3, Vertex AI et Cloud Run pour transformer les release notes de Google Cloud en un podcast hebdomadaire engageant. Architecture et code détaillés.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#larchitecture-serverless"&gt;L&amp;rsquo;Architecture &amp;ldquo;Serverless&amp;rdquo;&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#le-code-open-source"&gt;Le Code (Open Source)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-1-la-source-de-donnees-bigquery"&gt;Étape 1 : La Source de Données (BigQuery)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-2-le-scenariste-gemini-3-pro"&gt;Étape 2 : Le Scénariste (Gemini 3 Pro)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-3-le-studio-audio-gemini-25-flash-tts"&gt;Étape 3 : Le Studio Audio (Gemini 2.5 Flash TTS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-4-le-graphiste-imagen-3"&gt;Étape 4 : Le Graphiste (Imagen 3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-5-la-diffusion-fastapi-rss"&gt;Étape 5 : La Diffusion (FastAPI &amp;amp; RSS)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#le-flux-rss-dynamique"&gt;Le Flux RSS Dynamique&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-6-linfrastructure-as-code-terraform"&gt;Étape 6 : L&amp;rsquo;Infrastructure as Code (Terraform)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-7-le-pipeline-cicd-gitlab-ci-cloud-deploy"&gt;Étape 7 : Le Pipeline CI/CD (GitLab CI &amp;amp; Cloud Deploy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#le-cout-finops"&gt;Le Coût (FinOps)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Vous n&amp;rsquo;avez pas le temps de lire les centaines de release notes publiées par Google Cloud chaque semaine ? Moi non plus. C&amp;rsquo;est pour cela que j&amp;rsquo;ai créé le &lt;strong&gt;GCP News Podcast&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;🎙️ &lt;strong&gt;&lt;a href="https://podcast.kapable.info/"&gt;Écoutez le résultat final ici : podcast.kapable.info&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ce projet n&amp;rsquo;est pas juste une démo, c&amp;rsquo;est un pipeline de production entièrement automatisé &amp;ldquo;Serverless&amp;rdquo; qui :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Récupère&lt;/strong&gt; l&amp;rsquo;actualité technique brute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scénarise&lt;/strong&gt; un dialogue entre deux experts virtuels (Marc et Sophie).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Génère&lt;/strong&gt; l&amp;rsquo;audio avec un réalisme bluffant (intonations, rires, pauses).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crée&lt;/strong&gt; une couverture d&amp;rsquo;album unique pour chaque épisode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publie&lt;/strong&gt; le tout sur le web et les plateformes de podcast (Spotify, etc.).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voici comment j&amp;rsquo;ai construit ce système.&lt;/p&gt;
&lt;h2 id="larchitecture-serverless"&gt;L&amp;rsquo;Architecture &amp;ldquo;Serverless&amp;rdquo;&lt;a class="headerlink" href="#larchitecture-serverless" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Le système est conçu pour coûter près de zéro lorsqu&amp;rsquo;il ne tourne pas. Il s&amp;rsquo;appuie sur une architecture événementielle et des services managés.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Architecture du Podcast Generator" src="/images/2026-GCP-Podcast-Generator/architecture.png" /&gt;&lt;/p&gt;
&lt;h3 id="le-code-open-source"&gt;Le Code (Open Source)&lt;a class="headerlink" href="#le-code-open-source" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tout le code est disponible sur GitLab : &lt;a href="https://gitlab.com/matgou/podcast-generator"&gt;&lt;strong&gt;matgou/podcast-generator&lt;/strong&gt;&lt;/a&gt;.
Il est divisé en deux parties :
*   &lt;code&gt;generator/&lt;/code&gt; : Le script Python batch qui crée l&amp;rsquo;épisode.
*   &lt;code&gt;frontend/&lt;/code&gt; : L&amp;rsquo;interface web FastAPI pour écouter les épisodes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="etape-1-la-source-de-donnees-bigquery"&gt;Étape 1 : La Source de Données (BigQuery)&lt;a class="headerlink" href="#etape-1-la-source-de-donnees-bigquery" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Contrairement à beaucoup de projets qui font du &amp;ldquo;scraping&amp;rdquo;, ici nous utilisons une source de vérité propre : le dataset public BigQuery de Google.&lt;/p&gt;
&lt;p&gt;Le script exécute une simple requête SQL pour récupérer tout ce qui a changé depuis 7 jours.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    SELECT description, published_at, product_name&lt;/span&gt;
&lt;span class="s2"&gt;    FROM `bigquery-public-data.google_cloud_release_notes.release_notes`&lt;/span&gt;
&lt;span class="s2"&gt;    WHERE published_at &amp;gt; DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)&lt;/span&gt;
&lt;span class="s2"&gt;    ORDER BY published_at DESC&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cela nous donne une liste brute de texte, souvent très aride et technique. C&amp;rsquo;est là que l&amp;rsquo;IA entre en jeu.&lt;/p&gt;
&lt;h2 id="etape-2-le-scenariste-gemini-3-pro"&gt;Étape 2 : Le Scénariste (Gemini 3 Pro)&lt;a class="headerlink" href="#etape-2-le-scenariste-gemini-3-pro" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nous injectons ces notes brutes dans &lt;strong&gt;Gemini 3 Pro&lt;/strong&gt; avec un prompt système très spécifique (le &lt;code&gt;prompt_template.txt&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Le but n&amp;rsquo;est pas de résumer, mais de &lt;strong&gt;scénariser&lt;/strong&gt;.
*   &lt;strong&gt;Marc&lt;/strong&gt; est l&amp;rsquo;expert technique, précis et factuel.
*   &lt;strong&gt;Sophie&lt;/strong&gt; est l&amp;rsquo;animatrice curieuse, qui pose les questions que l&amp;rsquo;auditeur se poserait.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;une des fonctionnalités clés utilisées ici est le &lt;strong&gt;Grounding&lt;/strong&gt; avec Google Search. Cela permet à Gemini de vérifier les faits et d&amp;rsquo;ajouter des liens vers la documentation officielle dans les métadonnées de l&amp;rsquo;épisode.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Exemple de sortie structurée JSON attendue de Gemini&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cloud Run s&amp;#39;accélère et BigQuery se sécurise&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cette semaine, Marc et Sophie discutent des nouvelles instances...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;script&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sophie: Bonjour à tous ! ... Marc: Exactement, et c&amp;#39;est majeur...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;image_prompt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;An abstract representation of a fast server race in a neon city...&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-3-le-studio-audio-gemini-25-flash-tts"&gt;Étape 3 : Le Studio Audio (Gemini 2.5 Flash TTS)&lt;a class="headerlink" href="#etape-3-le-studio-audio-gemini-25-flash-tts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;C&amp;rsquo;est la partie la plus impressionnante. Nous n&amp;rsquo;utilisons pas l&amp;rsquo;ancien &amp;ldquo;Text-to-Speech&amp;rdquo; robotique. Nous utilisons le modèle &lt;strong&gt;Gemini 2.5 Flash TTS&lt;/strong&gt; (encore en preview), capable de générer plusieurs locuteurs dans le même flux audio.&lt;/p&gt;
&lt;p&gt;Le prompt définit les personnalités vocales :
*   Marc utilise la voix &lt;code&gt;Fenrir&lt;/code&gt;.
*   Sophie utilise la voix &lt;code&gt;Laomedeia&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le script Python appelle directement l&amp;rsquo;API REST de Vertex AI pour générer l&amp;rsquo;audio.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extrait simplifié de l&amp;#39;appel API&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;contents&amp;quot;&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;quot;parts&amp;quot;&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;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;}]}],&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;generationConfig&amp;quot;&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;quot;speechConfig&amp;quot;&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;quot;multiSpeakerVoiceConfig&amp;quot;&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;quot;speakerVoiceConfigs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&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;quot;speaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Marc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;voiceConfig&amp;quot;&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;quot;prebuiltVoiceConfig&amp;quot;&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;quot;voiceName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fenrir&amp;quot;&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;quot;speaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sophie&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;voiceConfig&amp;quot;&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;quot;prebuiltVoiceConfig&amp;quot;&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;quot;voiceName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Laomedeia&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}}}&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-4-le-graphiste-imagen-3"&gt;Étape 4 : Le Graphiste (Imagen 3)&lt;a class="headerlink" href="#etape-4-le-graphiste-imagen-3" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour rendre le podcast visuel sur les plateformes, il nous faut une couverture. Gemini (le scénariste) a déjà généré un &lt;code&gt;image_prompt&lt;/code&gt; basé sur le contenu de l&amp;rsquo;épisode.&lt;/p&gt;
&lt;p&gt;Nous passons ce prompt à &lt;strong&gt;Imagen 3&lt;/strong&gt; via Vertex AI pour générer une image carrée (1:1), que nous redimensionnons et compressons pour respecter les standards d&amp;rsquo;Apple Podcasts (1400x1400, &amp;lt;512KB).&lt;/p&gt;
&lt;h2 id="etape-5-la-diffusion-fastapi-rss"&gt;Étape 5 : La Diffusion (FastAPI &amp;amp; RSS)&lt;a class="headerlink" href="#etape-5-la-diffusion-fastapi-rss" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Une fois tous les fichiers générés (MP3, JSON, JPG, YAML), ils sont stockés sur &lt;strong&gt;Google Cloud Storage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Le Frontend (une app &lt;strong&gt;FastAPI&lt;/strong&gt; sur &lt;strong&gt;Cloud Run&lt;/strong&gt;) ne stocke rien. Il agit comme une interface de lecture sur le Bucket GCS.&lt;/p&gt;
&lt;h3 id="le-flux-rss-dynamique"&gt;Le Flux RSS Dynamique&lt;a class="headerlink" href="#le-flux-rss-dynamique" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pour s&amp;rsquo;abonner sur Apple Podcast ou Spotify, il faut un flux RSS XML conforme. L&amp;rsquo;application génère ce flux à la volée (&lt;code&gt;/feed.xml&lt;/code&gt;) en lisant les manifestes des épisodes présents dans le bucket.&lt;/p&gt;
&lt;p&gt;Elle gère aussi :
*   La signature d&amp;rsquo;URL (Signed URLs) pour sécuriser l&amp;rsquo;accès aux fichiers audio.
*   Les headers HTTP &lt;code&gt;HEAD&lt;/code&gt; nécessaires pour la validation par les agrégateurs de podcasts.&lt;/p&gt;
&lt;h2 id="etape-6-linfrastructure-as-code-terraform"&gt;Étape 6 : L&amp;rsquo;Infrastructure as Code (Terraform)&lt;a class="headerlink" href="#etape-6-linfrastructure-as-code-terraform" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour éviter de cliquer partout dans la console Google Cloud, toute l&amp;rsquo;infrastructure est définie en &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cela permet de créer des environnements reproductibles (Preprod, Prod) et de gérer les permissions finement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extrait de infra/production/main.tf&lt;/span&gt;
&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;&amp;quot;podcast_generator&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;source&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="s2"&gt;&amp;quot;../modules/podcast-generator&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;project_id&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="nv"&gt;var.project_id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;region&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="nv"&gt;var.region&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;bucket_name&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="s2"&gt;&amp;quot;${var.project_id}-podcast-output&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;  # Configuration du Job et du Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;podcast_model&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="s2"&gt;&amp;quot;gemini-3.0-pro-preview&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;schedule_cron&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="s2"&gt;&amp;quot;0 9 * * 1&amp;quot;&lt;/span&gt;&lt;span class="c1"&gt; # Tous les lundis à 9h&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-7-le-pipeline-cicd-gitlab-ci-cloud-deploy"&gt;Étape 7 : Le Pipeline CI/CD (GitLab CI &amp;amp; Cloud Deploy)&lt;a class="headerlink" href="#etape-7-le-pipeline-cicd-gitlab-ci-cloud-deploy" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Le déploiement est entièrement automatisé via &lt;strong&gt;GitLab CI&lt;/strong&gt; et &lt;strong&gt;Google Cloud Deploy&lt;/strong&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI&lt;/strong&gt; : Construit les images Docker (Generator &amp;amp; Frontend) et les pousse sur Artifact Registry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Deploy&lt;/strong&gt; : Récupère ces images, génère les manifestes Kubernetes (YAML) via un script et déploie le tout sur Cloud Run.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Le pipeline gère la promotion du code de la pré-production vers la production sans intervention manuelle risquée.&lt;/p&gt;
&lt;h2 id="le-cout-finops"&gt;Le Coût (FinOps)&lt;a class="headerlink" href="#le-cout-finops" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;C&amp;rsquo;est la meilleure partie. Comme l&amp;rsquo;architecture est &amp;ldquo;Scale to Zero&amp;rdquo;, je ne paie que lorsque le podcast est généré ou écouté.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coût mensuel réel : ~0,20 €&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Run (Job)&lt;/strong&gt; : Quelques centimes pour les 2-3 minutes de génération par semaine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Run (Service)&lt;/strong&gt; : Gratuit (dans le Free Tier) car peu de trafic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vertex AI&lt;/strong&gt; : Le plus gros poste, mais reste très faible pour une utilisation hebdomadaire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Storage&lt;/strong&gt; : Négligeable pour quelques fichiers MP3.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est une solution extrêmement économique pour un média entièrement automatisé.&lt;/p&gt;
&lt;p&gt;Ce projet démontre la puissance de la &lt;strong&gt;GenAI Multimodale&lt;/strong&gt;. En moins de 500 lignes de code Python, nous avons remplacé toute une chaîne de production médiatique (recherche, écriture, enregistrement, graphisme).&lt;/p&gt;
&lt;p&gt;Le résultat est une veille technologique agréable à écouter, toujours à jour, et qui tourne toute seule chaque lundi matin pendant que je prends mon café. ☕&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Liens utiles :&lt;/strong&gt;
*   🎧 &lt;a href="https://podcast.kapable.info/"&gt;Le Podcast&lt;/a&gt;
*   🛠️ &lt;a href="https://gitlab.com/matgou/podcast-generator"&gt;Code Source (GitLab)&lt;/a&gt;&lt;/p&gt;</content><category term="devops"/><category term="gcp"/><category term="ai"/><category term="genai"/><category term="cloudrun"/><category term="python"/></entry><entry><title>I Automated My Tech Watch with an AI-Generated Podcast (Gemini &amp; Cloud Run)</title><link href="https://www.ops-chronicles.cloud/gcp-podcast-generator.html" rel="alternate"/><published>2026-01-27T00:00:00+01:00</published><updated>2026-01-27T00:00:00+01:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-01-27:/gcp-podcast-generator.html</id><summary type="html">&lt;p&gt;How I used Gemini 3, Vertex AI, and Cloud Run to transform Google Cloud release notes into an engaging weekly podcast. Detailed architecture and code.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-serverless-architecture"&gt;The &amp;ldquo;Serverless&amp;rdquo; Architecture&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-code-open-source"&gt;The Code (Open Source)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-1-the-data-source-bigquery"&gt;Step 1: The Data Source (BigQuery)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-2-the-screenwriter-gemini-3-pro"&gt;Step 2: The Screenwriter (Gemini 3 Pro)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-3-the-audio-studio-gemini-25-flash-tts"&gt;Step 3: The Audio Studio (Gemini 2.5 Flash TTS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-4-the-graphic-designer-imagen-3"&gt;Step 4: The Graphic Designer (Imagen 3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-5-distribution-fastapi-rss"&gt;Step 5: Distribution (FastAPI &amp;amp; RSS)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-dynamic-rss-feed"&gt;The Dynamic RSS Feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-6-infrastructure-as-code-terraform"&gt;Step 6: Infrastructure as Code (Terraform)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-7-the-cicd-pipeline-gitlab-ci-cloud-deploy"&gt;Step 7: The CI/CD Pipeline (GitLab CI &amp;amp; Cloud Deploy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-cost-finops"&gt;The Cost (FinOps)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Don&amp;rsquo;t have time to read the hundreds of release notes published by Google Cloud every week? Neither do I. That&amp;rsquo;s why I created the &lt;strong&gt;GCP News Podcast&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;🎙️ &lt;strong&gt;&lt;a href="https://podcast.kapable.info/"&gt;Listen to the final result here: podcast.kapable.info&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project isn&amp;rsquo;t just a demo, it&amp;rsquo;s a fully automated &amp;ldquo;Serverless&amp;rdquo; production pipeline that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Retrieves&lt;/strong&gt; raw technical news.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scripts&lt;/strong&gt; a dialogue between two virtual experts (Marc and Sophie).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generates&lt;/strong&gt; audio with stunning realism (intonations, laughter, pauses).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creates&lt;/strong&gt; unique album art for each episode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publishes&lt;/strong&gt; everything to the web and podcast platforms (Spotify, etc.).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is how I built this system.&lt;/p&gt;
&lt;h2 id="the-serverless-architecture"&gt;The &amp;ldquo;Serverless&amp;rdquo; Architecture&lt;a class="headerlink" href="#the-serverless-architecture" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The system is designed to cost almost zero when it&amp;rsquo;s not running. It relies on an event-driven architecture and managed services.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Podcast Generator Architecture" src="/images/2026-GCP-Podcast-Generator/architecture.png" /&gt;&lt;/p&gt;
&lt;h3 id="the-code-open-source"&gt;The Code (Open Source)&lt;a class="headerlink" href="#the-code-open-source" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All the code is available on GitLab: &lt;a href="https://gitlab.com/matgou/podcast-generator"&gt;&lt;strong&gt;matgou/podcast-generator&lt;/strong&gt;&lt;/a&gt;.
It is divided into two parts:
*   &lt;code&gt;generator/&lt;/code&gt;: The Python batch script that creates the episode.
*   &lt;code&gt;frontend/&lt;/code&gt;: The FastAPI web interface to listen to episodes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="step-1-the-data-source-bigquery"&gt;Step 1: The Data Source (BigQuery)&lt;a class="headerlink" href="#step-1-the-data-source-bigquery" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unlike many projects that do &amp;ldquo;scraping&amp;rdquo;, here we use a clean source of truth: Google&amp;rsquo;s public BigQuery dataset.&lt;/p&gt;
&lt;p&gt;The script executes a simple SQL query to retrieve everything that has changed in the last 7 days.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    SELECT description, published_at, product_name&lt;/span&gt;
&lt;span class="s2"&gt;    FROM `bigquery-public-data.google_cloud_release_notes.release_notes`&lt;/span&gt;
&lt;span class="s2"&gt;    WHERE published_at &amp;gt; DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)&lt;/span&gt;
&lt;span class="s2"&gt;    ORDER BY published_at DESC&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives us a raw list of text, often very dry and technical. That&amp;rsquo;s where AI comes in.&lt;/p&gt;
&lt;h2 id="step-2-the-screenwriter-gemini-3-pro"&gt;Step 2: The Screenwriter (Gemini 3 Pro)&lt;a class="headerlink" href="#step-2-the-screenwriter-gemini-3-pro" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We inject these raw notes into &lt;strong&gt;Gemini 3 Pro&lt;/strong&gt; with a very specific system prompt (&lt;code&gt;prompt_template.txt&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The goal is not to summarize, but to &lt;strong&gt;script&lt;/strong&gt;.
*   &lt;strong&gt;Marc&lt;/strong&gt; is the technical expert, precise and factual.
*   &lt;strong&gt;Sophie&lt;/strong&gt; is the curious host, asking the questions the listener would ask.&lt;/p&gt;
&lt;p&gt;One of the key features used here is &lt;strong&gt;Grounding&lt;/strong&gt; with Google Search. This allows Gemini to verify facts and add links to official documentation in the episode metadata.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example of expected structured JSON output from Gemini&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cloud Run speeds up and BigQuery gets safer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This week, Marc and Sophie discuss the new instances...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;script&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sophie: Hello everyone! ... Marc: Exactly, and it&amp;#39;s major...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;image_prompt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;An abstract representation of a fast server race in a neon city...&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-3-the-audio-studio-gemini-25-flash-tts"&gt;Step 3: The Audio Studio (Gemini 2.5 Flash TTS)&lt;a class="headerlink" href="#step-3-the-audio-studio-gemini-25-flash-tts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the most impressive part. We don&amp;rsquo;t use the old robotic &amp;ldquo;Text-to-Speech&amp;rdquo;. We use the &lt;strong&gt;Gemini 2.5 Flash TTS&lt;/strong&gt; model (still in preview), capable of generating multiple speakers in the same audio stream.&lt;/p&gt;
&lt;p&gt;The prompt defines the voice personalities:
*   Marc uses the &lt;code&gt;Fenrir&lt;/code&gt; voice.
*   Sophie uses the &lt;code&gt;Laomedeia&lt;/code&gt; voice.&lt;/p&gt;
&lt;p&gt;The Python script directly calls the Vertex AI REST API to generate the audio.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified API call extract&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;contents&amp;quot;&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;quot;parts&amp;quot;&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;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;}]}],&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;generationConfig&amp;quot;&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;quot;speechConfig&amp;quot;&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;quot;multiSpeakerVoiceConfig&amp;quot;&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;quot;speakerVoiceConfigs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&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;quot;speaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Marc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;voiceConfig&amp;quot;&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;quot;prebuiltVoiceConfig&amp;quot;&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;quot;voiceName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fenrir&amp;quot;&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;quot;speaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sophie&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;voiceConfig&amp;quot;&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;quot;prebuiltVoiceConfig&amp;quot;&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;quot;voiceName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Laomedeia&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}}}&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-4-the-graphic-designer-imagen-3"&gt;Step 4: The Graphic Designer (Imagen 3)&lt;a class="headerlink" href="#step-4-the-graphic-designer-imagen-3" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make the podcast visual on platforms, we need a cover. Gemini (the screenwriter) has already generated an &lt;code&gt;image_prompt&lt;/code&gt; based on the episode content.&lt;/p&gt;
&lt;p&gt;We pass this prompt to &lt;strong&gt;Imagen 3&lt;/strong&gt; via Vertex AI to generate a square image (1:1), which we resize and compress to respect Apple Podcasts standards (1400x1400, &amp;lt;512KB).&lt;/p&gt;
&lt;h2 id="step-5-distribution-fastapi-rss"&gt;Step 5: Distribution (FastAPI &amp;amp; RSS)&lt;a class="headerlink" href="#step-5-distribution-fastapi-rss" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once all files are generated (MP3, JSON, JPG, YAML), they are stored on &lt;strong&gt;Google Cloud Storage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The Frontend (a &lt;strong&gt;FastAPI&lt;/strong&gt; app on &lt;strong&gt;Cloud Run&lt;/strong&gt;) stores nothing. It acts as a playback interface on the GCS Bucket.&lt;/p&gt;
&lt;h3 id="the-dynamic-rss-feed"&gt;The Dynamic RSS Feed&lt;a class="headerlink" href="#the-dynamic-rss-feed" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To subscribe on Apple Podcast or Spotify, a compliant XML RSS feed is required. The application generates this feed on the fly (&lt;code&gt;/feed.xml&lt;/code&gt;) by reading the manifests of episodes present in the bucket.&lt;/p&gt;
&lt;p&gt;It also handles:
*   URL signing (Signed URLs) to secure access to audio files.
*   &lt;code&gt;HEAD&lt;/code&gt; HTTP headers necessary for validation by podcast aggregators.&lt;/p&gt;
&lt;h2 id="step-6-infrastructure-as-code-terraform"&gt;Step 6: Infrastructure as Code (Terraform)&lt;a class="headerlink" href="#step-6-infrastructure-as-code-terraform" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To avoid clicking everywhere in the Google Cloud console, the entire infrastructure is defined in &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This allows creating reproducible environments (Preprod, Prod) and managing permissions finely.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extract from infra/production/main.tf&lt;/span&gt;
&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;&amp;quot;podcast_generator&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;source&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="s2"&gt;&amp;quot;../modules/podcast-generator&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;project_id&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="nv"&gt;var.project_id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;region&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="nv"&gt;var.region&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;bucket_name&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="s2"&gt;&amp;quot;${var.project_id}-podcast-output&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;  # Job and Service Configuration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;podcast_model&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="s2"&gt;&amp;quot;gemini-3.0-pro-preview&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;schedule_cron&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="s2"&gt;&amp;quot;0 9 * * 1&amp;quot;&lt;/span&gt;&lt;span class="c1"&gt; # Every Monday at 9am&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-7-the-cicd-pipeline-gitlab-ci-cloud-deploy"&gt;Step 7: The CI/CD Pipeline (GitLab CI &amp;amp; Cloud Deploy)&lt;a class="headerlink" href="#step-7-the-cicd-pipeline-gitlab-ci-cloud-deploy" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Deployment is fully automated via &lt;strong&gt;GitLab CI&lt;/strong&gt; and &lt;strong&gt;Google Cloud Deploy&lt;/strong&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI&lt;/strong&gt;: Builds Docker images (Generator &amp;amp; Frontend) and pushes them to Artifact Registry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Deploy&lt;/strong&gt;: Retrieves these images, generates Kubernetes manifests (YAML) via a script, and deploys everything to Cloud Run.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The pipeline handles code promotion from pre-production to production without risky manual intervention.&lt;/p&gt;
&lt;h2 id="the-cost-finops"&gt;The Cost (FinOps)&lt;a class="headerlink" href="#the-cost-finops" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the best part. Since the architecture is &amp;ldquo;Scale to Zero&amp;rdquo;, I only pay when the podcast is generated or listened to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actual monthly cost: ~€0.20&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Run (Job)&lt;/strong&gt;: A few cents for the 2-3 minutes of generation per week.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Run (Service)&lt;/strong&gt;: Free (in the Free Tier) because low traffic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vertex AI&lt;/strong&gt;: The biggest item, but remains very low for weekly use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Storage&lt;/strong&gt;: Negligible for a few MP3 files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s an extremely economical solution for fully automated media.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This project demonstrates the power of &lt;strong&gt;Multimodal GenAI&lt;/strong&gt;. In less than 500 lines of Python code, we replaced an entire media production chain (research, writing, recording, graphic design).&lt;/p&gt;
&lt;p&gt;The result is a tech watch that is pleasant to listen to, always up to date, and runs by itself every Monday morning while I have my coffee. ☕&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Useful links:&lt;/strong&gt;
*   🎧 &lt;a href="https://podcast.kapable.info/"&gt;The Podcast&lt;/a&gt;
*   🛠️ &lt;a href="https://gitlab.com/matgou/podcast-generator"&gt;Source Code (GitLab)&lt;/a&gt;&lt;/p&gt;</content><category term="devops"/><category term="gcp"/><category term="ai"/><category term="genai"/><category term="cloudrun"/><category term="python"/></entry><entry><title>InkFlowAI : De la note manuscrite au compte-rendu structuré (Grâce à l'IA)</title><link href="https://www.ops-chronicles.cloud/fr/inkflowai-architecture.html" rel="alternate"/><published>2026-01-20T00:00:00+01:00</published><updated>2026-01-20T00:00:00+01:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-01-20:/fr/inkflowai-architecture.html</id><summary type="html">&lt;p&gt;InkFlowAI : De la note manuscrite au compte-rendu structuré (Grâce à l&amp;rsquo;IA)&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#le-probleme"&gt;Le Problème&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#la-solution-inkflowai"&gt;La Solution : InkFlowAI&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#workflow-utilisateur"&gt;Workflow Utilisateur&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#architecture-technique-sous-le-capot"&gt;Architecture Technique (Sous le capot)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#diagramme-de-flux-de-donnees"&gt;Diagramme de Flux de Données&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etapes-cles"&gt;Étapes Clés&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Vous prenez des notes sur votre &lt;strong&gt;Kindle Scribe&lt;/strong&gt; (ou toute autre tablette e-ink) et vous vous retrouvez avec des PDFs manuscrits difficiles à exploiter ? Voici comment &lt;strong&gt;InkFlowAI&lt;/strong&gt; automatise la transformation de vos gribouillages en comptes-rendus professionnels, diagrammes inclus.&lt;/p&gt;
&lt;h2 id="le-probleme"&gt;Le Problème&lt;a class="headerlink" href="#le-probleme" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La prise de note manuscrite est fluide et naturelle, mais le partage et l&amp;rsquo;archivage sont pénibles. Retaper ses notes prend du temps. Rédiger des schémas sur ordinateur est laborieux.&lt;/p&gt;
&lt;h2 id="la-solution-inkflowai"&gt;La Solution : InkFlowAI&lt;a class="headerlink" href="#la-solution-inkflowai" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Un système &lt;strong&gt;Serverless&lt;/strong&gt; et &lt;strong&gt;Réactif&lt;/strong&gt; hébergé sur Google Cloud, qui agit comme un assistant personnel.&lt;/p&gt;
&lt;h3 id="workflow-utilisateur"&gt;Workflow Utilisateur&lt;a class="headerlink" href="#workflow-utilisateur" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Vous terminez votre réunion.&lt;/li&gt;
&lt;li&gt;Vous cliquez sur &amp;ldquo;Partager par email&amp;rdquo; sur votre Kindle.&lt;/li&gt;
&lt;li&gt;30 secondes plus tard, vous recevez un email en réponse contenant :&lt;ul&gt;
&lt;li&gt;Un &lt;strong&gt;résumé structuré&lt;/strong&gt; (Markdown) de la réunion.&lt;/li&gt;
&lt;li&gt;La liste des &lt;strong&gt;actions&lt;/strong&gt; et décisions.&lt;/li&gt;
&lt;li&gt;Vos &lt;strong&gt;schémas manuscrits convertis&lt;/strong&gt; en diagrammes propres (Mermaid/PNG).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2 id="architecture-technique-sous-le-capot"&gt;Architecture Technique (Sous le capot)&lt;a class="headerlink" href="#architecture-technique-sous-le-capot" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nous avons récemment migré d&amp;rsquo;une architecture de &lt;em&gt;polling&lt;/em&gt; (vérification périodique) vers une architecture &lt;strong&gt;événementielle&lt;/strong&gt; (Push) pour une réactivité temps réel.&lt;/p&gt;
&lt;h3 id="diagramme-de-flux-de-donnees"&gt;Diagramme de Flux de Données&lt;a class="headerlink" href="#diagramme-de-flux-de-donnees" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="image" src="/images/2026-InkflowAI-Architecture/2026-InkflowAI-Architecture.jpg" /&gt;&lt;/p&gt;
&lt;h3 id="etapes-cles"&gt;Étapes Clés&lt;a class="headerlink" href="#etapes-cles" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Réception Réactive (Gmail + Pub/Sub)&lt;/strong&gt; :
    Au lieu de demander toutes les minutes &amp;ldquo;J&amp;rsquo;ai du courrier ?&amp;rdquo;, notre service dort. Dès qu&amp;rsquo;un email arrive, Gmail notifie &lt;strong&gt;Cloud Pub/Sub&lt;/strong&gt;, qui réveille instantanément notre container sur &lt;strong&gt;Cloud Run&lt;/strong&gt;. Cela réduit les coûts et la latence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Analyse Multimodale (Gemini Pro Generation)&lt;/strong&gt; :
    Le cœur du système. Nous envoyons le PDF (images des pages manuscrites) directement à &lt;strong&gt;Gemini 3 Pro&lt;/strong&gt; via Vertex AI.
    Le prompt système est conçu pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Déchiffrer l&amp;rsquo;écriture manuscrite (OCR avancé).&lt;/li&gt;
&lt;li&gt;Comprendre le contexte (brainstorming, décision, TOD).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Détecter les dessins&lt;/strong&gt; et générer le code &lt;code&gt;Mermaid.js&lt;/code&gt; correspondant.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rendu de Diagrammes (&amp;ldquo;NanoBanana&amp;rdquo;)&lt;/strong&gt; :
    Si vous avez dessiné un carré fléché vers un cercle, Gemini le traduit en code. InkFlowAI compile ce code en une image PNG nette, prête à être insérée dans le rapport final.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stockage &amp;amp; Notification&lt;/strong&gt; :
    Tout est archivé sur &lt;strong&gt;Cloud Storage&lt;/strong&gt; (S3-compatible) pour la pérennité. Un email formaté est renvoyé à l&amp;rsquo;expéditeur.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;InkFlowAI montre comment l&amp;rsquo;IA générative (multimodale) peut combler le fossé entre le monde analogique (papier/stylo) et le monde numérique (Jira/Wiki/Docs), en automatisant la partie la plus ennuyeuse du travail : la mise au propre.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Le code est Open Source et déployable sur votre propre projet GCP : &lt;a href="https://gitlab.com/matgou/inkflowai"&gt;https://gitlab.com/matgou/inkflowai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="ai"/></entry><entry><title>InkFlowAI: From Handwritten Notes to Structured Reports (Powered by AI)</title><link href="https://www.ops-chronicles.cloud/inkflowai-architecture.html" rel="alternate"/><published>2026-01-20T00:00:00+01:00</published><updated>2026-01-20T00:00:00+01:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-01-20:/inkflowai-architecture.html</id><summary type="html">&lt;p&gt;InkFlowAI: From Handwritten Notes to Structured Reports (Powered by AI)&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-solution-inkflowai"&gt;The Solution: InkFlowAI&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#user-workflow"&gt;User Workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#technical-architecture-under-the-hood"&gt;Technical Architecture (Under the Hood)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#data-flow-diagram"&gt;Data Flow Diagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#key-components"&gt;Key Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Do you take notes on your &lt;strong&gt;Kindle Scribe&lt;/strong&gt; (or any e-ink tablet) only to end up with handwritten PDFs that sit in a digital graveyard? Here is how &lt;strong&gt;InkFlowAI&lt;/strong&gt; automates the transformation of your scribbles into professional meeting minutes, diagrams included.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Handwriting is fluid and natural for thinking, but terrible for sharing and archiving. Typing up notes takes time. Redrawing whiteboard sketches on a computer is tedious.&lt;/p&gt;
&lt;h2 id="the-solution-inkflowai"&gt;The Solution: InkFlowAI&lt;a class="headerlink" href="#the-solution-inkflowai" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Serverless&lt;/strong&gt; and &lt;strong&gt;Reactive&lt;/strong&gt; system hosted on Google Cloud that acts as your personal secretary.&lt;/p&gt;
&lt;h3 id="user-workflow"&gt;User Workflow&lt;a class="headerlink" href="#user-workflow" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;You finish your meeting.&lt;/li&gt;
&lt;li&gt;You click &amp;ldquo;Share via Email&amp;rdquo; on your Kindle.&lt;/li&gt;
&lt;li&gt;30 seconds later, you receive a reply email containing:&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;structured summary&lt;/strong&gt; (Markdown) of the meeting.&lt;/li&gt;
&lt;li&gt;A list of &lt;strong&gt;action items&lt;/strong&gt; and decisions.&lt;/li&gt;
&lt;li&gt;Your &lt;strong&gt;handdrawn sketches converted&lt;/strong&gt; into clean technical diagrams (Mermaid/PNG).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2 id="technical-architecture-under-the-hood"&gt;Technical Architecture (Under the Hood)&lt;a class="headerlink" href="#technical-architecture-under-the-hood" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We recently migrated from a &lt;em&gt;polling&lt;/em&gt; architecture to an &lt;strong&gt;event-driven&lt;/strong&gt; (Push) architecture for real-time responsiveness.&lt;/p&gt;
&lt;h3 id="data-flow-diagram"&gt;Data Flow Diagram&lt;a class="headerlink" href="#data-flow-diagram" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="image" src="/images/2026-InkflowAI-Architecture/2026-InkflowAI-Architecture.jpg" /&gt;&lt;/p&gt;
&lt;h3 id="key-components"&gt;Key Components&lt;a class="headerlink" href="#key-components" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reactive Ingestion (Gmail + Pub/Sub)&lt;/strong&gt;:
    Instead of asking &amp;ldquo;Do I have mail?&amp;rdquo; every minute, our service sleeps. As soon as an email arrives, Gmail notifies &lt;strong&gt;Cloud Pub/Sub&lt;/strong&gt;, which instantly wakes up our container on &lt;strong&gt;Cloud Run&lt;/strong&gt;. This minimizes cost and latency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multimodal Analysis (Gemini Pro Generative AI)&lt;/strong&gt;:
    The core of the system. We send the raw PDF (images of handwritten pages) directly to &lt;strong&gt;Gemini 3 Pro&lt;/strong&gt; via Vertex AI.
    The system prompt is engineered to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decipher handwriting (Advanced OCR).&lt;/li&gt;
&lt;li&gt;Understand context (brainstorming, decisions, TODOs).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detect drawings&lt;/strong&gt; and generate corresponding &lt;code&gt;Mermaid.js&lt;/code&gt; code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Diagram Rendering (&amp;ldquo;NanoBanana&amp;rdquo;)&lt;/strong&gt;:
    If you drew a box pointing to a circle, Gemini translates it into code. InkFlowAI compiles this code into a sharp PNG image, ready to be embedded in the final report.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage &amp;amp; Notification&lt;/strong&gt;:
    Everything is archived on &lt;strong&gt;Cloud Storage&lt;/strong&gt; (S3-compatible) for longevity. A formatted email is sent back to the sender.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;InkFlowAI demonstrates how Generative AI (multimodal) can bridge the gap between the analog world (pen/paper) and the digital world (Jira/Wiki/Docs), automating the most boring part of work: cleaning up notes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The code is Open Source and deployable on your own GCP project: &lt;a href="https://gitlab.com/matgou/inkflowai"&gt;https://gitlab.com/matgou/inkflowai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="ai"/></entry><entry><title>Votre propre Agent DevOps : L'avenir de l'observabilité</title><link href="https://www.ops-chronicles.cloud/fr/own-devops-agents.html" rel="alternate"/><published>2025-05-28T00:00:00+02:00</published><updated>2025-05-28T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2025-05-28:/fr/own-devops-agents.html</id><summary type="html">&lt;p&gt;Comment construire et utiliser un agent Kubernetes basé sur l&amp;rsquo;ADK (Python).&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#demarrage-rapide-du-developpement-dagent-adk"&gt;Démarrage rapide du développement d&amp;rsquo;agent ADK&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prerequis"&gt;Prérequis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#structure-de-base"&gt;Structure de base&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#donner-des-instructions-a-lagent"&gt;Donner des instructions à l&amp;rsquo;agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tester-lagent"&gt;Tester l&amp;rsquo;agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#donner-des-outils-a-mon-agent"&gt;Donner des outils à mon agent&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#et-maintenant-un-vrai-test"&gt;Et maintenant un vrai test&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#integration-kubernetes"&gt;Intégration Kubernetes&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#construire-un-conteneur-docker"&gt;Construire un conteneur docker :&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#dans-kubernetes_admindockerfile"&gt;Dans kubernetes_admin/Dockerfile&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#ajouter-rbac"&gt;Ajouter RBAC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ajouter-un-secret-pour-les-identifiants-google"&gt;Ajouter un secret pour les identifiants google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#deployer-le-service"&gt;Déployer le service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tests-et-experimentations"&gt;Tests et expérimentations&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#diagnostiquer-oomkill"&gt;Diagnostiquer OOMKill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion-et-prospective"&gt;Conclusion et prospective&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Google a récemment publié l&amp;rsquo;Agent Development Kit (ADK), un framework conçu pour simplifier le développement et le déploiement d&amp;rsquo;agents pilotés par l&amp;rsquo;IA. Ce framework est remarquablement convivial. Dans ce contexte, un agent est une API interactive qui exploite un grand modèle de langage (LLM), comme Gemini, et qui est renforcée par vos scripts personnalisés pour générer des réponses basées sur leur sortie.&lt;/p&gt;
&lt;p&gt;Dans le monde DevSecOps, nous sommes déjà équipés d&amp;rsquo;une multitude d&amp;rsquo;outils et de scripts (CLI, API, et plus). Maintenant, imaginez équiper une IA de toutes ces capacités.&lt;/p&gt;
&lt;p&gt;En tant qu&amp;rsquo;ingénieurs DevOps, nous nous retrouvons souvent à lire manuellement des journaux, à décrire des ressources pour trouver des statuts et à surveiller des événements. Et si chacun de nous avait un assistant dédié pour effectuer ces étapes de diagnostic initiales pour vous, en pointant directement vers les informations pertinentes ?&lt;/p&gt;
&lt;p&gt;Cet article explore comment construire et utiliser un agent Kubernetes ADK (basé sur Python). Cet agent agira comme un outil de diagnostic, appelable via chat ou une API, pour rassembler rapidement des informations cruciales sur notre cluster et fournir des aperçus de diagnostic initiaux.&lt;/p&gt;
&lt;p&gt;Testez-le ; personnellement, je suis convaincu : c&amp;rsquo;est l&amp;rsquo;avenir de l&amp;rsquo;observabilité.&lt;/p&gt;
&lt;h2 id="demarrage-rapide-du-developpement-dagent-adk"&gt;Démarrage rapide du développement d&amp;rsquo;agent ADK&lt;a class="headerlink" href="#demarrage-rapide-du-developpement-dagent-adk" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Commencer avec l&amp;rsquo;Agent Development Kit (ADK) est simple. Cette section vous guidera sur la façon de configurer et de créer un agent (très) basique.&lt;/p&gt;
&lt;h3 id="prerequis"&gt;Prérequis&lt;a class="headerlink" href="#prerequis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Avant de commencer, assurez-vous d&amp;rsquo;avoir &lt;strong&gt;Python&lt;/strong&gt; (y compris &lt;code&gt;venv&lt;/code&gt; pour les environnements virtuels et &lt;code&gt;pip&lt;/code&gt; pour l&amp;rsquo;installation de paquets) installé.&lt;/li&gt;
&lt;li&gt;Vous aurez également besoin d&amp;rsquo;un projet Google Cloud Platform (GCP) configuré pour utiliser Vertex AI afin d&amp;rsquo;appeler le modèle Gemini.&lt;/li&gt;
&lt;li&gt;Créez un dossier pour votre projet :
    &lt;code&gt;bash
    mkdir kubernetes-admin &amp;amp;&amp;amp; cd kubernetes-admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Initialisez un environnement virtuel :
    &lt;code&gt;bash
    python -m venv venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Activez l&amp;rsquo;environnement virtuel :&lt;ul&gt;
&lt;li&gt;Sur macOS et Linux :
    &lt;code&gt;bash
    source venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sur Windows :
    &lt;code&gt;bash
    .\venv\Scripts\activate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Installez poetry : &lt;code&gt;pip install poetry&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="structure-de-base"&gt;Structure de base&lt;a class="headerlink" href="#structure-de-base" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Créez les fichiers et répertoires suivants dans le répertoire &lt;code&gt;kubernetes-admin&lt;/code&gt; que vous avez créé plus tôt :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Contenant les instructions de l&amp;#39;agent : prompt initial et paramètres&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Un fichier init par défaut&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Pour héberger les futures interfaces d&amp;#39;outils&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Un fichier init par défaut pour le paquet tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Naviguez dans le répertoire interne &lt;code&gt;kubernetes_admin&lt;/code&gt; (c&amp;rsquo;est là que résidera votre paquet Python) :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubernetes_admin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Initialisez le projet Poetry dans ce répertoire &lt;code&gt;kubernetes_admin&lt;/code&gt; :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Suivez&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pouvez&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accepter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valeurs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;par&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;défaut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plupart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mais&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assurez&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paquet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`kubernetes_admin`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Ajoutez la dépendance ADK à votre projet :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;google-ai-agent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;*(Note : Le nom du paquet est &lt;span class="sb"&gt;`google-ai-agent`&lt;/span&gt;, pas &lt;span class="sb"&gt;`google-adk`&lt;/span&gt;)*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Remplissez le fichier &lt;code&gt;kubernetes_admin/__init__.py&lt;/code&gt;. Cela rend le module &lt;code&gt;agent&lt;/code&gt; accessible.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dans kubernetes_admin/kubernetes_admin/__init__.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Le fichier &lt;code&gt;kubernetes_admin/tools/__init__.py&lt;/code&gt; peut rester vide pour le moment.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dans kubernetes_admin/kubernetes_admin/tools/&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Créez un fichier &lt;code&gt;.env&lt;/code&gt; vide dans le répertoire &lt;code&gt;kubernetes_admin/kubernetes_admin/&lt;/code&gt;. Cela pourra être utilisé plus tard pour les configurations spécifiques à l&amp;rsquo;environnement si nécessaire.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dans kubernetes_admin/kubernetes_admin/&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;.env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Naviguez vers le répertoire racine du projet &lt;code&gt;kubernetes-admin&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="donner-des-instructions-a-lagent"&gt;Donner des instructions à l&amp;rsquo;agent&lt;a class="headerlink" href="#donner-des-instructions-a-lagent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Le fichier &lt;code&gt;agent.py&lt;/code&gt; (situé dans &lt;code&gt;kubernetes-admin/kubernetes_admin/agent.py&lt;/code&gt;) est crucial dans l&amp;rsquo;Agent Development Kit (ADK). Il définit le prompt initial et les instructions de base pour l&amp;rsquo;agent IA. Plus tard, il sera étendu pour inclure les outils spécifiques que l&amp;rsquo;agent peut utiliser et la logique sur la façon dont il doit les utiliser.&lt;/p&gt;
&lt;p&gt;Vous pouvez utiliser l&amp;rsquo;IA pour aider à générer le prompt initial et les instructions, ou les écrire vous-même.&lt;/p&gt;
&lt;p&gt;Créez/éditez &lt;code&gt;kubernetes-admin/kubernetes_admin/agent.py&lt;/code&gt; avec le contenu suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dans kubernetes_admin/kubernetes_admin/agent.py&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Kubernetes_Admin: Agent pour interagir avec et gérer un cluster Kubernetes.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;google.adk.agents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gemini-2.5-pro-preview-05-06&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;You are Kubernetes Admin, an AI assistant designed to help users manage and understand their Kubernetes (K8s) clusters.&lt;/span&gt;
&lt;span class="s2"&gt;Your primary goal is to help users by:&lt;/span&gt;
&lt;span class="s2"&gt;- Answering questions about their Kubernetes cluster.&lt;/span&gt;
&lt;span class="s2"&gt;- Retrieving information about resources like pods, nodes, services, deployments, namespaces, etc. using the available tools.&lt;/span&gt;
&lt;span class="s2"&gt;- Explaining Kubernetes concepts relevant to their queries.&lt;/span&gt;
&lt;span class="s2"&gt;- Assisting in understanding resource status and configurations.&lt;/span&gt;

&lt;span class="s2"&gt;Always strive to be clear, concise, and helpful in your responses.&lt;/span&gt;

&lt;span class="s2"&gt;Example interactions:&lt;/span&gt;
&lt;span class="s2"&gt;- User: &amp;quot;How many pods are running in the &amp;#39;default&amp;#39; namespace?&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- You: (After using a tool to get pod count) &amp;quot;There are X pods currently running in the &amp;#39;default&amp;#39; namespace.&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- User: &amp;quot;What&amp;#39;s the status of the pod named &amp;#39;my-app-pod-123&amp;#39;?&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- You: (After using a tool to get pod status) &amp;quot;The pod &amp;#39;my-app-pod-123&amp;#39; is currently in a &amp;#39;Running&amp;#39; state.&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes_admin_agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;An AI agent that helps users explore and understand their Kubernetes cluster by answering questions and retrieving information about resources.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="c1"&gt;# Passer l&amp;#39;instance FunctionTool&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="tester-lagent"&gt;Tester l&amp;rsquo;agent&lt;a class="headerlink" href="#tester-lagent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Avant de lancer le test, vous devez configurer un environnement cloud :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_GENAI_USE_VERTEXAI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Complétez&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;votre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;projet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCP&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;central1&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_STORAGE_BUCKET&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;adk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;-$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openssl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# générer un nom de bucket unique&lt;/span&gt;
&lt;span class="n"&gt;gcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;
&lt;span class="n"&gt;gcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//$&lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_STORAGE_BUCKET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Maintenant vous pouvez lancer le test avec la commande suivante :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adk run .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si tout est ok, vous verrez l&amp;rsquo;invite suivante :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agents_log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="mf"&gt;.20250528&lt;/span&gt;&lt;span class="n"&gt;_200909&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;
&lt;span class="k"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agents_log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;
&lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;managing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;understanding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;How&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;namespaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;across&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;namespaces&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="donner-des-outils-a-mon-agent"&gt;Donner des outils à mon agent&lt;a class="headerlink" href="#donner-des-outils-a-mon-agent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;D&amp;rsquo;abord, écrivons une fonction Python pour interagir avec &lt;code&gt;kubectl&lt;/code&gt; et l&amp;rsquo;envelopper comme un outil ADK.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Créez/éditez &lt;code&gt;kubernetes-admin/kubernetes_admin/tools/kubectl_tool.py&lt;/code&gt; avec le contenu suivant :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dans kubernetes_admin/kubernetes_admin/tools/kubectl_tool.py&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Tool for executing kubectl get commands.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt; &lt;span class="c1"&gt;# For actual kubectl calls&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;google.adk.tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run_kubectl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pods&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Runs a kubectl command with the specified verb, resource type, optional resource name, and namespace.&lt;/span&gt;
&lt;span class="sd"&gt;    Exemples:&lt;/span&gt;
&lt;span class="sd"&gt;        To get configuration of a specific pod use verbs: &amp;quot;describe&amp;quot;, namespace: &amp;quot;all&amp;quot;, resource_type: &amp;quot;pods&amp;quot;, resource_name: &amp;quot;&amp;lt;custom pod name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        To get logs of a specific pod use verbs: &amp;quot;logs&amp;quot;, namespace: &amp;quot;&amp;lt;namespace&amp;gt;&amp;quot;, resource_type: &amp;quot;&amp;quot;, resource_name: &amp;quot;&amp;lt;custom pod name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        To list pods in all namespace use verbs: &amp;quot;get&amp;quot;, namespace: &amp;quot;all&amp;quot;, resource_type: &amp;quot;pods&amp;quot;, resource_name: &amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="sd"&gt;    Args:&lt;/span&gt;
&lt;span class="sd"&gt;        verbs: The kubectl verb to use (e.g., &amp;#39;get&amp;#39;, &amp;#39;describe&amp;#39;, &amp;#39;logs&amp;#39;, &amp;#39;top&amp;#39;).&lt;/span&gt;
&lt;span class="sd"&gt;        resource_type: The type of Kubernetes resource (e.g., &amp;#39;pods&amp;#39;, &amp;#39;nodes&amp;#39;, &amp;#39;services&amp;#39;, &amp;#39;deployments&amp;#39;).&lt;/span&gt;
&lt;span class="sd"&gt;        resource_name: Optional. The specific name of the resource. If None, lists all resources of the type.&lt;/span&gt;
&lt;span class="sd"&gt;        namespace: Optional. The Kubernetes namespace. Defaults to &amp;#39;default&amp;#39;.&lt;/span&gt;

&lt;span class="sd"&gt;    Returns:&lt;/span&gt;
&lt;span class="sd"&gt;        A string containing the output of the kubectl command or an error message.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubectl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resource_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;all&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--all-namespaces&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Error: kubectl command not found. Please ensure kubectl is installed and in your PATH.&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error executing kubectl command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutExpired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Error: kubectl command timed out.&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;An unexpected error occurred: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;kubectl_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_kubectl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Maintenant, mettons à jour kubernetes-admin/kubernetes_admin/agent.py pour inclure ce nouvel outil.
Importez l&amp;rsquo;outil :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.tools.kubectl_tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;kubectl_tool&lt;/span&gt; &lt;span class="c1"&gt;# Import the FunctionTool instance&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes_admin_agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;An AI agent that helps users explore and understand their Kubernetes cluster by answering questions and retrieving information about resources.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubectl_tool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# Pass the FunctionTool instance&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Mettez à jour les instructions de l&amp;rsquo;agent dans KUBERNETES_ADMIN_INSTRUCTION dans kubernetes-admin/kubernetes_admin/agent.py pour informer le LLM du nouvel outil et comment l&amp;rsquo;utiliser :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;...&lt;/span&gt;
&lt;span class="s2"&gt;...&lt;/span&gt;
&lt;span class="s2"&gt;You have a tool named &amp;#39;run_kubectl_command&amp;#39; to fetch information from the Kubernetes cluster by executing kubectl commands.&lt;/span&gt;
&lt;span class="s2"&gt;Tool &amp;#39;run_kubectl_command&amp;#39; arguments:&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;verbs&amp;#39;: (string, required) The kubectl verb to use (e.g., &amp;#39;get&amp;#39;, &amp;#39;describe&amp;#39;, &amp;#39;logs&amp;#39;, &amp;#39;top&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;resource_type&amp;#39;: (string, required) The type of Kubernetes resource (e.g., &amp;#39;pods&amp;#39;, &amp;#39;nodes&amp;#39;, &amp;#39;services&amp;#39;, &amp;#39;deployments&amp;#39;, &amp;#39;namespaces&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;resource_name&amp;#39;: (string, optional) The specific name of the resource. If not provided, all resources of the given type in the namespace will be listed.&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;namespace&amp;#39;: (string, optional, defaults to &amp;#39;default&amp;#39;) The Kubernetes namespace to query.&lt;/span&gt;

&lt;span class="s2"&gt;When a user asks for information that requires querying the cluster (e.g., &amp;quot;list pods&amp;quot;, &amp;quot;get node status&amp;quot;, &amp;quot;describe service my-service&amp;quot;, &amp;quot;get logs for pod xyz&amp;quot;), you MUST use the &amp;#39;run_kubectl_command&amp;#39; tool.&lt;/span&gt;
&lt;span class="s2"&gt;Clearly state that you are using the tool, what you are querying for, and then present the information returned by the tool.&lt;/span&gt;
&lt;span class="s2"&gt;Determine the correct &amp;#39;verbs&amp;#39; argument based on the user&amp;#39;s request (e.g., &amp;quot;list&amp;quot; or &amp;quot;how many&amp;quot; implies &amp;#39;get&amp;#39;, &amp;quot;what&amp;#39;s the status&amp;quot; implies &amp;#39;get&amp;#39;, &amp;quot;describe&amp;quot; implies &amp;#39;describe&amp;#39;, &amp;quot;show logs&amp;quot; implies &amp;#39;logs&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="et-maintenant-un-vrai-test"&gt;Et maintenant un vrai test&lt;a class="headerlink" href="#et-maintenant-un-vrai-test" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Maintenant, effectuons un vrai test pour voir si notre agent utilise &lt;code&gt;kubectl&lt;/code&gt; comme prévu. (Assurez-vous d&amp;rsquo;avoir un fichier &lt;code&gt;kubeconfig&lt;/code&gt; valide et d&amp;rsquo;être connecté à un cluster Kubernetes pour ce test.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;adk&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;.
...
&lt;span class="o"&gt;[&lt;/span&gt;user&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;how&lt;span class="w"&gt; &lt;/span&gt;many&lt;span class="w"&gt; &lt;/span&gt;pod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces
&lt;span class="o"&gt;[&lt;/span&gt;kubernetes_admin_agent&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Okay,&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;that.&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;run_kubectl&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;count&lt;span class="w"&gt; &lt;/span&gt;them&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you.
&lt;span class="o"&gt;[&lt;/span&gt;kubernetes_admin_agent&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;retrieved&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;across&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces.&lt;span class="w"&gt; &lt;/span&gt;There&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;lot&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;them!
Based&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;output,&lt;span class="w"&gt; &lt;/span&gt;there&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;119&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;across&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="integration-kubernetes"&gt;Intégration Kubernetes&lt;a class="headerlink" href="#integration-kubernetes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nous allons maintenant déployer notre agent dans un cluster Kubernetes, pour donner la possibilité à d&amp;rsquo;autres utilisateurs de l&amp;rsquo;utiliser.&lt;/p&gt;
&lt;h3 id="construire-un-conteneur-docker"&gt;Construire un conteneur docker :&lt;a class="headerlink" href="#construire-un-conteneur-docker" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Voici un Dockerfile pour construire une image de conteneur pour votre agent Python. Ce Dockerfile :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Utilise une image de base Python slim.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installe &lt;code&gt;kubectl&lt;/code&gt; (car l&amp;rsquo;outil de votre agent l&amp;rsquo;utilise) et Poetry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure un utilisateur non-root pour une meilleure sécurité.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installe les dépendances de votre projet en utilisant Poetry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Définit la commande par défaut pour exécuter votre agent ADK en utilisant son serveur HTTP intégré.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;```Dockerfile&lt;/p&gt;
&lt;h1 id="dans-kubernetes_admindockerfile"&gt;Dans kubernetes_admin/Dockerfile&lt;a class="headerlink" href="#dans-kubernetes_admindockerfile" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;FROM python:3.13-slim
WORKDIR /app&lt;/p&gt;
&lt;p&gt;RUN pip install poetry
ENV PORT=8080
RUN apt update &amp;amp;&amp;amp; apt install -y kubernetes-client/stable prometheus/stable &amp;amp;&amp;amp; apt clean
COPY . .&lt;/p&gt;
&lt;p&gt;RUN adduser &amp;ndash;disabled-password &amp;ndash;gecos &amp;ldquo;&amp;rdquo; myuser &amp;amp;&amp;amp; \
    chown -R myuser:myuser /app&lt;/p&gt;
&lt;p&gt;USER myuser&lt;/p&gt;
&lt;p&gt;RUN poetry install&lt;/p&gt;
&lt;p&gt;ENV PATH=&amp;rdquo;/home/myuser/.local/bin:$PATH&amp;rdquo;
CMD poetry run uvicorn main:app &amp;ndash;host 0.0.0.0 &amp;ndash;port $PORT
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(Configuration unique) Créez un dépôt Artifact Registry si vous ne l&amp;rsquo;avez pas déjà fait.
    &lt;code&gt;bash
    #    Remplacez REGION et GOOGLE_CLOUD_PROJECT avec vos détails.
    gcloud artifacts repositories create adk-repo --repository-format=docker \
        --location=YOUR_REGION \
        --description="ADK agent repository" \
        --project=YOUR_GOOGLE_CLOUD_PROJECT&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Construisez l&amp;rsquo;image Docker et poussez-la vers Artifact Registry.
    &lt;code&gt;bash
    #    Remplacez REGION et GOOGLE_CLOUD_PROJECT avec vos détails.
    gcloud builds submit --tag YOUR_REGION-docker.pkg.dev/YOUR_GOOGLE_CLOUD_PROJECT/adk-repo/k8s-admin-agent:latest \&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="ajouter-rbac"&gt;Ajouter RBAC&lt;a class="headerlink" href="#ajouter-rbac" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Le manifeste suivant crée un &lt;code&gt;ServiceAccount&lt;/code&gt; Kubernetes dans l&amp;rsquo;espace de noms &lt;code&gt;kubernetes-admin-agent&lt;/code&gt;. Il définit également un &lt;code&gt;ClusterRole&lt;/code&gt; avec des permissions en lecture seule (get, list, watch) sur tous les groupes d&amp;rsquo;API et ressources, puis utilise un &lt;code&gt;ClusterRoleBinding&lt;/code&gt; pour accorder ces permissions au &lt;code&gt;ServiceAccount&lt;/code&gt; créé.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ServiceAccount&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;rules:&lt;/span&gt;
&lt;span class="s"&gt;- apiGroups:&lt;/span&gt;
&lt;span class="s"&gt;  - &amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;  resources:&lt;/span&gt;
&lt;span class="s"&gt;  - &amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;  verbs:&lt;/span&gt;
&lt;span class="s"&gt;  - get&lt;/span&gt;
&lt;span class="s"&gt;  - list&lt;/span&gt;
&lt;span class="s"&gt;  - watch&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;# This cluster role binding allows the kubernetes-admin-agent ServiceAccount to read all resources in any namespace.&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRoleBinding&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent-global&lt;/span&gt;
&lt;span class="s"&gt;subjects:&lt;/span&gt;
&lt;span class="s"&gt;- kind: ServiceAccount&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;  namespace: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;roleRef:&lt;/span&gt;
&lt;span class="s"&gt;  kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;  apiGroup: rbac.authorization.k8s.io&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="ajouter-un-secret-pour-les-identifiants-google"&gt;Ajouter un secret pour les identifiants google&lt;a class="headerlink" href="#ajouter-un-secret-pour-les-identifiants-google" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pour permettre à votre agent fonctionnant dans Kubernetes de s&amp;rsquo;authentifier auprès des services Google Cloud (spécifiquement Vertex AI pour Gemini), vous devez créer un compte de service GCP, lui accorder les permissions nécessaires, générer une clé pour celui-ci, et stocker cette clé comme un secret Kubernetes.&lt;/p&gt;
&lt;p&gt;Assurez-vous que votre variable d&amp;rsquo;environnement &lt;code&gt;GOOGLE_CLOUD_PROJECT&lt;/code&gt; est correctement définie avant d&amp;rsquo;exécuter ces commandes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Créer le compte de service&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;iam&lt;span class="w"&gt; &lt;/span&gt;service-accounts&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent

&lt;span class="c1"&gt;# 2. Accorder au compte de service le rôle &amp;quot;Vertex AI User&amp;quot; sur votre projet&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;projects&lt;span class="w"&gt; &lt;/span&gt;add-iam-policy-binding&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--member&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:kubernetes-admin-agent@&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iam.gserviceaccount.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--role&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;roles/aiplatform.user&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Créer et télécharger une clé pour le compte de service&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;iam&lt;span class="w"&gt; &lt;/span&gt;service-accounts&lt;span class="w"&gt; &lt;/span&gt;keys&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;key.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--iam-account&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes-admin-agent@&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iam.gserviceaccount.com&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Créer un espace de noms Kubernetes pour l&amp;#39;agent (si pas déjà créé)&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;ns&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent
&lt;span class="c1"&gt;# 5. Créer un secret Kubernetes à partir du fichier de clé téléchargé&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;secret&lt;span class="w"&gt; &lt;/span&gt;generic&lt;span class="w"&gt; &lt;/span&gt;google-cloud-key&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent&lt;span class="w"&gt; &lt;/span&gt;--from-file&lt;span class="o"&gt;=&lt;/span&gt;key.json&lt;span class="o"&gt;=&lt;/span&gt;key.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="deployer-le-service"&gt;Déployer le service&lt;a class="headerlink" href="#deployer-le-service" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id="tests-et-experimentations"&gt;Tests et expérimentations&lt;a class="headerlink" href="#tests-et-experimentations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="diagnostiquer-oomkill"&gt;Diagnostiquer OOMKill&lt;a class="headerlink" href="#diagnostiquer-oomkill" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id="conclusion-et-prospective"&gt;Conclusion et prospective&lt;a class="headerlink" href="#conclusion-et-prospective" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;</content><category term="devops"/><category term="devops"/><category term="ai"/></entry><entry><title>Your Own Devops Agent: The future of observability</title><link href="https://www.ops-chronicles.cloud/own-devops-agents.html" rel="alternate"/><published>2025-05-28T00:00:00+02:00</published><updated>2025-05-28T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2025-05-28:/own-devops-agents.html</id><summary type="html">&lt;p&gt;How to build and utilize an ADK (Python-based) Kubernetes agent.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adk-agent-developpement-quick-start"&gt;ADK agent developpement quick start&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prerequisites"&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#basic-structure"&gt;Basic structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#giving-agent-instructions"&gt;Giving Agent Instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing-agent"&gt;Testing agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#giving-tools-to-my-agent"&gt;Giving tools to my agent&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#and-now-real-testing"&gt;And now real testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#kubernetes-integration"&gt;Kubernetes integration&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#build-a-docker-container"&gt;Build a docker container :&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#in-kubernetes_admindockerfile"&gt;In kubernetes_admin/Dockerfile&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#add-rbac"&gt;Add RBAC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#add-secret-for-google-credentials"&gt;Add secret for google credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#deploy-service"&gt;Deploy service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing-and-experimentings"&gt;Testing and experimentings&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#diagnose-oomkill"&gt;Diagnose OOMKill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion-and-prospective"&gt;Conclusion and prospective&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Google has recently released the Agent Development Kit (ADK), a framework designed to simplify the development and deployment of AI-driven agents. This framework is remarkably user-friendly. In this context, an agent is an interactive API that leverages a Large Language Model (LLM), like Gemini, and is further empowered by your custom scripts to generate responses based on their output.&lt;/p&gt;
&lt;p&gt;In the DevSecOps world, we&amp;rsquo;re already equipped with a multitude of tools and scripts (CLI, API, and more). Now, imagine equipping an AI with all of these capabilities. &lt;/p&gt;
&lt;p&gt;As DevOps engineers, we often find ourselves manually reading logs, describing resources to find statuses, and watching events. What if each of us had a dedicated assistant to perform these initial diagnostic steps for you, directly pointing to relevant information?&lt;/p&gt;
&lt;p&gt;This article explores how to build and utilize an ADK (Python-based) Kubernetes agent. This agent will act as a diagnostic tool, callable via chat or an API, to quickly gather crucial information about our cluster and provide initial diagnostic insights.&lt;/p&gt;
&lt;p&gt;Test it; personally, I am convinced: this is the future of observability.&lt;/p&gt;
&lt;h2 id="adk-agent-developpement-quick-start"&gt;ADK agent developpement quick start&lt;a class="headerlink" href="#adk-agent-developpement-quick-start" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Getting started with the Agent Development Kit (ADK) is straightforward. This section will guide you through how to set up and create a (very) basic agent.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;a class="headerlink" href="#prerequisites" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Before you begin, ensure you have &lt;strong&gt;Python&lt;/strong&gt; (including &lt;code&gt;venv&lt;/code&gt; for virtual environments and &lt;code&gt;pip&lt;/code&gt; for package installation) installed.&lt;/li&gt;
&lt;li&gt;You will also need a Google Cloud Platform (GCP) project set up to use Vertex AI for calling the Gemini model.&lt;/li&gt;
&lt;li&gt;Create a folder for your project:
    &lt;code&gt;bash
    mkdir kubernetes-admin &amp;amp;&amp;amp; cd kubernetes-admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Initialize a virtual environment:
    &lt;code&gt;bash
    python -m venv venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Activate the virtual environment:&lt;ul&gt;
&lt;li&gt;On macOS and Linux:
    &lt;code&gt;bash
    source venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On Windows:
    &lt;code&gt;bash
    .\venv\Scripts\activate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Install poetry: &lt;code&gt;pip install poetry&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="basic-structure"&gt;Basic structure&lt;a class="headerlink" href="#basic-structure" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Create the following files and directories within the &lt;code&gt;kubernetes-admin&lt;/code&gt; directory you created earlier:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Containing agent instructions : initial prompt and parameters&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# A default init file&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# To hosting futurer tools interfaces&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# A default init file for the tools package&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Navigate into the inner &lt;code&gt;kubernetes_admin&lt;/code&gt; directory (this is where your Python package will reside):&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubernetes_admin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Initialize the Poetry project within this &lt;code&gt;kubernetes_admin&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Follow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`kubernetes_admin`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Add the ADK dependency to your project:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;google-ai-agent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;adk&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Populate the &lt;code&gt;kubernetes_admin/__init__.py&lt;/code&gt; file. This makes the &lt;code&gt;agent&lt;/code&gt; module accessible.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In kubernetes_admin/kubernetes_admin/__init__.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;gt; __init__.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;kubernetes_admin/tools/__init__.py&lt;/code&gt; file can remain empty for now.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In kubernetes_admin/kubernetes_admin/tools/&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Create an empty &lt;code&gt;.env&lt;/code&gt; file in the &lt;code&gt;kubernetes_admin/kubernetes_admin/&lt;/code&gt; directory. This can be used later for environment-specific configurations if needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In kubernetes_admin/kubernetes_admin/&lt;/span&gt;
touch&lt;span class="w"&gt; &lt;/span&gt;.env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Navigate back to the root &lt;code&gt;kubernetes-admin&lt;/code&gt; project directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="giving-agent-instructions"&gt;Giving Agent Instructions&lt;a class="headerlink" href="#giving-agent-instructions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;agent.py&lt;/code&gt; file (located at &lt;code&gt;kubernetes-admin/kubernetes_admin/agent.py&lt;/code&gt;) is crucial in the Agent Development Kit (ADK). It defines the initial prompt and core instructions for the AI agent. Later, it will be expanded to include the specific tools the agent can use and the logic for how it should utilize them.&lt;/p&gt;
&lt;p&gt;You can use AI to help generate the initial prompt and instructions, or write them yourself.&lt;/p&gt;
&lt;p&gt;Create/edit &lt;code&gt;kubernetes-admin/kubernetes_admin/agent.py&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In kubernetes_admin/kubernetes_admin/agent.py&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Kubernetes_Admin: Agent for interacting with and managing a Kubernetes cluster.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;google.adk.agents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gemini-2.5-pro-preview-05-06&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;You are Kubernetes Admin, an AI assistant designed to help users manage and understand their Kubernetes (K8s) clusters.&lt;/span&gt;
&lt;span class="s2"&gt;Your primary goal is to help users by:&lt;/span&gt;
&lt;span class="s2"&gt;- Answering questions about their Kubernetes cluster.&lt;/span&gt;
&lt;span class="s2"&gt;- Retrieving information about resources like pods, nodes, services, deployments, namespaces, etc. using the available tools.&lt;/span&gt;
&lt;span class="s2"&gt;- Explaining Kubernetes concepts relevant to their queries.&lt;/span&gt;
&lt;span class="s2"&gt;- Assisting in understanding resource status and configurations.&lt;/span&gt;

&lt;span class="s2"&gt;Always strive to be clear, concise, and helpful in your responses.&lt;/span&gt;

&lt;span class="s2"&gt;Example interactions:&lt;/span&gt;
&lt;span class="s2"&gt;- User: &amp;quot;How many pods are running in the &amp;#39;default&amp;#39; namespace?&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- You: (After using a tool to get pod count) &amp;quot;There are X pods currently running in the &amp;#39;default&amp;#39; namespace.&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- User: &amp;quot;What&amp;#39;s the status of the pod named &amp;#39;my-app-pod-123&amp;#39;?&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;- You: (After using a tool to get pod status) &amp;quot;The pod &amp;#39;my-app-pod-123&amp;#39; is currently in a &amp;#39;Running&amp;#39; state.&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes_admin_agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;An AI agent that helps users explore and understand their Kubernetes cluster by answering questions and retrieving information about resources.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="c1"&gt;# Pass the FunctionTool instance&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="testing-agent"&gt;Testing agent&lt;a class="headerlink" href="#testing-agent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before running test, you must configure a cloud-environement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_GENAI_USE_VERTEXAI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;central1&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_STORAGE_BUCKET&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;adk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;-$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openssl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# generate a unique bucket name&lt;/span&gt;
&lt;span class="n"&gt;gcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;
&lt;span class="n"&gt;gcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//$&lt;/span&gt;&lt;span class="n"&gt;GOOGLE_CLOUD_STORAGE_BUCKET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you can run test with the following command :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adk run .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If everything is ok, you will see the following prompt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agents_log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="mf"&gt;.20250528&lt;/span&gt;&lt;span class="n"&gt;_200909&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;
&lt;span class="k"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agents_log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;
&lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;managing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;understanding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;How&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;namespaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;across&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;namespaces&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="giving-tools-to-my-agent"&gt;Giving tools to my agent&lt;a class="headerlink" href="#giving-tools-to-my-agent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s write a Python function to interact with &lt;code&gt;kubectl&lt;/code&gt; and wrap it as an ADK tool.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create/edit &lt;code&gt;kubernetes-admin/kubernetes_admin/tools/kubectl_tool.py&lt;/code&gt; with the following content:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In kubernetes_admin/kubernetes_admin/tools/kubectl_tool.py&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Tool for executing kubectl get commands.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt; &lt;span class="c1"&gt;# For actual kubectl calls&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;google.adk.tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run_kubectl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pods&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Runs a kubectl command with the specified verb, resource type, optional resource name, and namespace.&lt;/span&gt;
&lt;span class="sd"&gt;    Exemples:&lt;/span&gt;
&lt;span class="sd"&gt;        To get configuration of a specific pod use verbs: &amp;quot;describe&amp;quot;, namespace: &amp;quot;all&amp;quot;, resource_type: &amp;quot;pods&amp;quot;, resource_name: &amp;quot;&amp;lt;custom pod name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        To get logs of a specific pod use verbs: &amp;quot;logs&amp;quot;, namespace: &amp;quot;&amp;lt;namespace&amp;gt;&amp;quot;, resource_type: &amp;quot;&amp;quot;, resource_name: &amp;quot;&amp;lt;custom pod name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        To list pods in all namespace use verbs: &amp;quot;get&amp;quot;, namespace: &amp;quot;all&amp;quot;, resource_type: &amp;quot;pods&amp;quot;, resource_name: &amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="sd"&gt;    Args:&lt;/span&gt;
&lt;span class="sd"&gt;        verbs: The kubectl verb to use (e.g., &amp;#39;get&amp;#39;, &amp;#39;describe&amp;#39;, &amp;#39;logs&amp;#39;, &amp;#39;top&amp;#39;).&lt;/span&gt;
&lt;span class="sd"&gt;        resource_type: The type of Kubernetes resource (e.g., &amp;#39;pods&amp;#39;, &amp;#39;nodes&amp;#39;, &amp;#39;services&amp;#39;, &amp;#39;deployments&amp;#39;).&lt;/span&gt;
&lt;span class="sd"&gt;        resource_name: Optional. The specific name of the resource. If None, lists all resources of the type.&lt;/span&gt;
&lt;span class="sd"&gt;        namespace: Optional. The Kubernetes namespace. Defaults to &amp;#39;default&amp;#39;.&lt;/span&gt;

&lt;span class="sd"&gt;    Returns:&lt;/span&gt;
&lt;span class="sd"&gt;        A string containing the output of the kubectl command or an error message.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubectl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resource_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;all&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--all-namespaces&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Error: kubectl command not found. Please ensure kubectl is installed and in your PATH.&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error executing kubectl command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutExpired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Error: kubectl command timed out.&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;An unexpected error occurred: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;kubectl_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_kubectl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Now, let&amp;rsquo;s update kubernetes-admin/kubernetes_admin/agent.py to include this new tool.
Import the tool:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.tools.kubectl_tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;kubectl_tool&lt;/span&gt; &lt;span class="c1"&gt;# Import the FunctionTool instance&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;kubernetes_admin_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes_admin_agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;An AI agent that helps users explore and understand their Kubernetes cluster by answering questions and retrieving information about resources.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kubectl_tool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# Pass the FunctionTool instance&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Update the agent&amp;rsquo;s instructions within KUBERNETES_ADMIN_INSTRUCTION in kubernetes-admin/kubernetes_admin/agent.py to inform the LLM about the new tool and how to use it:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;KUBERNETES_ADMIN_INSTRUCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;...&lt;/span&gt;
&lt;span class="s2"&gt;...&lt;/span&gt;
&lt;span class="s2"&gt;You have a tool named &amp;#39;run_kubectl_command&amp;#39; to fetch information from the Kubernetes cluster by executing kubectl commands.&lt;/span&gt;
&lt;span class="s2"&gt;Tool &amp;#39;run_kubectl_command&amp;#39; arguments:&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;verbs&amp;#39;: (string, required) The kubectl verb to use (e.g., &amp;#39;get&amp;#39;, &amp;#39;describe&amp;#39;, &amp;#39;logs&amp;#39;, &amp;#39;top&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;resource_type&amp;#39;: (string, required) The type of Kubernetes resource (e.g., &amp;#39;pods&amp;#39;, &amp;#39;nodes&amp;#39;, &amp;#39;services&amp;#39;, &amp;#39;deployments&amp;#39;, &amp;#39;namespaces&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;resource_name&amp;#39;: (string, optional) The specific name of the resource. If not provided, all resources of the given type in the namespace will be listed.&lt;/span&gt;
&lt;span class="s2"&gt;- &amp;#39;namespace&amp;#39;: (string, optional, defaults to &amp;#39;default&amp;#39;) The Kubernetes namespace to query.&lt;/span&gt;

&lt;span class="s2"&gt;When a user asks for information that requires querying the cluster (e.g., &amp;quot;list pods&amp;quot;, &amp;quot;get node status&amp;quot;, &amp;quot;describe service my-service&amp;quot;, &amp;quot;get logs for pod xyz&amp;quot;), you MUST use the &amp;#39;run_kubectl_command&amp;#39; tool.&lt;/span&gt;
&lt;span class="s2"&gt;Clearly state that you are using the tool, what you are querying for, and then present the information returned by the tool.&lt;/span&gt;
&lt;span class="s2"&gt;Determine the correct &amp;#39;verbs&amp;#39; argument based on the user&amp;#39;s request (e.g., &amp;quot;list&amp;quot; or &amp;quot;how many&amp;quot; implies &amp;#39;get&amp;#39;, &amp;quot;what&amp;#39;s the status&amp;quot; implies &amp;#39;get&amp;#39;, &amp;quot;describe&amp;quot; implies &amp;#39;describe&amp;#39;, &amp;quot;show logs&amp;quot; implies &amp;#39;logs&amp;#39;).&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="and-now-real-testing"&gt;And now real testing&lt;a class="headerlink" href="#and-now-real-testing" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now, let&amp;rsquo;s perform a real test to see if our agent uses &lt;code&gt;kubectl&lt;/code&gt; as expected. (Ensure you have a valid &lt;code&gt;kubeconfig&lt;/code&gt; file and are connected to a Kubernetes cluster for this test.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;adk&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;.
...
&lt;span class="o"&gt;[&lt;/span&gt;user&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;how&lt;span class="w"&gt; &lt;/span&gt;many&lt;span class="w"&gt; &lt;/span&gt;pod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces
&lt;span class="o"&gt;[&lt;/span&gt;kubernetes_admin_agent&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Okay,&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;that.&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;run_kubectl&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;count&lt;span class="w"&gt; &lt;/span&gt;them&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you.
&lt;span class="o"&gt;[&lt;/span&gt;kubernetes_admin_agent&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;retrieved&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;across&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces.&lt;span class="w"&gt; &lt;/span&gt;There&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;lot&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;them!
Based&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;output,&lt;span class="w"&gt; &lt;/span&gt;there&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;119&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;across&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;namespaces.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="kubernetes-integration"&gt;Kubernetes integration&lt;a class="headerlink" href="#kubernetes-integration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We will now deploy our agent into a kubernetes cluster, to give possibility to other user to use it.&lt;/p&gt;
&lt;h3 id="build-a-docker-container"&gt;Build a docker container :&lt;a class="headerlink" href="#build-a-docker-container" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Here&amp;rsquo;s a Dockerfile to build a container image for your Python agent. This Dockerfile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Uses a slim Python base image.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installs &lt;code&gt;kubectl&lt;/code&gt; (as your agent&amp;rsquo;s tool uses it) and Poetry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sets up a non-root user for better security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installs your project dependencies using Poetry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sets the default command to run your ADK agent using its built-in HTTP server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;```Dockerfile&lt;/p&gt;
&lt;h1 id="in-kubernetes_admindockerfile"&gt;In kubernetes_admin/Dockerfile&lt;a class="headerlink" href="#in-kubernetes_admindockerfile" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;FROM python:3.13-slim
WORKDIR /app&lt;/p&gt;
&lt;p&gt;RUN pip install poetry
ENV PORT=8080
RUN apt update &amp;amp;&amp;amp; apt install -y kubernetes-client/stable prometheus/stable &amp;amp;&amp;amp; apt clean
COPY . .&lt;/p&gt;
&lt;p&gt;RUN adduser &amp;ndash;disabled-password &amp;ndash;gecos &amp;ldquo;&amp;rdquo; myuser &amp;amp;&amp;amp; \
    chown -R myuser:myuser /app&lt;/p&gt;
&lt;p&gt;USER myuser&lt;/p&gt;
&lt;p&gt;RUN poetry install&lt;/p&gt;
&lt;p&gt;ENV PATH=&amp;rdquo;/home/myuser/.local/bin:$PATH&amp;rdquo;
CMD poetry run uvicorn main:app &amp;ndash;host 0.0.0.0 &amp;ndash;port $PORT
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(One-time setup) Create an Artifact Registry repository if you haven&amp;rsquo;t already.
    &lt;code&gt;bash
    #    Replace REGION and GOOGLE_CLOUD_PROJECT with your details.
    gcloud artifacts repositories create adk-repo --repository-format=docker \
        --location=YOUR_REGION \
        --description="ADK agent repository" \
        --project=YOUR_GOOGLE_CLOUD_PROJECT&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Build the Docker image and push it to Artifact Registry.
    &lt;code&gt;bash
    #    Replace REGION and GOOGLE_CLOUD_PROJECT with your details.
    gcloud builds submit --tag YOUR_REGION-docker.pkg.dev/YOUR_GOOGLE_CLOUD_PROJECT/adk-repo/k8s-admin-agent:latest \&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="add-rbac"&gt;Add RBAC&lt;a class="headerlink" href="#add-rbac" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following manifest creates a Kubernetes &lt;code&gt;ServiceAccount&lt;/code&gt; in the &lt;code&gt;kubernetes-admin-agent&lt;/code&gt; namespace. It also defines a &lt;code&gt;ClusterRole&lt;/code&gt; with read-only permissions (get, list, watch) across all API groups and resources, and then uses a &lt;code&gt;ClusterRoleBinding&lt;/code&gt; to grant these permissions to the created &lt;code&gt;ServiceAccount&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ServiceAccount&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;rules:&lt;/span&gt;
&lt;span class="s"&gt;- apiGroups:&lt;/span&gt;
&lt;span class="s"&gt;  - &amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;  resources:&lt;/span&gt;
&lt;span class="s"&gt;  - &amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;  verbs:&lt;/span&gt;
&lt;span class="s"&gt;  - get&lt;/span&gt;
&lt;span class="s"&gt;  - list&lt;/span&gt;
&lt;span class="s"&gt;  - watch&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;# This cluster role binding allows the kubernetes-admin-agent ServiceAccount to read all resources in any namespace.&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRoleBinding&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent-global&lt;/span&gt;
&lt;span class="s"&gt;subjects:&lt;/span&gt;
&lt;span class="s"&gt;- kind: ServiceAccount&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;  namespace: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;roleRef:&lt;/span&gt;
&lt;span class="s"&gt;  kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;  name: kubernetes-admin-agent&lt;/span&gt;
&lt;span class="s"&gt;  apiGroup: rbac.authorization.k8s.io&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="add-secret-for-google-credentials"&gt;Add secret for google credentials&lt;a class="headerlink" href="#add-secret-for-google-credentials" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To allow your agent running in Kubernetes to authenticate with Google Cloud services (specifically Vertex AI for Gemini), you need to create a GCP service account, grant it the necessary permissions, generate a key for it, and store this key as a Kubernetes secret.&lt;/p&gt;
&lt;p&gt;Make sure your &lt;code&gt;GOOGLE_CLOUD_PROJECT&lt;/code&gt; environment variable is set correctly before running these commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Create the service account&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;iam&lt;span class="w"&gt; &lt;/span&gt;service-accounts&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent

&lt;span class="c1"&gt;# 2. Grant the service account the &amp;quot;Vertex AI User&amp;quot; role on your project&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;projects&lt;span class="w"&gt; &lt;/span&gt;add-iam-policy-binding&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--member&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:kubernetes-admin-agent@&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iam.gserviceaccount.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--role&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;roles/aiplatform.user&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Create and download a key for the service account&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;iam&lt;span class="w"&gt; &lt;/span&gt;service-accounts&lt;span class="w"&gt; &lt;/span&gt;keys&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;key.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--iam-account&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kubernetes-admin-agent@&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iam.gserviceaccount.com&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Create a Kubernetes namespace for the agent (if not already created)&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;ns&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent
&lt;span class="c1"&gt;# 5. Create a Kubernetes secret from the downloaded key file&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;secret&lt;span class="w"&gt; &lt;/span&gt;generic&lt;span class="w"&gt; &lt;/span&gt;google-cloud-key&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;kubernetes-admin-agent&lt;span class="w"&gt; &lt;/span&gt;--from-file&lt;span class="o"&gt;=&lt;/span&gt;key.json&lt;span class="o"&gt;=&lt;/span&gt;key.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="deploy-service"&gt;Deploy service&lt;a class="headerlink" href="#deploy-service" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id="testing-and-experimentings"&gt;Testing and experimentings&lt;a class="headerlink" href="#testing-and-experimentings" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="diagnose-oomkill"&gt;Diagnose OOMKill&lt;a class="headerlink" href="#diagnose-oomkill" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id="conclusion-and-prospective"&gt;Conclusion and prospective&lt;a class="headerlink" href="#conclusion-and-prospective" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;</content><category term="devops"/><category term="devops"/><category term="ai"/></entry><entry><title>Pourquoi vous avez besoin de certificats dans SSH ?</title><link href="https://www.ops-chronicles.cloud/fr/why-you-need-SSH-certificates.html" rel="alternate"/><published>2024-07-25T00:00:00+02:00</published><updated>2024-07-25T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-07-25:/fr/why-you-need-SSH-certificates.html</id><summary type="html">&lt;p&gt;Les certificats SSH offrent une alternative plus sécurisée et plus gérable aux clés SSH traditionnelles en tirant parti d&amp;rsquo;une autorité de certification centralisée comme Hashicorp Vault.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#les-perils-des-cles-privees-pourquoi-le-partage-ou-une-mauvaise-gestion-est-risque"&gt;Les périls des clés privées : pourquoi le partage ou une mauvaise gestion est risqué&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#cas-dutilisation-acces-sre-et-developpeur-ssh-avec-hashicorp-vault"&gt;Cas d&amp;rsquo;utilisation : accès SRE et développeur SSH avec Hashicorp Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#generer-et-distribuer-un-certificat-ssh"&gt;Générer et distribuer un certificat SSH&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prerequis"&gt;Prérequis:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#activez-le-ssh-secrets-engine"&gt;Activez le &amp;ldquo;SSH Secrets Engine&amp;rdquo;:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#creer-une-autorite-de-certification-pour-le-coffre-fort-hashicorp"&gt;Créer une autorité de certification pour le coffre-fort hashicorp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ajout-dune-autorisation-de-roles-pour-les-developpeurs-dans-hashicorp-vault"&gt;Ajout d&amp;rsquo;une autorisation de rôles pour les développeurs dans Hashicorp Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-0-injection-de-lautorite-de-certification-dans-les-serveurs-aws-azure-et-gcp"&gt;Étape 0 : Injection de l&amp;rsquo;autorité de certification dans les serveurs AWS, Azure et GCP&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#aws"&gt;AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#gcp"&gt;GCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#azure"&gt;Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#custom-onprem-instances"&gt;Custom OnPrem Instances&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#comment-les-developpeurs-peuvent-se-connecter-avec-des-certificats-signes-ssh"&gt;Comment les développeurs peuvent se connecter avec des certificats signés SSH&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#etape-1-demander-un-certificat-en-tant-que-developpeur-je-souhaite-signer-mes-cles-ssh"&gt;Étape 1 : Demander un certificat : En tant que développeur, je souhaite signer mes clés ssh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-2-connexion-ssh-en-tant-que-developpeur-je-souhaite-me-connecter-sur-mes-instances-via-ssh"&gt;Étape 2 : Connexion SSH : En tant que développeur, je souhaite me connecter sur mes instances via ssh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#liens"&gt;Liens :&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lorsque vous travaillez dans le cloud, sécuriser l&amp;rsquo;accès aux serveurs Linux de production est essentiel car votre charge de travail est confrontée à Internet. Les grandes entreprises jonglent avec les besoins critiques : maintenir un contrôle strict, appliquer un accès granulaire et garantir le plus haut niveau de sécurité.&lt;/p&gt;
&lt;p&gt;Les clés SSH traditionnelles, bien qu’elles constituent la pierre angulaire de l’accès à distance, peuvent présenter des défis dans ces domaines.
Cet article de blog explique pourquoi les certificats SSH, en combinant la puissance de Vault, offrent une alternative intéressante aux clés SSH traditionnelles. &lt;/p&gt;
&lt;p&gt;Nous aborderons les aspects cruciaux de la gestion de l&amp;rsquo;accès aux environnements de production :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contrôle centralisé et informations d&amp;rsquo;identification expirantes&lt;/strong&gt; : les certificats SSH, émis et gérés par une autorité de certification (CA) centrale, fournissent un point de contrôle unique pour l&amp;rsquo;accès. Contrairement aux clés SSH statiques, les certificats peuvent avoir une durée de vie définie, garantissant que les informations d&amp;rsquo;identification compromises sont automatiquement révoquées après expiration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Banishing Root Login&lt;/strong&gt; : Les dangers inhérents à la connexion root sont déjà bien documentés. Les certificats SSH vous permettent d&amp;rsquo;appliquer un accès non root, en promouvant un principe de moindre privilège et en minimisant les dommages potentiels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visibilité granulaire et contrôle d&amp;rsquo;accès&lt;/strong&gt; : les certificats offrent un niveau granulaire de contrôle d&amp;rsquo;accès. Vous pouvez définir non seulement qui peut accéder à un serveur, mais aussi quelles commandes ils peuvent exécuter et dans quelles conditions (permit-pty, port-forwarding, &amp;hellip;). Ce niveau de détail fournit une piste d&amp;rsquo;audit claire et minimise la surface d&amp;rsquo;attaque.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En mettant en œuvre des certificats SSH, les entreprises peuvent gagner un avantage significatif dans la sécurisation de leurs environnements de production.&lt;/p&gt;
&lt;h2 id="les-perils-des-cles-privees-pourquoi-le-partage-ou-une-mauvaise-gestion-est-risque"&gt;Les périls des clés privées : pourquoi le partage ou une mauvaise gestion est risqué&lt;a class="headerlink" href="#les-perils-des-cles-privees-pourquoi-le-partage-ou-une-mauvaise-gestion-est-risque" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Il existe plusieurs raisons pour lesquelles les utilisateurs/Administrateurs peuvent conserver leurs clés privées SSH sur leur ordinateur.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C&amp;rsquo;est indéniablement pratique. Avoir votre clé privée à portée de main permet un accès rapide et facile aux serveurs sans avoir besoin de saisir un mot de passe à chaque fois. Cela peut représenter un gain de temps considérable, en particulier pour ceux qui se connectent fréquemment à plusieurs serveurs.&lt;/li&gt;
&lt;li&gt;Certains utilisateurs comprennent mal les implications en matière de sécurité. Ils pourraient croire que tant que leur ordinateur est sécurisé (mots de passe forts, pare-feu, etc.), la clé privée qui y est stockée est également sécurisée.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mais&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Si votre clé privée tombe entre de mauvaises mains, les attaquants obtiennent un accès complet à n&amp;rsquo;importe quel serveur auquel vous pouvez accéder avec cette clé. Cela pourrait leur permettre de voler des données, d’installer des logiciels malveillants ou de perturber des opérations critiques.&lt;/li&gt;
&lt;li&gt;si vous perdez votre clé privée, vous serez exclu des serveurs auxquels vous devez accéder. Cela peut entraîner des temps d’arrêt et des perturbations importants.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La mise en œuvre et la gestion des certificats SSH, en particulier pour ceux qui ne les connaissent pas, peuvent sembler une étape supplémentaire complexe par rapport à la simplicité d&amp;rsquo;utilisation d&amp;rsquo;une clé privée. Cependant, l&amp;rsquo;utilisation d&amp;rsquo;outils de fournisseurs cloud tels que &lt;a href="https://docs.aws.amazon.com/fr_fr/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html"&gt;AWS SSM Connect&lt;/a&gt; ou &lt;a href="https://cloud.google.com/compute/docs/oslogin?hl=fr"&gt;GCP oslogin&lt;/a&gt; peut être une solution. Mais l&amp;rsquo;utilisation de certificats SSH offre l&amp;rsquo;avantage d&amp;rsquo;être nativement compatible multi-cloud, offrant une approche unifiée dans différents environnements cloud.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡💡💡 ** Nous avons donc besoin d&amp;rsquo;un outil pour nous aider ** 💡💡💡&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="cas-dutilisation-acces-sre-et-developpeur-ssh-avec-hashicorp-vault"&gt;Cas d&amp;rsquo;utilisation : accès SRE et développeur SSH avec Hashicorp Vault&lt;a class="headerlink" href="#cas-dutilisation-acces-sre-et-developpeur-ssh-avec-hashicorp-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Une équipe de développement a besoin d&amp;rsquo;accéder à un serveur de production à des fins de dépannage et de débogage. L&amp;rsquo;équipe SRE est responsable de la gestion de l&amp;rsquo;accès et de la sécurité du serveur.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="image" src="/images/2024-why-you-need-SSH-certificates/use-case.png" /&gt;&lt;/p&gt;
&lt;h2 id="generer-et-distribuer-un-certificat-ssh"&gt;Générer et distribuer un certificat SSH&lt;a class="headerlink" href="#generer-et-distribuer-un-certificat-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="prerequis"&gt;Prérequis:&lt;a class="headerlink" href="#prerequis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ops-chronicles.cloud/fr/hosting-hashicorpvault-on-cloudrun.html"&gt;Une instance Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Une compréhension de base des concepts de Vault tels que les &amp;ldquo;secrets engines&amp;rdquo;, &amp;ldquo;policies&amp;rdquo;, et &amp;ldquo;roles&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="activez-le-ssh-secrets-engine"&gt;Activez le &amp;ldquo;SSH Secrets Engine&amp;rdquo;:&lt;a class="headerlink" href="#activez-le-ssh-secrets-engine" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Avant de plonger dans la configuration, il est essentiel de comprendre ce que fait le moteur de secrets SSH. Il fournit un moyen sécurisé de gérer les informations d&amp;rsquo;identification SSH, offrant des fonctionnalités telles que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Génération d&amp;rsquo;informations d&amp;rsquo;identification SSH dynamiques&lt;/li&gt;
&lt;li&gt;Authentification basée sur un certificat&lt;/li&gt;
&lt;li&gt;Contrôle d&amp;rsquo;accès basé sur les rôles&lt;/li&gt;
&lt;li&gt;Intégration avec d&amp;rsquo;autres fonctionnalités de Vault&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour activer le moteur de secrets SSH, vous utiliserez la CLI Vault. Voici la commande :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;secrets&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="o"&gt;=&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;ssh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cette commande monte le moteur de secrets SSH sur le chemin /ssh. Vous pouvez personnaliser le chemin si nécessaire.&lt;/p&gt;
&lt;h3 id="creer-une-autorite-de-certification-pour-le-coffre-fort-hashicorp"&gt;Créer une autorité de certification pour le coffre-fort hashicorp&lt;a class="headerlink" href="#creer-une-autorite-de-certification-pour-le-coffre-fort-hashicorp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nous disposons de deux méthodes pour établir une autorité de certification (CA) : l&amp;rsquo;&lt;strong&gt;interface de ligne de commande (CLI)&lt;/strong&gt; et &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pour générer une CA via bash :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault write ssh/config/ca generate_signing_key=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Soit pour générer une CA via terraform :&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_mount&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ssh_signer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;path&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="s"&gt;&amp;quot;ssh-client-signer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;type&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="s"&gt;&amp;quot;ssh&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;description&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="s"&gt;&amp;quot;Sign ssh public keys&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_ssh_secret_backend_ca&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&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;vault_mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssh_signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;generate_signing_key&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="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pub_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;value&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;vault_ssh_secret_backend_ca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="ajout-dune-autorisation-de-roles-pour-les-developpeurs-dans-hashicorp-vault"&gt;Ajout d&amp;rsquo;une autorisation de rôles pour les développeurs dans Hashicorp Vault&lt;a class="headerlink" href="#ajout-dune-autorisation-de-roles-pour-les-developpeurs-dans-hashicorp-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pour gérer efficacement l&amp;rsquo;accès des développeurs aux certificats SSH dans Hashicorp Vault, nous devons configurer les autorisations SSH pour signer les demandes des développeurs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avec terraform&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_ssh_secret_backend_role&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;name&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="s"&gt;&amp;quot;mgoulin&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&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;vault_mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssh_signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;key_type&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="s"&gt;&amp;quot;ca&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;allow_user_certificates&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="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;default_extensions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-agent-forwarding&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-pty&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-port-forwarding&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ttl&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="s"&gt;&amp;quot;1h&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="etape-0-injection-de-lautorite-de-certification-dans-les-serveurs-aws-azure-et-gcp"&gt;Étape 0 : Injection de l&amp;rsquo;autorité de certification dans les serveurs AWS, Azure et GCP&lt;a class="headerlink" href="#etape-0-injection-de-lautorite-de-certification-dans-les-serveurs-aws-azure-et-gcp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;L&amp;rsquo;injection de clés publiques SSH dans des instances cloud est une pratique courante pour permettre un accès SSH sécurisé. Voici comment procéder en tant que SRE sur AWS, Azure et GCP.&lt;/p&gt;
&lt;h4 id="aws"&gt;AWS&lt;a class="headerlink" href="#aws" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ajoutez la clé publique à un objet paire de clés dans le service EC2 à l&amp;rsquo;aide de la commande &lt;code&gt;aws ec2 import-key-pair&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo &amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot; &amp;gt; /tmp/authorized_keys.txt
aws ec2 import-key-pair --key-name &amp;quot;dev-key&amp;quot; --public-key-material fileb:///tmp/authorized_keys.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="gcp"&gt;GCP&lt;a class="headerlink" href="#gcp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ajoutez la clé publique aux métadonnées de l&amp;rsquo;instance à l&amp;rsquo;aide de la commande &lt;code&gt;gcloud compute project-info add-metadata&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$USERNAME:cert-authority $(terraform output -r pub_key)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="azure"&gt;Azure&lt;a class="headerlink" href="#azure" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ajoutez la clé publique aux métadonnées de l&amp;rsquo;instance à l&amp;rsquo;aide de la commande &lt;code&gt;az sshkey&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo &amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot; &amp;gt; /tmp/authorized_keys.txt
az sshkey create --name &amp;quot;dev-key&amp;quot; --public-key &amp;quot;@/tmp/authorized_keys.txt&amp;quot; --resource-group &amp;quot;myResourceGroup&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="custom-onprem-instances"&gt;Custom OnPrem Instances&lt;a class="headerlink" href="#custom-onprem-instances" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ajoutez la clé publique à l&amp;rsquo;instance en tant que root en ajoutant la ligne suivante au fichier &lt;code&gt;/home/$USERNAME/authorized_keys&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="comment-les-developpeurs-peuvent-se-connecter-avec-des-certificats-signes-ssh"&gt;Comment les développeurs peuvent se connecter avec des certificats signés SSH&lt;a class="headerlink" href="#comment-les-developpeurs-peuvent-se-connecter-avec-des-certificats-signes-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Traditionnellement, les connexions SSH reposaient sur des paires de clés publiques et privées pour l&amp;rsquo;authentification. Bien qu&amp;rsquo;efficace, cette méthode présente des défis en termes de gestion des clés, de révocation et de contrôle d&amp;rsquo;accès.
Avec Certificate-Authority et HashicorpVautl, les certificats lient l&amp;rsquo;identité d&amp;rsquo;un utilisateur à sa clé publique, permettant une authentification plus forte et un contrôle d&amp;rsquo;accès granulaire.&lt;/p&gt;
&lt;p&gt;Avec les certificats SSH, les développeurs peuvent se connecter en toute sécurité aux serveurs distants sans avoir besoin de gérer directement les clés privées. Le processus implique :&lt;/p&gt;
&lt;h3 id="etape-1-demander-un-certificat-en-tant-que-developpeur-je-souhaite-signer-mes-cles-ssh"&gt;Étape 1 : Demander un certificat : En tant que développeur, je souhaite signer mes clés ssh&lt;a class="headerlink" href="#etape-1-demander-un-certificat-en-tant-que-developpeur-je-souhaite-signer-mes-cles-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Les développeurs demandent un certificat SSH à une autorité centralisée, souvent via un portail en libre-service ou en appelant l&amp;rsquo;API Vault.&lt;/p&gt;
&lt;p&gt;Cela peut être fait avec la commande &lt;code&gt;vault&lt;/code&gt; suivante. Lors de la requête, les développeurs fournissent leur clé publique, généralement stockée sous &lt;code&gt;id_rsa.pub&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault write -field=signed_key ssh/sign/dev \ 
    public_key=@$HOME/.ssh/id_rsa.pub &amp;gt; signed-cert.pub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="etape-2-connexion-ssh-en-tant-que-developpeur-je-souhaite-me-connecter-sur-mes-instances-via-ssh"&gt;Étape 2 : Connexion SSH : En tant que développeur, je souhaite me connecter sur mes instances via ssh&lt;a class="headerlink" href="#etape-2-connexion-ssh-en-tant-que-developpeur-je-souhaite-me-connecter-sur-mes-instances-via-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Les développeurs utilisent le certificat signé précédemment émis : &lt;code&gt;signed-cert.pub&lt;/code&gt;, souvent appelé certificat client, pour s&amp;rsquo;authentifier auprès du serveur cible. Ce certificat, généralement au format PEM, contient la clé publique du développeur signée par l&amp;rsquo;autorité de certification (CA) de confiance, qui dans ce cas est Vault.&lt;/p&gt;
&lt;p&gt;Lors de la connexion au serveur, le client SSH présente le certificat signé. Le serveur vérifie ensuite l&amp;rsquo;authenticité et la validité du certificat par rapport à la clé publique de l&amp;rsquo;autorité de certification. En cas de succès, la connexion est établie et le développeur accède au serveur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh -i ./signed-cert.pub -v -i ~/.ssh/id_rsa dev@&amp;lt;target_server&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En adoptant les certificats SSH et en tirant parti d&amp;rsquo;un outil de gestion de l&amp;rsquo;autorité de certification centralisée tel que Hashicorp Vault, les organisations peuvent améliorer considérablement leur posture de sécurité SSH. Cette approche offre une solution robuste aux défis posés par la gestion traditionnelle des clés SSH, offrant un contrôle granulaire, une auditabilité améliorée et une gestion rationalisée des accès.&lt;/p&gt;
&lt;p&gt;En passant aux certificats SSH, les organisations peuvent atténuer les risques associés aux clés compromises, faciliter la rotation des clés et appliquer des politiques d&amp;rsquo;accès strictes. Cela conduit finalement à une infrastructure SSH plus sécurisée et efficace.&lt;/p&gt;
&lt;p&gt;Même si la mise en œuvre de certificats SSH nécessite une planification et une configuration minutieuses, les avantages à long terme en termes de sécurité et de facilité de gestion dépassent de loin l&amp;rsquo;investissement initial.&lt;/p&gt;
&lt;p&gt;En suivant les directives décrites dans cet article, les organisations peuvent déployer avec succès des certificats SSH et récolter les fruits d&amp;rsquo;un environnement SSH plus sécurisé et contrôlé.&lt;/p&gt;
&lt;h2 id="liens"&gt;Liens :&lt;a class="headerlink" href="#liens" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/sec-creating_ssh_ca_certificate_signing-keys"&gt;RedHat documentation about ssh-certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/vault/docs/secrets/ssh/signed-ssh-certificates"&gt;Hashicorp Vault documentation about SSH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="devops"/><category term="devops"/><category term="ssh"/><category term="sysadmin"/></entry><entry><title>Why You Need SSH Certificates</title><link href="https://www.ops-chronicles.cloud/why-you-need-SSH-certificates.html" rel="alternate"/><published>2024-07-25T00:00:00+02:00</published><updated>2024-07-25T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-07-25:/why-you-need-SSH-certificates.html</id><summary type="html">&lt;p&gt;SSH certificates offer a more secure and manageable alternative to traditional SSH keys by leveraging a centralized Certificate Authority like Hashicorp Vault.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-perils-of-private-keys-why-sharing-or-poor-management-is-risky"&gt;The Perils of Private Keys: Why Sharing or Poor Management is Risky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#use-case-sre-and-developer-ssh-access-with-hashicorp-vault"&gt;Use Case: SRE and Developer SSH Access with Hashicorp Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#generating-and-distributing-ssh-certificate"&gt;Generating and distributing SSH Certificate&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prerequisites"&gt;Prerequisites:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enable-the-ssh-secrets-engine"&gt;Enable the SSH Secrets Engine:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create-certificate-authority-for-hashicorp-vault"&gt;Create Certificate-Authority for hashicorp vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adding-roles-authorization-for-developers-in-hashicorp-vault"&gt;Adding Roles Authorization for Developers in Hashicorp Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-0-injecting-certificate-authority-into-aws-azure-and-gcp-servers"&gt;Step 0: Injecting Certificate Authority into AWS, Azure, and GCP Servers&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#aws"&gt;AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#gcp"&gt;GCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#azure"&gt;Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#custom-onprem-instances"&gt;Custom OnPrem Instances&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-developers-can-connect-with-ssh-signed-certificates"&gt;How developers can connect with ssh signed certificates&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#step-1-requesting-a-certificate-as-a-developper-i-want-to-sign-my-ssh-keys"&gt;Step 1: Requesting a Certificate: As a developper I want to sign my ssh keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-2-ssh-connection-as-a-developper-i-want-to-connect-on-my-instances-via-ssh"&gt;Step 2: SSH Connection: As a developper I want to connect on my instances via ssh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#links"&gt;Links :&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you work in the cloud, securing access to production Linux servers is essential as your workload are internet faced. Large companies juggle the critical needs of maintaining tight control, enforcing granular access, and ensuring the highest level of security.&lt;/p&gt;
&lt;p&gt;Traditional SSH keys, while a cornerstone of remote access, can introduce challenges in these areas.
This blog post explores why, SSH certificates in combining the power of Vault offer a compelling alternative to traditional SSH keys. &lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll delve into the crucial aspects of managing access to production environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralized Control and Expiring Credentials&lt;/strong&gt;: SSH certificates, issued and managed by a central Certificate Authority (CA), provide a single point of control for access. Unlike static SSH keys, certificates can have defined lifespans, ensuring compromised credentials are automatically revoked after expiration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Banishing Root Login&lt;/strong&gt;: The inherent dangers of root login are well documented. SSH certificates empower you to enforce non-root access, promoting a principle of least privilege and minimizing potential damage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Granular Visibility and Access Control&lt;/strong&gt;: Certificates offer a granular level of access control. You can define not only who can access a server, but also what commands they can execute and under what conditions (permit-pty, port-forwarding, &amp;hellip;). This level of detail provides a clear audit trail and minimizes the attack surface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By implementing SSH certificates, Compagnies can gain a significant edge in securing their production environments.&lt;/p&gt;
&lt;h2 id="the-perils-of-private-keys-why-sharing-or-poor-management-is-risky"&gt;The Perils of Private Keys: Why Sharing or Poor Management is Risky&lt;a class="headerlink" href="#the-perils-of-private-keys-why-sharing-or-poor-management-is-risky" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few reasons why people or administrators might keep their SSH private keys on their computers. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s undeniably convenient. Having your private key readily available allows for quick and easy access to servers without needing to enter a password every time. This can be a major time-saver, especially for those who connect to multiple servers frequently.&lt;/li&gt;
&lt;li&gt;Some users misunderstand the security implications. They might believe that as long as their computer is secure (strong passwords, firewalls etc.) then the private key stored on it is safe as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your private key falls into the wrong hands, attackers gain full access to any server you can access with that key. This could allow them to steal data, install malware, or disrupt critical operations.&lt;/li&gt;
&lt;li&gt;if you lose your private key, you&amp;rsquo;ll be locked out of the servers you need to access. This can cause significant downtime and disruption.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Implementing and managing SSH certificates, especially for those unfamiliar with them, might seem like a complex additional step compared to the simplicity of using a private key. However, while using cloud provider tools like &lt;a href="https://docs.aws.amazon.com/fr_fr/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html"&gt;AWS SSM Connect&lt;/a&gt; or &lt;a href="https://cloud.google.com/compute/docs/oslogin?hl=fr"&gt;GCP oslogin&lt;/a&gt; can be a solution. But using SSH certificates offer the advantage of being natively multi-cloud compatible, providing a unified approach across different cloud environments.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡💡💡 ** So we need a tools to help us ** 💡💡💡&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="use-case-sre-and-developer-ssh-access-with-hashicorp-vault"&gt;Use Case: SRE and Developer SSH Access with Hashicorp Vault&lt;a class="headerlink" href="#use-case-sre-and-developer-ssh-access-with-hashicorp-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A development team needs access to a production server for troubleshooting and debugging purposes. The SRE team is responsible for managing server access and security.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="image" src="/images/2024-why-you-need-SSH-certificates/use-case.png" /&gt;&lt;/p&gt;
&lt;h2 id="generating-and-distributing-ssh-certificate"&gt;Generating and distributing SSH Certificate&lt;a class="headerlink" href="#generating-and-distributing-ssh-certificate" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites:&lt;a class="headerlink" href="#prerequisites" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ops-chronicles.cloud/hosting-hashicorpvault-on-cloudrun.html"&gt;A running Vault instance &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A basic understanding of Vault concepts like secrets engines, policies, and roles.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="enable-the-ssh-secrets-engine"&gt;Enable the SSH Secrets Engine:&lt;a class="headerlink" href="#enable-the-ssh-secrets-engine" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before diving into the configuration, it&amp;rsquo;s essential to understand what the SSH secrets engine does. It provides a secure way to manage SSH credentials, offering features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dynamic SSH credentials generation&lt;/li&gt;
&lt;li&gt;Certificate-based authentication&lt;/li&gt;
&lt;li&gt;Role-based access control&lt;/li&gt;
&lt;li&gt;Integration with other Vault features&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To enable the SSH secrets engine, you&amp;rsquo;ll use the Vault CLI. Here&amp;rsquo;s the command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;secrets&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="o"&gt;=&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;ssh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command mounts the SSH secrets engine at the path /ssh. You can customize the path if needed.&lt;/p&gt;
&lt;h3 id="create-certificate-authority-for-hashicorp-vault"&gt;Create Certificate-Authority for hashicorp vault&lt;a class="headerlink" href="#create-certificate-authority-for-hashicorp-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We have two methods for establishing a Certificate Authority (CA): the &lt;strong&gt;Command-Line Interface (CLI)&lt;/strong&gt; and &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To generate a CA via bash : &lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault write ssh/config/ca generate_signing_key=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Or to generate a CA via terraform : &lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_mount&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ssh_signer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;path&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="s"&gt;&amp;quot;ssh-client-signer&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;type&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="s"&gt;&amp;quot;ssh&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;description&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="s"&gt;&amp;quot;Sign ssh public keys&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_ssh_secret_backend_ca&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&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;vault_mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssh_signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;generate_signing_key&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="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pub_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;value&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;vault_ssh_secret_backend_ca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="adding-roles-authorization-for-developers-in-hashicorp-vault"&gt;Adding Roles Authorization for Developers in Hashicorp Vault&lt;a class="headerlink" href="#adding-roles-authorization-for-developers-in-hashicorp-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To effectively manage developer access to SSH certificates within Hashicorp Vault, we need to configure SSH authorizations for signing developper requests.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With terraform&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vault_ssh_secret_backend_role&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;name&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="s"&gt;&amp;quot;mgoulin&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;backend&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;vault_mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssh_signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;key_type&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="s"&gt;&amp;quot;ca&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;allow_user_certificates&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="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;default_extensions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-agent-forwarding&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-pty&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;permit-port-forwarding&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ttl&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="s"&gt;&amp;quot;1h&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="step-0-injecting-certificate-authority-into-aws-azure-and-gcp-servers"&gt;Step 0: Injecting Certificate Authority into AWS, Azure, and GCP Servers&lt;a class="headerlink" href="#step-0-injecting-certificate-authority-into-aws-azure-and-gcp-servers" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Injecting SSH public keys into cloud instances is a common practice to enable secure SSH access. Here&amp;rsquo;s how to do as SRE it on AWS, Azure, and GCP.&lt;/p&gt;
&lt;h4 id="aws"&gt;AWS&lt;a class="headerlink" href="#aws" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Add the public key to a key-pair object in EC2 service using the &lt;code&gt;aws ec2 import-key-pair&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo &amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot; &amp;gt; /tmp/authorized_keys.txt
aws ec2 import-key-pair --key-name &amp;quot;dev-key&amp;quot; --public-key-material fileb:///tmp/authorized_keys.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="gcp"&gt;GCP&lt;a class="headerlink" href="#gcp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Add the public key to the instance metadata using the &lt;code&gt;gcloud compute project-info add-metadata&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$USERNAME:cert-authority $(terraform output -r pub_key)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="azure"&gt;Azure&lt;a class="headerlink" href="#azure" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Add the public key to the instance metadata using the &lt;code&gt;az sshkey&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo &amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot; &amp;gt; /tmp/authorized_keys.txt
az sshkey create --name &amp;quot;dev-key&amp;quot; --public-key &amp;quot;@/tmp/authorized_keys.txt&amp;quot; --resource-group &amp;quot;myResourceGroup&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="custom-onprem-instances"&gt;Custom OnPrem Instances&lt;a class="headerlink" href="#custom-onprem-instances" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Add the public key to the instance as root by adding the following line to &lt;code&gt;/home/$USERNAME/authorized_keys&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cert-authority $(vault read -field public_key ssh-client-signer/config/ca)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;authorized_keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="how-developers-can-connect-with-ssh-signed-certificates"&gt;How developers can connect with ssh signed certificates&lt;a class="headerlink" href="#how-developers-can-connect-with-ssh-signed-certificates" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Traditionally, SSH connections relied on public and private key pairs for authentication. While effective, this method presents challenges in terms of key management, revocation, and access control.
With Certificate-Authority and HashicorpVautl, certificates bind a user&amp;rsquo;s identity to their public key, enabling stronger authentication and granular access control.&lt;/p&gt;
&lt;p&gt;With SSH certificates, developers can connect to remote servers securely without the need to manage private keys directly. The process involves:&lt;/p&gt;
&lt;h3 id="step-1-requesting-a-certificate-as-a-developper-i-want-to-sign-my-ssh-keys"&gt;Step 1: Requesting a Certificate: As a developper I want to sign my ssh keys&lt;a class="headerlink" href="#step-1-requesting-a-certificate-as-a-developper-i-want-to-sign-my-ssh-keys" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Developers request an SSH certificate from a centralized authority, often through a self-service portal or calling Vault API.&lt;/p&gt;
&lt;p&gt;It can be done with the following &lt;code&gt;vault&lt;/code&gt; command. During the request, developers provide their public key, typically stored as &lt;code&gt;id_rsa.pub&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault write -field=signed_key ssh/sign/dev \ 
    public_key=@$HOME/.ssh/id_rsa.pub &amp;gt; signed-cert.pub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="step-2-ssh-connection-as-a-developper-i-want-to-connect-on-my-instances-via-ssh"&gt;Step 2: SSH Connection: As a developper I want to connect on my instances via ssh&lt;a class="headerlink" href="#step-2-ssh-connection-as-a-developper-i-want-to-connect-on-my-instances-via-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Developers utilize the previously issued signed certificate : &lt;code&gt;signed-cert.pub&lt;/code&gt;, often referred to as a client certificate, to authenticate with the target server. This certificate, typically in PEM format, contains the developer&amp;rsquo;s public key signed by the trusted Certificate Authority (CA), which in this case is Vault.&lt;/p&gt;
&lt;p&gt;When connecting to the server, the SSH client presents the signed certificate. The server then verifies the certificate&amp;rsquo;s authenticity and validity against the CA&amp;rsquo;s public key. If successful, the connection is established, and the developer gains access to the server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh -i ./signed-cert.pub -v -i ~/.ssh/id_rsa dev@&amp;lt;target_server&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By adopting SSH certificates and leveraging a centralized certificate authority management tool such as Hashicorp Vault, organizations can significantly enhance their SSH security posture. This approach offers a robust solution to the challenges posed by traditional SSH key management, providing granular control, improved auditability, and streamlined access management.&lt;/p&gt;
&lt;p&gt;By transitioning to SSH certificates, organizations can mitigate risks associated with compromised keys, facilitate key rotation, and enforce strong access policies. This ultimately leads to a more secure and efficient SSH infrastructure.&lt;/p&gt;
&lt;p&gt;While implementing SSH certificates requires careful planning and configuration, the long-term benefits in terms of security and manageability far outweigh the initial investment.&lt;/p&gt;
&lt;p&gt;By following the guidelines outlined in this article, organizations can successfully deploy SSH certificates and reap the rewards of a more secure and controlled SSH environment.&lt;/p&gt;
&lt;h2 id="links"&gt;Links :&lt;a class="headerlink" href="#links" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/sec-creating_ssh_ca_certificate_signing-keys"&gt;RedHat documentation about ssh-certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/vault/docs/secrets/ssh/signed-ssh-certificates"&gt;Hashicorp Vault documentation about SSH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="devops"/><category term="devops"/><category term="ssh"/><category term="sysadmin"/></entry><entry><title>Sécurisez vos secret avec HashiCorp Vault : avec le PaaS GCP</title><link href="https://www.ops-chronicles.cloud/fr/hosting-hashicorpvault-on-cloudrun.html" rel="alternate"/><published>2024-07-22T00:00:00+02:00</published><updated>2024-07-22T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-07-22:/fr/hosting-hashicorpvault-on-cloudrun.html</id><summary type="html">&lt;p&gt;Heberger votre instance Vault en PaaS sur GCP : pour une gestion simple, évolutive et sécurisée des secrets de vos applications.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#prerequis"&gt;Prérequis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-1-creer-un-service-de-compte"&gt;Étape 1 : Créer un service de compte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-2-confirgurer-une-cle-kms"&gt;Étape 2 : Confirgurer une Clé KMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-3-configurer-un-bucket-de-stockage"&gt;Étape 3 : Configurer un bucket de stockage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-4-generer-un-fichier-de-configuration-pour-le-container"&gt;Étape 4 : Générer un fichier de configuration pour le container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-5-configurer-le-lancement-de-notre-container"&gt;Étape 5 : Configurer le lancement de notre container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etape-6-initialiser-le-vault"&gt;Étape 6 : Initialiser le Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.vaultproject.io/"&gt;HashiCorp Vault&lt;/a&gt; est un outil de gestion des secrets populaire qui permet de centraliser et de sécuriser les secrets sensibles tels que les mots de passe, les jetons d&amp;rsquo;API, les certificats et toute sorte de données confidentielles.
C&amp;rsquo;est donc un outil d&amp;rsquo;exploitation qui devient très rapidement indispensable. À titre personnel, j&amp;rsquo;en ai en particulier besoin pour mon article sur la gestion des clés SSH.&lt;/p&gt;
&lt;p&gt;Cependant même si l&amp;rsquo;éditeur de la solution le conseil de l&amp;rsquo;installer sur une plateforme IaaS (un cluster de machine), pour des raisons de coût je pense qu&amp;rsquo;on peut obtenir un très bon compromis sur une installation de type PaaS.
* IaaS apporterait la possibilité d&amp;rsquo;isoler au maximum les secrets du reste de l&amp;rsquo;infrastructure et comme beaucoup d&amp;rsquo;autres infrastructures sont dépendantes de ce service, cela permet aussi de la relancer en premier en cas de gros crash.
* PaaS lui évite les contraintes de gestion des mises à jour des serveurs, les pannes matérielles et offre une tarification à l&amp;rsquo;utilisation qui est très intéressante pour de petites infrastructures.&lt;/p&gt;
&lt;p&gt;Il n&amp;rsquo;y a cependant pas beaucoup de documentation selon moi sur l&amp;rsquo;utilisation de Hashicorp Vault sur du PaaS. Voici ici une aide sur comment déployer la solution Hashicorp sur une infrastructure PaaS :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CloudRun&lt;/strong&gt; pour l’hébergement du compute et le lancement de container à la demande. Vous êtes facturé en fonction du temps d&amp;rsquo;exécution de vos conteneurs, de la mémoire qu&amp;rsquo;ils utilisent et du nombre de requêtes qu&amp;rsquo;ils traitent. Lorsqu&amp;rsquo;il n&amp;rsquo;y a pas de requêtes vers vos containers, ceux-ci sont coupés et vous n&amp;rsquo;êtes pas facturé.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GCS&lt;/strong&gt; pour le stockage des secrets et la base de données. Vous êtes facturé en fonction de la quantité de données stockées dans vos buckets (négligeable, ici, car nous n&amp;rsquo;allons pas avoir des secrets de plusieurs Go) et le trafic entrant et sortant de vos buckets, y compris les requêtes API, les téléchargements et les téléchargements. Il existe des frais distincts pour le trafic standard et le trafic par API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KMS&lt;/strong&gt; pour le chiffrement des données sur le bucket (ce qu&amp;rsquo;on appelle &amp;ldquo;Seal&amp;rdquo; dans le language Vault). Vous êtes facturé pour chaque opération de chiffrement ou de déchiffrement effectuée à l&amp;rsquo;aide d&amp;rsquo;une clé KMS. Il existe différents tarifs pour les opérations de chiffrement symétrique et asymétrique.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En résumé, utiliser ces services permet de n&amp;rsquo;être facturé qu&amp;rsquo;as l&amp;rsquo;utilisation. Lectures de mot de passe au gestionnaire de secrets, décodage de flux, entraînera une facturation, mais si l&amp;rsquo;infrastructure &amp;ldquo;dort&amp;rdquo; elle ne coûtera rien.&lt;/p&gt;
&lt;p&gt;Le schéma ci-dessous resume comment le service peut fonctionner sur du PaaS : 
&lt;img alt="image" src="/images/2024-Hosting-HashicorpVault-on-cloudrun/architecture.png" /&gt;&lt;/p&gt;
&lt;p&gt;Voici maintenant comment installer HashiCorp Vault sur Cloud Run en utilisant un backend de stockage Cloud Storage (GCS) et un chiffrement Cloud Key Management Service (KMS) sur GCP.&lt;/p&gt;
&lt;h2 id="prerequis"&gt;Prérequis&lt;a class="headerlink" href="#prerequis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Avant de commencer, vous devez avoir les éléments suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un compte GCP avec les permissions appropriées&lt;/li&gt;
&lt;li&gt;Gcloud SDK installé et configuré (inclus dans la CLI de la cloud-console GCP)&lt;/li&gt;
&lt;li&gt;Terraform (inclus dans la CLI de la cloud-console GCP)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="etape-1-creer-un-service-de-compte"&gt;Étape 1 : Créer un service de compte&lt;a class="headerlink" href="#etape-1-creer-un-service-de-compte" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour que notre application puisse interagir avec les APIs dans GCP (accès aux objets du bucket, KMS, &amp;hellip;). Il sera utilisé par notre service CloudRUN et nous lui donnerons les droits en respectant scrupuleusement le principe des moindres-privilèges.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# service_account.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Create a new service account&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_service_account&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_sa&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;account_id&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="s2"&gt;&amp;quot;vault-${var.environment}-kapable-info-sa&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-2-confirgurer-une-cle-kms"&gt;Étape 2 : Confirgurer une Clé KMS&lt;a class="headerlink" href="#etape-2-confirgurer-une-cle-kms" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On initialise maintenant une clé KMS qui sera elle utilisé par hashicorp pour chiffrer les secrets avant de les déposer dans le bucket. Cette clé appelé &amp;ldquo;seal&amp;rdquo;, dans le verbiage Vault est une sécurité permettant de ne pas avoir &amp;ldquo;en clair&amp;rdquo; les données dans le bucket.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;utilisation d&amp;rsquo;un service comme KMS pour ce chiffrement permet de ne pas avoir a &amp;ldquo;imprimer&amp;rdquo;/&amp;rdquo;stocker&amp;rdquo; des &amp;ldquo;unseal-key&amp;rdquo;, ces chaines de caractères demandés au lancement de Vault pour décoder les données présente dans Vault.&lt;/p&gt;
&lt;p&gt;Voici le code terraform permettant de générer une tel clé dans GCP et de la rendre accessible a notre service-account préalablement généré.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# kms.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Generate random string for each ressource name&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_kms&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;byte_length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# A keyring to store our keys&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_key_ring&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;${var.environment}-vault-${random_id.vault_kms.hex}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The crypto-key ressources&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_crypto_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;${var.environment}-vault-${random_id.vault_kms.hex}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;key_ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;version_template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;algorithm&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="s2"&gt;&amp;quot;GOOGLE_SYMMETRIC_ENCRYPTION&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;protection_level&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="s2"&gt;&amp;quot;SOFTWARE&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# If we lost this key, then we won&amp;#39;t be able to read our data&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;lifecycle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;prevent_destroy&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="bp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Allow our application service account to use this key&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;seal&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;role&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="s2"&gt;&amp;quot;roles/cloudkms.cryptoKeyEncrypterDecrypter&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:${google_service_account.vault_sa.email}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Atach policy to our key&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_key_ring_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;seal&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;key_ring_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-3-configurer-un-bucket-de-stockage"&gt;Étape 3 : Configurer un bucket de stockage&lt;a class="headerlink" href="#etape-3-configurer-un-bucket-de-stockage" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Voici mon code standard pour la création d&amp;rsquo;un bucket de stockage&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# bucket.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Define vars&lt;/span&gt;
&lt;span class="n"&gt;locals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Generate random string for each ressource name&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_bucket&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;byte_length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create buckets&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_storage_bucket&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;buckets&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;${var.environment}-data-${each.key}-${random_id.vault_bucket.hex}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;force_destroy&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="bp"&gt;true&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;public_access_prevention&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="s2"&gt;&amp;quot;enforced&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Allow our application service account to use this bucket&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;role&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="s2"&gt;&amp;quot;roles/storage.admin&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:${google_service_account.vault_sa.email}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Atach policy to our key&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_storage_bucket_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-4-generer-un-fichier-de-configuration-pour-le-container"&gt;Étape 4 : Générer un fichier de configuration pour le container&lt;a class="headerlink" href="#etape-4-generer-un-fichier-de-configuration-pour-le-container" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Voici le fichier de configuration qui sera utilisé par le service Vault :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;vault-server.hcl

default_max_request_duration&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;90s&amp;quot;
disable_clustering&lt;span class="w"&gt;           &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;false
disable_mlock&lt;span class="w"&gt;                &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;true
ui&lt;span class="w"&gt;                           &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;true

log_file&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;/dev/stdout&amp;quot;

listener&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;tcp&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;[::]:8200&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;cluster_address&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;[::]:8201&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;tls_disable&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;true&amp;quot;
}

#&lt;span class="w"&gt; &lt;/span&gt;Utilisation&lt;span class="w"&gt; &lt;/span&gt;du&lt;span class="w"&gt; &lt;/span&gt;KMS&lt;span class="w"&gt; &lt;/span&gt;Vault
seal&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;gcpckms&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;key_ring&lt;span class="w"&gt;   &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;key_ring&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;crypto_key&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;crypto_key&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;region&lt;span class="w"&gt;     &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
}

#&lt;span class="w"&gt; &lt;/span&gt;Utilisation&lt;span class="w"&gt; &lt;/span&gt;du&lt;span class="w"&gt; &lt;/span&gt;stockage&lt;span class="w"&gt; &lt;/span&gt;GCS
storage&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;gcs&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;ha_enabled&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;true&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On injecte ensuite le fichier ci-dessus dans le service GCP Secret Manager :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Generate random string for each ressource name&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;byte_length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Secret to store config file&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_id&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="s2"&gt;&amp;quot;${var.environment}-vault-config-${random_id.vault-config.hex}&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;environment&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;replication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;user_managed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Inject config into our secret and complete config-file with KMS values&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret_version&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${path.module}/vault-server.hcl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;key_ring&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;crypto_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_crypto_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Atach policy to our secret&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;role&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="s2"&gt;&amp;quot;roles/secretmanager.secretAccessor&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:${google_service_account.vault_sa.email}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-5-configurer-le-lancement-de-notre-container"&gt;Étape 5 : Configurer le lancement de notre container&lt;a class="headerlink" href="#etape-5-configurer-le-lancement-de-notre-container" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Maintenant il est possible d&amp;rsquo;enfin lancer notre container pour cela on utilise un module perso et on prend soin de surcharger l&amp;rsquo;entrypoint du container. En effet par défaut le container utilise un mode &amp;ldquo;dev&amp;rdquo; pour se lancer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_service&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;source&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="s2"&gt;&amp;quot;../modules/http-container&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fqdn&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&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="s2"&gt;&amp;quot;prod&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-${var.environment}.kapable.info&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault.kapable.info&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;docker_image&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="s2"&gt;&amp;quot;hashicorp/vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allways_on&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="bp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;container_port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8200&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;cpus&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/bin/vault&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;server&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/vault/config.hcl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;container_env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SKIP_SETCAP&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_PROJECT&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_STORAGE_BUCKET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_LOG_LEVEL&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="s2"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SKIP_CHOWN&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_ADDR&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="s2"&gt;&amp;quot;http://127.0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_API_ADDR&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="s2"&gt;&amp;quot;http://127.0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_file&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="s2"&gt;&amp;quot;config.hcl&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;mount_path&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="s2"&gt;&amp;quot;/etc/vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;depends_on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="etape-6-initialiser-le-vault"&gt;Étape 6 : Initialiser le Vault&lt;a class="headerlink" href="#etape-6-initialiser-le-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Créez un répertoire pour stocker vos fichiers de configuration Vault.&lt;/li&gt;
&lt;li&gt;Téléchargez le binaire Vault depuis https://developer.hashicorp.com/vault/install.&lt;/li&gt;
&lt;li&gt;Déplacez le binaire Vault dans le répertoire que vous avez créé à l&amp;rsquo;étape 1.&lt;/li&gt;
&lt;li&gt;Initialisez Vault en exécutant la commande suivante :
 ̀``
vault operator init -key-shares=1 -key-threshold=1
```
Suivez les instructions à l&amp;rsquo;écran pour enregistrer les clés de déverrouillage et le jeton racine.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ce guide vous a montré comment déployer et configurer Hashicorp Vault sur Cloud Run en utilisant un backend de stockage Cloud Storage (GCS) et un chiffrement Cloud Key Management Service (KMS) sur GCP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avantages de cette approche :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplicité&lt;/strong&gt;: Déploiement et gestion simplifiés via Cloud Run et Terraform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Évolution&lt;/strong&gt;: Échelle automatique en fonction de la demande.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sécurité&lt;/strong&gt;: Chiffrement des secrets avec KMS et isolation des conteneurs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coût&lt;/strong&gt;: Paiement à la consommation pour une optimisation des coûts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Points importants à retenir:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assurez-vous de suivre scrupuleusement les instructions de sécurité pour protéger vos secrets.&lt;/li&gt;
&lt;li&gt;Utilisez des politiques IAM strictes pour limiter l&amp;rsquo;accès à vos ressources GCP.&lt;/li&gt;
&lt;li&gt;Surveillez l&amp;rsquo;utilisation de vos ressources et ajustez la configuration de votre infrastructure en fonction de vos besoins.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En conclusion, l&amp;rsquo;hébergement de Hashicorp Vault sur Cloud Run avec un backend de stockage GCS et un chiffrement KMS sur GCP offre une solution simple, évolutive et sécurisée pour la gestion des secrets de vos applications.&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="gcp"/></entry><entry><title>Securely Manage Secrets with HashiCorp Vault on Cloud Run: A GCP Guide</title><link href="https://www.ops-chronicles.cloud/hosting-hashicorpvault-on-cloudrun.html" rel="alternate"/><published>2024-07-22T00:00:00+02:00</published><updated>2024-07-22T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-07-22:/hosting-hashicorpvault-on-cloudrun.html</id><summary type="html">&lt;p&gt;Leveraging Google Cloud Platform&amp;rsquo;s Platform-as-a-Service (PaaS) capabilities, you can effortlessly deploy and manage your HashiCorp Vault instance&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#architecture"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#prerequisites"&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-1-create-a-service-account"&gt;Step 1: Create a Service Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-2-configure-kms-key-management-service"&gt;Step 2: Configure KMS (Key Management Service)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-3-configure-a-storage-backend"&gt;Step 3: Configure a Storage Backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-4-generate-a-configuration-file-for-the-container"&gt;Step 4: Generate a Configuration File for the Container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-5-configure-container-deployment"&gt;Step 5: Configure Container Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-6-initialize-vault"&gt;Step 6: Initialize Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.vaultproject.io/"&gt;HashiCorp Vault&lt;/a&gt; is a powerful tool for managing application secrets, providing a centralized platform to store, control, and secure sensitive information. It offers a comprehensive suite of features to safeguard your critical secrets, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secret Storage and Management&lt;/strong&gt;: Vault securely stores a wide range of secrets, including passwords, API keys, certificates, and authentication tokens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Granular Access Control&lt;/strong&gt;: Implement fine-grained access controls to manage who can access specific secrets, ensuring that only authorized users and applications have access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust Encryption&lt;/strong&gt;: Vault encrypts secrets using strong encryption algorithms and utilizes system-managed encryption keys to guarantee data security.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing and Logging&lt;/strong&gt;: Vault maintains a comprehensive audit log of all secret access and modifications, enabling you to monitor and audit activities effectively.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vault: A Pillar for Enhanced Security and Control&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Vault has become an indispensable tool for organizations seeking to strengthen their security posture and protect sensitive data. Its centralized approach to secrets management simplifies the process of securing and controlling access to critical information, mitigating the risk of unauthorized access and data breaches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplifying Vault Deployment and Management with GCP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While Vault offers immense value, deploying and managing a Vault instance can be a complex and time-consuming endeavor. This is where GCP PaaS simplify the process.&lt;/p&gt;
&lt;p&gt;By leveraging the power of Google Cloud Platform (GCP), you can further enhance the security and scalability of your Vault deployment. GCP&amp;rsquo;s robust cloud infrastructure and suite of security services provide an ideal foundation for hosting and managing Vault:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Run&lt;/strong&gt; is a fully managed serverless platform for deploying and running containerized applications. It provides an ideal environment for hosting Vault.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Storage (GCS)&lt;/strong&gt;: Utilize GCS as a secure and scalable backend storage solution for Vault data. GCS&amp;rsquo;s durability and high availability ensure that your secrets remain protected and accessible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Key Management Service (KMS)&lt;/strong&gt;: Employ KMS to manage and control cryptographic keys used for encrypting Vault data. KMS&amp;rsquo;s centralized key management capabilities safeguard your encryption keys and simplify key rotation processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Leveraging Cloud Run for a Secure and Scalable Vault Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By combining the strengths of HashiCorp Vault and Cloud Run, organizations can achieve a powerful and scalable solution for managing application secrets. Cloud Run eliminates the complexities of infrastructure management, allowing you to focus on the core functionalities of Vault.&lt;/p&gt;
&lt;p&gt;With Cloud Run, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deploy Vault quickly and easily&lt;/strong&gt;: Utilize Cloud Run&amp;rsquo;s simplified deployment process to get Vault up and running in no time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale Vault automatically&lt;/strong&gt;: Cloud Run automatically scales Vault instances based on demand, ensuring it can handle fluctuating workloads efficiently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize costs&lt;/strong&gt;: Benefit from Cloud Run&amp;rsquo;s pay-per-use model, paying only for the resources consumed by Vault.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;A Comprehensive Solution for Secure Secrets Management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By combining the strengths of HashiCorp Vault, GCS, KMS, and Cloud Run on GCP, organizations can achieve a comprehensive and secure solution for managing application secrets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralized Secret Storage&lt;/strong&gt;: Vault provides a centralized repository for storing and managing all types of secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust Encryption&lt;/strong&gt;: KMS ensures that secrets are encrypted with strong encryption keys and stored securely in GCS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fine-grained Access Control&lt;/strong&gt;: Vault enforces granular access controls to restrict access to secrets based on user identities and application permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Embrace a Secure and Streamlined Approach to Secrets Management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By adopting HashiCorp Vault on Cloud Run, you can streamline secrets management, enhance application security, and gain peace of mind knowing that your sensitive data is protected. Cloud Run&amp;rsquo;s serverless architecture and pay-per-use model further simplify the process, making it an ideal choice for organizations seeking a cost-effective and scalable solution.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;a class="headerlink" href="#architecture" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="image" src="/images/2024-Hosting-HashicorpVault-on-cloudrun/architecture.png" /&gt;&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;a class="headerlink" href="#prerequisites" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To embark on this journey of securing your application secrets with HashiCorp Vault on Cloud Run, ensure you have the following prerequisites in place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Platform (GCP) Account&lt;/strong&gt;: A GCP account is essential to access and utilize GCP&amp;rsquo;s services, including Cloud Run, GCS, and KMS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GCP Project&lt;/strong&gt;: A GCP project serves as the container for your GCP resources, including the Vault deployment. Ensure you have a designated project for this purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gcloud Command-Line Tool&lt;/strong&gt;: The gcloud command-line tool is a powerful interface for interacting with GCP services from your local machine. Install and configure gcloud to proceed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vault Command-Line Tool&lt;/strong&gt;: The vault command-line tool is the primary interface for interacting with HashiCorp Vault. Install and configure vault to manage your Vault instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-1-create-a-service-account"&gt;Step 1: Create a Service Account&lt;a class="headerlink" href="#step-1-create-a-service-account" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To enable Vault to interact with GCP APIs, we&amp;rsquo;ll create a &lt;strong&gt;service account&lt;/strong&gt;. A &lt;strong&gt;service account&lt;/strong&gt; is a special type of account used by applications or compute workloads, rather than a person. It allows applications to perform authorized actions on GCP resources, such as creating or deleting instances, or accessing Cloud Storage buckets.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# service_account.tf&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_service_account&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_sa&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;account_id&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="s2"&gt;&amp;quot;vault-${var.environment}-kpabl-info-sa&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-2-configure-kms-key-management-service"&gt;Step 2: Configure KMS (Key Management Service)&lt;a class="headerlink" href="#step-2-configure-kms-key-management-service" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this step, we&amp;rsquo;ll set up a Key Management Service (KMS) to encrypt our secrets. KMS is a crucial component for ensuring the security of your secrets. It provides a centralized and secure way to manage cryptographic keys. Vault will encrypt and decrypting secrets and confidential information before it&amp;rsquo;s stored in GCS.&lt;/p&gt;
&lt;p&gt;Also we need to create a :
* KMS key ring
* KMS cryptographic key &lt;/p&gt;
&lt;p&gt;Refer to the official documentation for detailed instructions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# kms.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Create a KMS&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_crypto_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_kms&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;crypto_key_id&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="s2"&gt;&amp;quot;vault-${var.environment}-kpabl-info-key&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;purpose&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="s2"&gt;&amp;quot;ENCRYPT_DECRYPT&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;version_template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;algorithm&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="s2"&gt;&amp;quot;GOOGLE_SYMMETRIC_ENCRYPTION&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;protection_level&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="s2"&gt;&amp;quot;SOFTWARE&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# If we lose this key, then we won&amp;#39;t be able to read our data&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;lifecycle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;prevent_destroy&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="bp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a Keyring&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_key_ring&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_keyring&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;vault-${var.environment}-kpabl-info-keyring&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Associate the Keyring with a KMS&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_kms_crypto_key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_kms_keyring&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;crypto_key_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_crypto_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_kms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crypto_key_id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;key_ring_id&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_keyring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_ring_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-3-configure-a-storage-backend"&gt;Step 3: Configure a Storage Backend&lt;a class="headerlink" href="#step-3-configure-a-storage-backend" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this step, we&amp;rsquo;ll configure a storage backend using Google Cloud Storage (GCS) to securely store your Vault secrets. GCS provides a scalable, durable, and highly available object storage solution, making it an ideal choice for storing sensitive information like secrets.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# backend.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Create a Bucket&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_storage_bucket&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_bucket&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&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="s2"&gt;&amp;quot;vault-${var.environment}-kpabl-info-bucket&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;force_destroy&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="bp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;storage_class&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="s2"&gt;&amp;quot;STANDARD&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Give the service account access to the bucket&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_storage_bucket_iam_member&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_bucket_sa&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;role&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="s2"&gt;&amp;quot;roles/storage.admin&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;member&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="s2"&gt;&amp;quot;serviceAccount:${google_service_account.vault_sa.email}&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you&amp;rsquo;ve created a GCS bucket, we&amp;rsquo;ll need to configure Vault to use it as the storage backend. This will involve setting up the necessary Vault configuration and policies to enable secure storage of secrets in your GCS bucket.&lt;/p&gt;
&lt;h2 id="step-4-generate-a-configuration-file-for-the-container"&gt;Step 4: Generate a Configuration File for the Container&lt;a class="headerlink" href="#step-4-generate-a-configuration-file-for-the-container" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With all the essential components in place, we&amp;rsquo;ll now generate a configuration file that will guide the containerized Vault application. This file will define the parameters and settings required for Vault to operate effectively within our GCP environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;vault-server.hcl

default_max_request_duration&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;90s&amp;quot;
disable_clustering&lt;span class="w"&gt;           &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;false
disable_mlock&lt;span class="w"&gt;                &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;true
ui&lt;span class="w"&gt;                           &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;true

log_file&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;/dev/stdout&amp;quot;

listener&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;tcp&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;[::]:8200&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;cluster_address&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;[::]:8201&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;tls_disable&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;true&amp;quot;
}

seal&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;gcpckms&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;key_ring&lt;span class="w"&gt;   &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;key_ring&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;crypto_key&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;crypto_key&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
&lt;span class="w"&gt;  &lt;/span&gt;region&lt;span class="w"&gt;     &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;
}

storage&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;gcs&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;  &lt;/span&gt;ha_enabled&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;true&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To further enhance security, we&amp;rsquo;ll leverage Google Cloud Secret Manager (SM) to store and manage the sensitive Vault configuration file. SM provides a centralized and secure vault for storing sensitive information like configuration data, ensuring that your Vault configuration remains protected and accessible only to authorized entities.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.tf&lt;/span&gt;

&lt;span class="c1"&gt;# Generate random string for each ressource name&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;byte_length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Secret to store config file&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_id&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="s2"&gt;&amp;quot;${var.environment}-vault-config-${random_id.vault-config.hex}&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;environment&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;replication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;user_managed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;location&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="s2"&gt;&amp;quot;europe-west1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Inject config into our secret and complete config-file with KMS values&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret_version&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${path.module}/vault-server.hcl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;key_ring&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;crypto_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_crypto_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Atach policy to our secret&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;role&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="s2"&gt;&amp;quot;roles/secretmanager.secretAccessor&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;serviceAccount:${google_service_account.vault_sa.email}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google_secret_manager_secret_iam_policy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_config&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-5-configure-container-deployment"&gt;Step 5: Configure Container Deployment&lt;a class="headerlink" href="#step-5-configure-container-deployment" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have all the components in place, it&amp;rsquo;s time to deploy the containerized Vault application using Cloud Run. However, we&amp;rsquo;ll make some adjustments to ensure Vault runs in a secure mode rather than the default &amp;ldquo;dev&amp;rdquo; mode.
By default, the official Vault Docker image runs in &amp;ldquo;dev&amp;rdquo; mode, which is not suitable for production environments. To operate Vault in a secure manner, we need to override the default entrypoint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault_service&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;source&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="s2"&gt;&amp;quot;../modules/http-container&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;project&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fqdn&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&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="s2"&gt;&amp;quot;prod&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-${var.environment}.kapable.info&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault.kapable.info&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;docker_image&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="s2"&gt;&amp;quot;hashicorp/vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allways_on&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="bp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;container_port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8200&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;cpus&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/bin/vault&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;server&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/vault/config.hcl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;container_env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SKIP_SETCAP&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_PROJECT&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="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;GOOGLE_STORAGE_BUCKET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_storage_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_LOG_LEVEL&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="s2"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SKIP_CHOWN&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_ADDR&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="s2"&gt;&amp;quot;http://127.0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;VAULT_API_ADDR&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="s2"&gt;&amp;quot;http://127.0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;google_secret_manager_secret_version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;secret_file&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="s2"&gt;&amp;quot;config.hcl&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;mount_path&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="s2"&gt;&amp;quot;/etc/vault&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;depends_on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;google_kms_key_ring_iam_policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-6-initialize-vault"&gt;Step 6: Initialize Vault&lt;a class="headerlink" href="#step-6-initialize-vault" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With Vault successfully deployed and configured, it&amp;rsquo;s time to initialize it to enable secret management capabilities. This involves creating a storage directory, downloading the Vault binary, and executing the initialization command.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create a Storage Directory&lt;/strong&gt;: Create a directory to store your Vault configuration files. For example, create a directory named vault-config in your home directory:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Initialize Vault&lt;/strong&gt;: Navigate to the directory containing the Vault binary and execute the initialization command:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow On-screen Instructions&lt;/strong&gt;: The initialization process will generate five key shares and a root token. Carefully record and store these key shares and the root token in a secure location. You will need these to unseal and recover Vault if necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault operator init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This comprehensive guide has walked you through the process of deploying and configuring Hashicorp Vault on Cloud Run, leveraging a Cloud Storage (GCS) storage backend and Cloud Key Management Service (KMS) encryption on GCP. By following these steps, you have successfully established a secure and scalable solution for managing your secrets in the GCP environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Benefits of this Approach:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: Streamlined deployment and management through Cloud Run and Terraform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Automatic scaling based on demand, ensuring your infrastructure can handle fluctuating workloads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Robust security measures, including encryption of secrets with KMS and container isolation, safeguard your sensitive data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-Effectiveness&lt;/strong&gt;: Pay-as-you-go model optimizes resource utilization and minimizes unnecessary expenses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Essential Considerations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Adhere to Security Guidelines&lt;/strong&gt;: Strictly follow security best practices to protect your secrets from unauthorized access and potential breaches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement IAM Policies&lt;/strong&gt;: Enforce stringent IAM policies to restrict access to your GCP resources, ensuring only authorized users can manage your infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Resource Usage&lt;/strong&gt;: Continuously monitor resource utilization and adjust your infrastructure configuration as needed to meet evolving demands.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By hosting Hashicorp Vault on Cloud Run with a GCS storage backend and KMS encryption on GCP, you have gained a robust, scalable, and secure solution for managing your application secrets. This approach offers a streamlined deployment process, cost-effective operation, and enhanced security, making it an ideal choice for organizations seeking efficient and reliable secret management in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust in GCP&amp;rsquo;s KMS Service and Confidentiality:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is important to note that this model relies on trust in GCP&amp;rsquo;s KMS service and its commitment to confidentiality. By choosing to encrypt your secrets with KMS, you are entrusting GCP with the protection of your sensitive data. Therefore, it is crucial to carefully evaluate GCP&amp;rsquo;s security practices and ensure that they align with your organization&amp;rsquo;s risk tolerance and compliance requirements.&lt;/p&gt;
&lt;p&gt;This model is suitable for organizations that have a high level of trust in GCP&amp;rsquo;s security practices and are comfortable with entrusting their secrets to a third-party service.&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="gcp"/></entry><entry><title>La quête du Graal Devops : Concilier scaling à zéro et disponibilité immédiate</title><link href="https://www.ops-chronicles.cloud/fr/scaleto0-with-event-driven-ansible-and-istio.html" rel="alternate"/><published>2024-06-02T00:00:00+02:00</published><updated>2024-06-02T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-06-02:/fr/scaleto0-with-event-driven-ansible-and-istio.html</id><summary type="html">&lt;p&gt;Dans le monde impitoyable du cloud, chaque ressource inutilisée représente un coût inutile. Que ce soit les environnements de recette le weekend ou les applications utilisés que ponctuelement, il y a autant de couts a optimiser. La solution : le scaling à zéro des deploiements Kubernetes, bien que séduisant pour optimiser les coûts, il se traduira souvent par une expérience utilisateur dégradée avec des applications indisponibles. Alors, comment concilier économies et réactivité ?&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#comprendre-le-probleme"&gt;Comprendre le Problème&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#la-solution-autoscaling-pilote-par-les-evenements"&gt;La Solution : Autoscaling piloté par les Événements&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#fonctionnement-du-systeme"&gt;Fonctionnement du Système&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#mise-en-place-de-la-plateforme"&gt;Mise en place de la plateforme&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#installation-de-kubernetesistio"&gt;Installation de kubernetes+istio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#installation-du-monitoring-prometheus-alertmanager"&gt;Installation du monitoring : Prometheus / Alertmanager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#installation-dune-application"&gt;Installation d&amp;rsquo;une application&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#event-drivent-ansible"&gt;Event drivent Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mise-en-oeuvre-de-eda-dans-le-cluster"&gt;Mise en oeuvre de EDA dans le cluster&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#construction-dun-playbook-pour-le-scaling"&gt;Construction d&amp;rsquo;un playbook pour le scaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#integration-avec-la-supervision"&gt;Intégration avec la supervision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#creation-de-lalerte-de-supervision"&gt;Création de l&amp;rsquo;alerte de supervision&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#test-du-scaling-a-zero"&gt;Test du scaling à zéro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#principaux-avantages-de-cette-approche"&gt;Principaux avantages de cette approche&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#questions-pour-reflexion"&gt;Questions pour réflexion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ressources-supplementaires"&gt;Ressources supplémentaires&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Dans le monde impitoyable du cloud, chaque ressource inutilisée représente un coût inutile. Le scaling à zéro des pods Kubernetes, bien que séduisant pour optimiser les coûts, se traduit souvent par une expérience utilisateur dégradée avec des applications indisponibles. Comment alors concilier économies et réactivité ?&lt;/p&gt;
&lt;p&gt;Oubliez les compromis, et adoptez une approche &lt;strong&gt;pilotée par les événements&lt;/strong&gt;, en combinant la puissance d&amp;rsquo;&lt;strong&gt;Event Driven Ansible&lt;/strong&gt; à la finesse d&amp;rsquo;outils tels qu&amp;rsquo;&lt;strong&gt;Istio, Prometheus et Alertmanager&lt;/strong&gt;. Imaginez un système capable de :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Détecter instantanément&lt;/strong&gt; les premiers appels HTTP vers vos applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Déclencher automatiquement&lt;/strong&gt; le lancement de vos pods Kubernetes pour répondre à la demande.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestrer la mise à l&amp;rsquo;échelle&lt;/strong&gt; de vos ressources de manière dynamique et transparente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Réduire vos coûts d&amp;rsquo;infrastructure&lt;/strong&gt; en stoppant les pods inactifs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Plus besoin d&amp;rsquo;attendre le redémarrage de vos applications, la disponibilité est quasi-immédiate, et vos utilisateurs ne se rendent compte de rien !&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dans cet article, nous allons explorer en détail comment mettre en place cette solution élégante et efficace. Vous découvrirez :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les mécanismes de détection d&amp;rsquo;événements basés sur le trafic HTTP.&lt;/li&gt;
&lt;li&gt;La configuration d&amp;rsquo;alertes intelligentes pour déclencher l&amp;rsquo;autoscaling.&lt;/li&gt;
&lt;li&gt;La mise en œuvre de playbooks Ansible pour automatiser le scaling de vos déploiements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Préparez-vous à dire adieu aux compromis et à entrer dans l&amp;rsquo;ère du scaling à zéro intelligent !&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="comprendre-le-probleme"&gt;Comprendre le Problème&lt;a class="headerlink" href="#comprendre-le-probleme" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Considérons un environnement de développement ou de test. Un &amp;ldquo;opérateur&amp;rdquo; pourrait allumer l&amp;rsquo;environnement avant son utilisation et l&amp;rsquo;éteindre (scaling à zéro) après. Cependant, l&amp;rsquo;approche DevOps vise à automatiser ces processus pour éliminer les interventions manuelles.&lt;/p&gt;
&lt;p&gt;Le défi réside dans la recherche d&amp;rsquo;une solution automatique pour gérer le scaling de manière dynamique, en s&amp;rsquo;adaptant aux fluctuations de la demande. Nous voulons que l&amp;rsquo;environnement soit disponible immédiatement lorsqu&amp;rsquo;il est nécessaire, sans que les utilisateurs ne subissent de délai d&amp;rsquo;attente.&lt;/p&gt;
&lt;h3 id="la-solution-autoscaling-pilote-par-les-evenements"&gt;La Solution : Autoscaling piloté par les Événements&lt;a class="headerlink" href="#la-solution-autoscaling-pilote-par-les-evenements" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;L&amp;rsquo;autoscaling basé sur les événements est la clé pour concilier économies de ressources et disponibilité immédiate. L&amp;rsquo;idée est de surveiller le trafic HTTP vers les pods Kubernetes et de déclencher automatiquement le scaling (augmenter le nombre de pods) lorsque les demandes ne trouvent pas de destinations.&lt;/p&gt;
&lt;h4 id="fonctionnement-du-systeme"&gt;Fonctionnement du Système&lt;a class="headerlink" href="#fonctionnement-du-systeme" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Détection des Événements:&lt;/strong&gt; Istio, un outil de gestion de trafic, surveille le trafic HTTP vers vos pods Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collecte de Données:&lt;/strong&gt; Prometheus, un système de monitoring, collecte les données de trafic provenant d&amp;rsquo;Istio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Génération d&amp;rsquo;Alertes:&lt;/strong&gt; Alertmanager analyse les données de Prometheus et déclenche des alertes lorsque le trafic HTTP dépasse un seuil défini.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action Automatique:&lt;/strong&gt; Les alertes d&amp;rsquo;Alertmanager déclenchent des playbooks Ansible via Event Driven Ansible, qui lancent automatiquement les pods Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arrêt Automatique:&lt;/strong&gt; Lorsqu&amp;rsquo;il n&amp;rsquo;y a plus de trafic HTTP pendant une période définie, Alertmanager déclenche un autre playbook Ansible qui arrête les pods.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Architecture" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_archi0.drawio.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sequences" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_sequences.png" /&gt;&lt;/p&gt;
&lt;h2 id="mise-en-place-de-la-plateforme"&gt;Mise en place de la plateforme&lt;a class="headerlink" href="#mise-en-place-de-la-plateforme" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="installation-de-kubernetesistio"&gt;Installation de kubernetes+istio&lt;a class="headerlink" href="#installation-de-kubernetesistio" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nous utiliserons Google Kubernetes Engine (GKE) avec Node-Autoscaling, qui s&amp;rsquo;occupe automatiquement de la mise à l&amp;rsquo;échelle des nœuds. Cela permet d&amp;rsquo;optimiser les ressources facturables en fonction du nombre de pods en cours d&amp;rsquo;exécution.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;compute&lt;span class="w"&gt; &lt;/span&gt;networks&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-vpc&lt;span class="w"&gt; &lt;/span&gt;--project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;clusters&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--enable-autoscaling&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--num-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--min-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--max-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--network&lt;span class="w"&gt; &lt;/span&gt;scaleto0-vpc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--release-channel&lt;span class="o"&gt;=&lt;/span&gt;regular&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--zone&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;-b
gcloud&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;clusters&lt;span class="w"&gt; &lt;/span&gt;get-credentials&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--zone&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--project&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, nous installerons Istio pour la gestion du trafic :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://istio.io/downloadIstio&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;istio-&amp;lt;&amp;lt;VERSION&amp;gt;&amp;gt;
./bin/istioctl&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--set&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo&lt;span class="w"&gt; &lt;/span&gt;-y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vérifiez l&amp;rsquo;installation d&amp;rsquo;Istio :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Vérifier l&amp;#39;état des pods&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system

&lt;span class="c1"&gt;# Vérifier l&amp;#39;état des services et s&amp;#39;assurer qu&amp;#39;un LB externe est créé pour istio-ingressgateway&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;svc&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="installation-du-monitoring-prometheus-alertmanager"&gt;Installation du monitoring : Prometheus / Alertmanager&lt;a class="headerlink" href="#installation-du-monitoring-prometheus-alertmanager" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Dans cet article nous réaliserons l&amp;rsquo;installation d&amp;rsquo;un système de surveillance minimaliste pour Istio, en se concentrant uniquement sur la surveillance d&amp;rsquo;Istio lui-même, sans surveiller les serveurs, les CPU ou la RAM. Nous utiliserons un chart Helm pour une installation simplifiée et maintiendrons la cohérence avec le reste de l&amp;rsquo;article en utilisant l&amp;rsquo;espace de noms &lt;code&gt;istio-system&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;helm&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;prometheus&lt;span class="w"&gt; &lt;/span&gt;prometheus-community/prometheus&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;# prometheus_values.yaml&lt;/span&gt;
&lt;span class="s"&gt;rbac:&lt;/span&gt;
&lt;span class="s"&gt;  create: true&lt;/span&gt;
&lt;span class="s"&gt;configmapReload:&lt;/span&gt;
&lt;span class="s"&gt;  prometheus:&lt;/span&gt;
&lt;span class="s"&gt;    enabled: false&lt;/span&gt;
&lt;span class="s"&gt;server:&lt;/span&gt;
&lt;span class="s"&gt;  namespaces:&lt;/span&gt;
&lt;span class="s"&gt;    - istio-system&lt;/span&gt;
&lt;span class="s"&gt;  resources:&lt;/span&gt;
&lt;span class="s"&gt;    limits:&lt;/span&gt;
&lt;span class="s"&gt;      cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;      memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;    requests:&lt;/span&gt;
&lt;span class="s"&gt;      cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;      memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;  global:&lt;/span&gt;
&lt;span class="s"&gt;    scrape_interval: 1s&lt;/span&gt;
&lt;span class="s"&gt;    scrape_timeout: 1s&lt;/span&gt;
&lt;span class="s"&gt;    evaluation_interval: 2s&lt;/span&gt;
&lt;span class="s"&gt;serverFiles:&lt;/span&gt;
&lt;span class="s"&gt;  prometheus.yml:&lt;/span&gt;
&lt;span class="s"&gt;    rule_files:&lt;/span&gt;
&lt;span class="s"&gt;      - /etc/config/recording_rules.yml&lt;/span&gt;
&lt;span class="s"&gt;      - /etc/config/alerting_rules.yml&lt;/span&gt;
&lt;span class="s"&gt;    scrape_configs:&lt;/span&gt;
&lt;span class="s"&gt;      - job_name: &amp;#39;istiod&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;        kubernetes_sd_configs:&lt;/span&gt;
&lt;span class="s"&gt;          - role: pod&lt;/span&gt;
&lt;span class="s"&gt;        relabel_configs:&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]&lt;/span&gt;
&lt;span class="s"&gt;            action: keep&lt;/span&gt;
&lt;span class="s"&gt;            regex: true&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape_slow]&lt;/span&gt;
&lt;span class="s"&gt;            action: drop&lt;/span&gt;
&lt;span class="s"&gt;            regex: true&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            regex: (https?)&lt;/span&gt;
&lt;span class="s"&gt;            target_label: __scheme__&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            target_label: __metrics_path__&lt;/span&gt;
&lt;span class="s"&gt;            regex: (.+)&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            regex: (\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})&lt;/span&gt;
&lt;span class="s"&gt;            replacement: &amp;#39;[\$2]:\$1&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;            target_label: __address__&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            regex: (\\d+);((([0-9]+?)(\\.|$)){4})&lt;/span&gt;
&lt;span class="s"&gt;            replacement: \$2:\$1&lt;/span&gt;
&lt;span class="s"&gt;            target_label: __address__&lt;/span&gt;
&lt;span class="s"&gt;          - action: labelmap&lt;/span&gt;
&lt;span class="s"&gt;            regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)&lt;/span&gt;
&lt;span class="s"&gt;            replacement: __param_$1&lt;/span&gt;
&lt;span class="s"&gt;          - action: labelmap&lt;/span&gt;
&lt;span class="s"&gt;            regex: __meta_kubernetes_pod_label_(.+)&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_namespace]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            target_label: namespace&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_name]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            target_label: pod&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_phase]&lt;/span&gt;
&lt;span class="s"&gt;            regex: Pending|Succeeded|Failed|Completed&lt;/span&gt;
&lt;span class="s"&gt;            action: drop&lt;/span&gt;
&lt;span class="s"&gt;          - source_labels: [__meta_kubernetes_pod_node_name]&lt;/span&gt;
&lt;span class="s"&gt;            action: replace&lt;/span&gt;
&lt;span class="s"&gt;            target_label: node&lt;/span&gt;
&lt;span class="s"&gt;alertmanager:&lt;/span&gt;
&lt;span class="s"&gt;  enabled: true&lt;/span&gt;

&lt;span class="s"&gt;kube-state-metrics:&lt;/span&gt;
&lt;span class="s"&gt;  enabled: false&lt;/span&gt;

&lt;span class="s"&gt;prometheus-node-exporter:&lt;/span&gt;
&lt;span class="s"&gt;  enabled: false&lt;/span&gt;

&lt;span class="s"&gt;prometheus-pushgateway:&lt;/span&gt;
&lt;span class="s"&gt;  enabled: false&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrole&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--verb&lt;span class="o"&gt;=&lt;/span&gt;get,list,watch&lt;span class="w"&gt; &lt;/span&gt;--resource&lt;span class="o"&gt;=&lt;/span&gt;pods,endpoints,services,nodes,namespaces&lt;span class="w"&gt;                                       &lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrolebinding&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--clusterrole&lt;span class="o"&gt;=&lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--serviceaccount&lt;span class="o"&gt;=&lt;/span&gt;istio-system:prometheus-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour tester le système de monitoring, nous pouvons utiliser la commande suivante afin de rediriger http://localhost:9090 vers le pod Prometheus :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl --namespace istio-system port-forward $(kubectl get pods --namespace istio-system -l &amp;quot;app.kubernetes.io/name=prometheus,app.kubernetes.io/instance=prometheus&amp;quot; -o jsonpath=&amp;quot;{.items[0].metadata.name}&amp;quot;) 9090
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ouvrez ensuite un navigateur web à l&amp;rsquo;adresse : http://localhost:9090&lt;/p&gt;
&lt;p&gt;&lt;img alt="Prometheus" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_prometheus0.png" /&gt;&lt;/p&gt;
&lt;h3 id="installation-dune-application"&gt;Installation d&amp;rsquo;une application&lt;a class="headerlink" href="#installation-dune-application" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pour simuler une application dans notre démo, nous allons utiliser &amp;ldquo;whoami&amp;rdquo; qui se contente de renvoyer la requête HTTP qu&amp;rsquo;il reçoit.
Si cela fonctionne avec &amp;ldquo;whoami&amp;rdquo;, on pourra appliquer le meme principe sur tous les déploiements du cluster Kubernetes.&lt;/p&gt;
&lt;p&gt;Créons un fichier YAML pour le déploiement, comme suit :&lt;/p&gt;
&lt;p&gt;Ce code YAML décrit la configuration d&amp;rsquo;un déploiement Kubernetes pour une application nommée &amp;ldquo;whoami&amp;rdquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Déploiement &amp;ldquo;whoami&amp;rdquo;&lt;/strong&gt;: Définit un déploiement avec le nom &amp;ldquo;whoami&amp;rdquo; qui utilise l&amp;rsquo;image Docker &amp;ldquo;traefik/whoami:latest&amp;rdquo;. Il indique que le déploiement devrait avoir 1 &amp;ldquo;replica&amp;rdquo; (scaling à 1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service &amp;ldquo;whoami&amp;rdquo;&lt;/strong&gt;: Définit un service Kubernetes nommé &amp;ldquo;whoami&amp;rdquo; qui se connecte à des pods avec l&amp;rsquo;étiquette &amp;ldquo;app: whoami&amp;rdquo;. Expose le port 80 du service, qui se redirige vers le port 80 du conteneur.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl create ns whoami&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl label namespace whoami istio-injection=enabled&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl apply -n whoami -f - &amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# whoami_deployment.yaml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;apps/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Deployment&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sidecar.istio.io/proxyCPU&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;500m&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sidecar.istio.io/proxyMemory&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;512Mi&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;master&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;traefik/whoami:latest&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500m&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;512Mi&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500m&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;512Mi&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Service&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et exposons ce service via Istio :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl apply -n whoami -f - &amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;apiVersion&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networking.istio.io/v1alpha3&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami-gateway&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# The selector matches the ingress gateway pod labels.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# If you installed Istio using Helm following the standard documentation, this would be &amp;quot;istio=ingress&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;istio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ingressgateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networking.istio.io/v1alpha3&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;VirtualService&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;gateways&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami-gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;15&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;perTryTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2s&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test de l&amp;rsquo;application&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;INGRESS_HOST&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubectl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ingressgateway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{.status.loadBalancer.ingress[0].ip}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;HHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scaleto0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://$INGRESS_HOST/&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GMT&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;570&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;envoy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;upstream&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1073&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;envoy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Downscaling de l&amp;rsquo;application&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;scale&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;--replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L&amp;rsquo;appliation une fois coupé (down) ne devrait plus fonctionner : Indisponnibilité de l&amp;rsquo;application&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;HHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;whoami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleto0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://$INGRESS_HOST:$INGRESS_PORT/&amp;quot;&lt;/span&gt;

&lt;span class="nx"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m m-Double"&gt;1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Unavailable&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;
&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Thu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GMT&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;envoy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Prometheus" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_app503.png" /&gt;&lt;/p&gt;
&lt;h2 id="event-drivent-ansible"&gt;Event drivent Ansible&lt;a class="headerlink" href="#event-drivent-ansible" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En suivant la documentation de Redhat, nous allons construire une image de conteneur pour servir notre rulebook. Voici le fichier Dockerfile qui crée un conteneur pour lancer la commande &amp;ldquo;ansible-rulebook&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Dockerfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;FROM debian:latest&lt;/span&gt;

&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;    apt-get --assume-yes install openjdk-17-jdk python3-pip python3-psycopg &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;    pip3 install ansible ansible-rulebook ansible-runner kubernetes --break-system-packages&lt;/span&gt;

&lt;span class="s"&gt;RUN mkdir /app &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;    useradd -u 1001 -ms /bin/bash ansible &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;    chown ansible:root /app&lt;/span&gt;

&lt;span class="s"&gt;WORKDIR /app&lt;/span&gt;
&lt;span class="s"&gt;USER ansible&lt;/span&gt;

&lt;span class="s"&gt;RUN ansible-galaxy collection install ansible.eda&lt;/span&gt;

&lt;span class="s"&gt;ENTRYPOINT [&amp;quot;ansible-rulebook&amp;quot;, &amp;quot;-r&amp;quot;, &amp;quot;/app/rules.yaml&amp;quot;, &amp;quot;-i&amp;quot;, &amp;quot;/app/inventory.yml&amp;quot;, &amp;quot;-S&amp;quot;, &amp;quot;/app&amp;quot;]&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous pouvons construire et enregistrer notre image dans artifact-registry de Google :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creation du repository&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;artifacts&lt;span class="w"&gt; &lt;/span&gt;repositories&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo-repo&lt;span class="w"&gt; &lt;/span&gt;--repository-format&lt;span class="o"&gt;=&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--location&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--description&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Docker scaleto0 Demo repository&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Creation de l&amp;#39;image&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;builds&lt;span class="w"&gt; &lt;/span&gt;submit&lt;span class="w"&gt; &lt;/span&gt;--region&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--tag&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;-docker.pkg.dev/&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;/scaleto0-demo-repo/rulebook-scaleto0-demo:v1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce conteneur sera déployé dans Kubernetes. Nous allons surcharger le fichier &lt;code&gt;/app/rules.yaml&lt;/code&gt; pour y inclure nos règles et playbooks, notamment pour le scaling.&lt;/p&gt;
&lt;h2 id="mise-en-oeuvre-de-eda-dans-le-cluster"&gt;Mise en oeuvre de EDA dans le cluster&lt;a class="headerlink" href="#mise-en-oeuvre-de-eda-dans-le-cluster" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="construction-dun-playbook-pour-le-scaling"&gt;Construction d&amp;rsquo;un playbook pour le scaling&lt;a class="headerlink" href="#construction-dun-playbook-pour-le-scaling" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ansible, un langage puissant pour la manipulation de composants d&amp;rsquo;infrastructure, nous permetant de créer un &amp;ldquo;playbook&amp;rdquo; qui permettra de scaller le déploiement Kubernetes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;scale-deployment.yaml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;- hosts: localhost&lt;/span&gt;
&lt;span class="s"&gt;  connection: local&lt;/span&gt;
&lt;span class="s"&gt;  gather_facts: false&lt;/span&gt;
&lt;span class="s"&gt;  tasks:&lt;/span&gt;
&lt;span class="s"&gt;  - name: Scale deployment up&lt;/span&gt;
&lt;span class="s"&gt;    kubernetes.core.k8s_scale:&lt;/span&gt;
&lt;span class="s"&gt;      api_version: v1&lt;/span&gt;
&lt;span class="s"&gt;      kind: Deployment&lt;/span&gt;
&lt;span class="s"&gt;      name: &amp;quot;{{ deployment_name }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;      namespace: &amp;quot;{{ namespace }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;      replicas: &amp;quot;{{ num_replicas }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;      wait_timeout: 60&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous pouvons tester le playbook avec la commande ci-dessous :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;deployment_name&amp;quot;:&amp;quot;whoami&amp;quot;, &amp;quot;namespace&amp;quot;:&amp;quot;whoami&amp;quot;, &amp;quot;num_replicas&amp;quot;: &amp;quot;1&amp;quot;}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scale-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, créons un fichier &lt;code&gt;rules.yaml&lt;/code&gt; pour définir l&amp;rsquo;interface entre le monitoring et le playbook de scaling.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;gather_facts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run_playbook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;extra_vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;deployment_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;num_replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="n"&gt;ungrouped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ansible_connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Injecter le playbook dans Kubernetes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;ns&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook-config&lt;span class="w"&gt; &lt;/span&gt;--from-file&lt;span class="o"&gt;=&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Démarrer le conteneur:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;# ansible-rulebook_deployment.yaml&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: apps/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: Deployment&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;spec:&lt;/span&gt;
&lt;span class="s"&gt;  selector:&lt;/span&gt;
&lt;span class="s"&gt;    matchLabels:&lt;/span&gt;
&lt;span class="s"&gt;      app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;  replicas: 1&lt;/span&gt;
&lt;span class="s"&gt;  template:&lt;/span&gt;
&lt;span class="s"&gt;    metadata:&lt;/span&gt;
&lt;span class="s"&gt;      labels:&lt;/span&gt;
&lt;span class="s"&gt;        app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;    spec:&lt;/span&gt;
&lt;span class="s"&gt;      containers:&lt;/span&gt;
&lt;span class="s"&gt;      - name: master&lt;/span&gt;
&lt;span class="s"&gt;        image: $REGION-docker.pkg.dev/$PROJECT_ID/scaleto0-demo-repo/rulebook-scaleto0-demo:v1&lt;/span&gt;
&lt;span class="s"&gt;        resources:&lt;/span&gt;
&lt;span class="s"&gt;          requests:&lt;/span&gt;
&lt;span class="s"&gt;            cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;            memory: 128Mi&lt;/span&gt;
&lt;span class="s"&gt;          limits:&lt;/span&gt;
&lt;span class="s"&gt;            cpu: 500m&lt;/span&gt;
&lt;span class="s"&gt;            memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;        ports:&lt;/span&gt;
&lt;span class="s"&gt;        - containerPort: 5000&lt;/span&gt;
&lt;span class="s"&gt;        volumeMounts:&lt;/span&gt;
&lt;span class="s"&gt;        - name: config&lt;/span&gt;
&lt;span class="s"&gt;          mountPath: /app&lt;/span&gt;
&lt;span class="s"&gt;      volumes:&lt;/span&gt;
&lt;span class="s"&gt;      - name: config&lt;/span&gt;
&lt;span class="s"&gt;        configMap:&lt;/span&gt;
&lt;span class="s"&gt;          name: ansible-rulebook-config&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: v1&lt;/span&gt;
&lt;span class="s"&gt;kind: Service&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  name: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;  labels:&lt;/span&gt;
&lt;span class="s"&gt;    app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;spec:&lt;/span&gt;
&lt;span class="s"&gt;  ports:&lt;/span&gt;
&lt;span class="s"&gt;  - port: 5000&lt;/span&gt;
&lt;span class="s"&gt;    targetPort: 5000&lt;/span&gt;
&lt;span class="s"&gt;  selector:&lt;/span&gt;
&lt;span class="s"&gt;    app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Accorder des droits à &lt;code&gt;ansible-rulebook&lt;/code&gt; pour interagir avec Kubernetes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;# ansible-rulebook_clusterrole.yaml&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;  creationTimestamp: &amp;quot;2024-05-31T19:01:36Z&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;  name: deployment-scaler&lt;/span&gt;
&lt;span class="s"&gt;rules:&lt;/span&gt;
&lt;span class="s"&gt;- apiGroups:&lt;/span&gt;
&lt;span class="s"&gt;  - apps&lt;/span&gt;
&lt;span class="s"&gt;  resources:&lt;/span&gt;
&lt;span class="s"&gt;  - deployments/scale&lt;/span&gt;
&lt;span class="s"&gt;  - deployments&lt;/span&gt;
&lt;span class="s"&gt;  verbs:&lt;/span&gt;
&lt;span class="s"&gt;  - get&lt;/span&gt;
&lt;span class="s"&gt;  - list&lt;/span&gt;
&lt;span class="s"&gt;  - patch&lt;/span&gt;
&lt;span class="s"&gt;  - update&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enfin, mapper le rôle ansible-rulebook au déploiement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrolebinding&lt;span class="w"&gt; &lt;/span&gt;deployment-scaler&lt;span class="w"&gt; &lt;/span&gt;--clusterrole&lt;span class="o"&gt;=&lt;/span&gt;deployment-scaler&lt;span class="w"&gt; &lt;/span&gt;--serviceaccount&lt;span class="o"&gt;=&lt;/span&gt;ansible-rulebook:default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="integration-avec-la-supervision"&gt;Intégration avec la supervision&lt;a class="headerlink" href="#integration-avec-la-supervision" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Il faut connecter Alertmanager à Ansible rulebook. Pour ce faire, mettons à jour la configuration d&amp;rsquo;Alertmanager pour qu&amp;rsquo;elle envoie ses alertes vers le rulebook.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;patch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;prometheus-alertmanager&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;merge&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;data:&lt;/span&gt;
&lt;span class="s2"&gt;  alertmanager.yml: |&lt;/span&gt;
&lt;span class="s2"&gt;    global: {}&lt;/span&gt;
&lt;span class="s2"&gt;    receivers:&lt;/span&gt;
&lt;span class="s2"&gt;    - name: default-receiver&lt;/span&gt;
&lt;span class="s2"&gt;      webhook_configs:&lt;/span&gt;
&lt;span class="s2"&gt;      - url: http://ansible-rulebook.ansible-rulebook.svc.cluster.local:5000/endpoint&lt;/span&gt;
&lt;span class="s2"&gt;    route:&lt;/span&gt;
&lt;span class="s2"&gt;      group_interval: 5s&lt;/span&gt;
&lt;span class="s2"&gt;      group_wait: 10s&lt;/span&gt;
&lt;span class="s2"&gt;      receiver: default-receiver&lt;/span&gt;
&lt;span class="s2"&gt;      repeat_interval: 5m&lt;/span&gt;
&lt;span class="s2"&gt;    templates:&lt;/span&gt;
&lt;span class="s2"&gt;    - /etc/alertmanager/*.tmpl&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Relancer les pods de monitoring pour prendre en compte le nouveau configmap :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n istio-system rollout restart statefulset/prometheus-alertmanager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="creation-de-lalerte-de-supervision"&gt;Création de l&amp;rsquo;alerte de supervision&lt;a class="headerlink" href="#creation-de-lalerte-de-supervision" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Créons une alerte de supervision qui détectera les erreurs 503 dans Prometheus et déclenchera le &amp;ldquo;rulebook&amp;rdquo; Ansible.&lt;/p&gt;
&lt;p&gt;Étant donné que Prometheus est installé avec Helm, nous mettrons à jour la configuration statique pour injecter l&amp;rsquo;alerte. Dans un environnement de production, les alertes seraient injectées via l&amp;rsquo;opérateur et les CRD &amp;ldquo;PrometheusRules&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;patch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;merge&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;data:&lt;/span&gt;
&lt;span class="s2"&gt;  alerting_rules.yml: |&lt;/span&gt;
&lt;span class="s2"&gt;    groups:&lt;/span&gt;
&lt;span class="s2"&gt;      - name: DeploymentDown&lt;/span&gt;
&lt;span class="s2"&gt;        rules:&lt;/span&gt;
&lt;span class="s2"&gt;          - alert: DeploymentDown&lt;/span&gt;
&lt;span class="s2"&gt;            expr: sum by (destination_service_name) (rate(istio_requests_total{response_code=\&amp;quot;503\&amp;quot;}[3s])) &amp;gt; 0&lt;/span&gt;
&lt;span class="s2"&gt;            for: 2s&lt;/span&gt;
&lt;span class="s2"&gt;            labels:&lt;/span&gt;
&lt;span class="s2"&gt;              severity: page&lt;/span&gt;
&lt;span class="s2"&gt;            annotations:&lt;/span&gt;
&lt;span class="s2"&gt;              description: &amp;#39;No upstream on {{ \$labels.destination_service_name }}&amp;#39;&lt;/span&gt;
&lt;span class="s2"&gt;              summary: &amp;#39;{{ \$labels.destination_service_name }} down&amp;#39;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Relancer les pods de monitoring pour prendre en compte le nouveau configmap :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n istio-system rollout restart deployment/prometheus-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Après avoir généré quelques appels vers l&amp;rsquo;application sans pods, nous devrions retrouver l&amp;rsquo;alerte dans Prometheus :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Architecture" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_alert0.png" /&gt;&lt;/p&gt;
&lt;h2 id="test-du-scaling-a-zero"&gt;Test du scaling à zéro&lt;a class="headerlink" href="#test-du-scaling-a-zero" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Test de base:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scénario:&lt;/strong&gt; Démarrez l&amp;rsquo;application &amp;ldquo;whoami&amp;rdquo; avec un seul pod et observez que l&amp;rsquo;application est accessible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accédez au service &amp;ldquo;whoami&amp;rdquo; via un navigateur ou curl : Verifier que le résultat est correct et garder la fenetre ouverte pour visualiser le status de l&amp;rsquo;application.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;INGRESS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;svc&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;istio-ingressgateway&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{.status.loadBalancer.ingress[0].ip}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
watch&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;-HHost:whoami.scaleto0.demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="nv"&gt;$INGRESS_HOST&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Watch App" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_watch_app.png" /&gt;
* Assurez-vous que vous obtenez une réponse HTTP 200 et que le contenu renvoyé correspond à l&amp;rsquo;application &amp;ldquo;whoami&amp;rdquo;.
* Vérifiez que le pod &amp;ldquo;whoami&amp;rdquo; est en cours d&amp;rsquo;exécution dans le cluster Kubernetes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pod
NAME&lt;span class="w"&gt;                      &lt;/span&gt;READY&lt;span class="w"&gt;   &lt;/span&gt;STATUS&lt;span class="w"&gt;    &lt;/span&gt;RESTARTS&lt;span class="w"&gt;   &lt;/span&gt;AGE
whoami-5d696b585d-twv98&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;/2&lt;span class="w"&gt;     &lt;/span&gt;Running&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;4m58s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Test du scaling à zéro:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scénario:&lt;/strong&gt; Mettez l&amp;rsquo;application &amp;ldquo;whoami&amp;rdquo; à zéro pods et déclenchez des requêtes HTTP vers l&amp;rsquo;application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Utilisez la commande &lt;code&gt;kubectl -n whoami scale deployment whoami --replicas=0&lt;/code&gt; pour réduire le nombre de pods à zéro.&lt;/li&gt;
&lt;li&gt;Effectuez plusieurs requêtes HTTP vers l&amp;rsquo;application &amp;ldquo;whoami&amp;rdquo; (via un navigateur ou curl).&lt;/li&gt;
&lt;li&gt;Assurez-vous que les requêtes retournent une réponse HTTP 503 (service indisponible).&lt;/li&gt;
&lt;li&gt;Vérifiez que le monitoring Prometheus détecte l&amp;rsquo;absence de pod et génère une alerte.&lt;/li&gt;
&lt;li&gt;Vérifiez que l&amp;rsquo;alerte &amp;ldquo;DeploymentDown&amp;rdquo; est générée par Alertmanager et envoyée au webhook d&amp;rsquo;Ansible.&lt;/li&gt;
&lt;li&gt;Vérifiez que le playbook Ansible &amp;ldquo;scale-deployment.yaml&amp;rdquo; est exécuté et qu&amp;rsquo;un nouveau pod &amp;ldquo;whoami&amp;rdquo; est crée&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n ansible-rulebook logs deployment/ansible-rulebook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;De mon côté j&amp;rsquo;ai observé que le service répond en 25seconde après une coupure.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;autoscaling basé sur les événements offre un équilibre parfait entre l&amp;rsquo;optimisation des ressources et la disponibilité immédiate des applications. En combinant Event Driven Ansible et des outils tels qu&amp;rsquo;Istio, Prometheus et Alertmanager, vous pouvez mettre en place un système robuste et efficace pour garantir une performance optimale et des coûts réduits.&lt;/p&gt;
&lt;h3 id="principaux-avantages-de-cette-approche"&gt;Principaux avantages de cette approche&lt;a class="headerlink" href="#principaux-avantages-de-cette-approche" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Disponibilité immédiate :&lt;/strong&gt; Les pods sont lancés automatiquement lors des premiers appels HTTP, garantissant une réponse rapide aux demandes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimisation des ressources :&lt;/strong&gt; Les pods sont arrêtés automatiquement lorsqu&amp;rsquo;ils ne sont plus utilisés, ce qui réduit les coûts de ressources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficacité et fiabilité :&lt;/strong&gt; Le système fonctionne de manière automatique et fiable, sans intervention humaine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Évolutivité :&lt;/strong&gt; La solution peut être facilement adaptée aux besoins évolutifs des applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="questions-pour-reflexion"&gt;Questions pour réflexion&lt;a class="headerlink" href="#questions-pour-reflexion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Avez-vous déjà mis en place des solutions similaires pour le scaling à zéro dans vos environnements Kubernetes ?&lt;/li&gt;
&lt;li&gt;Comment pouvez-vous adapter cette solution à vos besoins spécifiques et l&amp;rsquo;intégrer dans votre pipeline DevOps ?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ressources-supplementaires"&gt;Ressources supplémentaires&lt;a class="headerlink" href="#ressources-supplementaires" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;https://chimbu.medium.com/installing-istio-not-anthos-service-mesh-on-gke-autopilot-2b78f1bbe90a&lt;/li&gt;
&lt;li&gt;https://developers.redhat.com/articles/2024/04/12/event-driven-ansible-rulebook-automation#ansible_rulebook_cli_setup&lt;/li&gt;
&lt;li&gt;https://github.com/istio/istio/issues/10543#issuecomment-921179277&lt;/li&gt;
&lt;li&gt;https://medium.com/@letsretry/retry-between-region-using-ingress-controller-ingress-level-retry-e8c000580bfe&lt;/li&gt;
&lt;/ul&gt;</content><category term="devops"/><category term="devops"/><category term="kubernetes"/><category term="Ansible"/><category term="Istio"/></entry><entry><title>The Quest for the Devops Grail: Reconciling Zero Scaling and Immediate Availability</title><link href="https://www.ops-chronicles.cloud/scaleto0-with-event-driven-ansible-and-istio.html" rel="alternate"/><published>2024-06-02T00:00:00+02:00</published><updated>2024-06-02T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-06-02:/scaleto0-with-event-driven-ansible-and-istio.html</id><summary type="html">&lt;p&gt;In the ruthless world of the cloud, every unused resource represents an unnecessary cost. Whether it&amp;rsquo;s test environments on weekends or applications used only intermittently, there are costs to optimize. The solution: zero scaling of Kubernetes deployments, while attractive for optimizing costs, often results in a degraded user experience with unavailable applications. So how do you reconcile savings and responsiveness?&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#understanding-the-problem"&gt;Understanding the Problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-solution-event-driven-autoscaling"&gt;The Solution: Event-Driven Autoscaling&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#how-the-system-works"&gt;How the System Works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#setting-up-the-platform"&gt;Setting up the Platform&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#installing-kubernetesistio"&gt;Installing kubernetes+istio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#installing-monitoring-prometheus-alertmanager"&gt;Installing monitoring: Prometheus / Alertmanager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#installing-an-application"&gt;Installing an Application&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#event-driven-ansible"&gt;Event Driven Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#implementing-eda-in-the-cluster"&gt;Implementing EDA in the Cluster&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#building-a-playbook-for-scaling"&gt;Building a playbook for scaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#integration-with-supervision"&gt;Integration with Supervision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#creating-the-supervision-alert"&gt;Creating the Supervision Alert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing-zero-scaling"&gt;Testing Zero Scaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#key-benefits-of-this-approach"&gt;Key Benefits of This Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#questions-for-reflection"&gt;Questions for Reflection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#additional-resources"&gt;Additional Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;In the ruthless world of the cloud, every unused resource represents an unnecessary cost. Zero scaling of Kubernetes pods, while attractive for optimizing costs, often results in a degraded user experience with unavailable applications. How then can you reconcile savings and responsiveness?&lt;/p&gt;
&lt;p&gt;Forget about compromises, and adopt an &lt;strong&gt;event-driven&lt;/strong&gt; approach, combining the power of &lt;strong&gt;Event Driven Ansible&lt;/strong&gt; with the finesse of tools like &lt;strong&gt;Istio, Prometheus, and Alertmanager&lt;/strong&gt;. Imagine a system capable of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Instantly detecting&lt;/strong&gt; the first HTTP calls to your applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatically triggering&lt;/strong&gt; the launch of your Kubernetes pods to meet demand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestrating the scaling&lt;/strong&gt; of your resources dynamically and transparently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducing your infrastructure costs&lt;/strong&gt; by stopping inactive pods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No more waiting for your applications to restart, availability is almost immediate, and your users don&amp;rsquo;t notice a thing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article, we will explore in detail how to implement this elegant and efficient solution. You will discover:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The mechanisms of event detection based on HTTP traffic.&lt;/li&gt;
&lt;li&gt;The configuration of intelligent alerts to trigger autoscaling.&lt;/li&gt;
&lt;li&gt;The implementation of Ansible playbooks to automate the scaling of your deployments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Get ready to say goodbye to compromises and enter the era of intelligent zero scaling!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="understanding-the-problem"&gt;Understanding the Problem&lt;a class="headerlink" href="#understanding-the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Consider a development or test environment. An &amp;ldquo;operator&amp;rdquo; could turn on the environment before using it and turn it off (zero scaling) afterwards. However, the DevOps approach aims to automate these processes to eliminate manual interventions.&lt;/p&gt;
&lt;p&gt;The challenge lies in finding an automatic solution to manage scaling dynamically, adapting to fluctuations in demand. We want the environment to be immediately available when needed, without users experiencing any wait time.&lt;/p&gt;
&lt;h3 id="the-solution-event-driven-autoscaling"&gt;The Solution: Event-Driven Autoscaling&lt;a class="headerlink" href="#the-solution-event-driven-autoscaling" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Event-based autoscaling is the key to reconciling resource savings and immediate availability. The idea is to monitor HTTP traffic to Kubernetes pods and automatically trigger scaling (increase the number of pods) when requests do not find destinations.&lt;/p&gt;
&lt;h4 id="how-the-system-works"&gt;How the System Works&lt;a class="headerlink" href="#how-the-system-works" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Event Detection:&lt;/strong&gt; Istio, a traffic management tool, monitors HTTP traffic to your Kubernetes pods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Collection:&lt;/strong&gt; Prometheus, a monitoring system, collects traffic data from Istio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alert Generation:&lt;/strong&gt; Alertmanager analyzes Prometheus data and triggers alerts when HTTP traffic exceeds a defined threshold.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Action:&lt;/strong&gt; Alertmanager alerts trigger Ansible playbooks via Event Driven Ansible, which automatically launch Kubernetes pods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Shutdown:&lt;/strong&gt; When there is no more HTTP traffic for a defined period, Alertmanager triggers another Ansible playbook that stops the pods.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Architecture" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_archi0.drawio.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sequences" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_sequences.png" /&gt;&lt;/p&gt;
&lt;h2 id="setting-up-the-platform"&gt;Setting up the Platform&lt;a class="headerlink" href="#setting-up-the-platform" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="installing-kubernetesistio"&gt;Installing kubernetes+istio&lt;a class="headerlink" href="#installing-kubernetesistio" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We will use Google Kubernetes Engine (GKE) with Node-Autoscaling, which automatically handles node scaling. This helps optimize billable resources based on the number of pods running.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;compute&lt;span class="w"&gt; &lt;/span&gt;networks&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-vpc&lt;span class="w"&gt; &lt;/span&gt;--project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;clusters&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--enable-autoscaling&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--num-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--min-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--max-nodes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--network&lt;span class="w"&gt; &lt;/span&gt;scaleto0-vpc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--release-channel&lt;span class="o"&gt;=&lt;/span&gt;regular&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--zone&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;-b
gcloud&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;clusters&lt;span class="w"&gt; &lt;/span&gt;get-credentials&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--zone&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--project&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we will install Istio for traffic management:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://istio.io/downloadIstio&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;istio-&amp;lt;&amp;lt;VERSION&amp;gt;&amp;gt;
./bin/istioctl&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--set&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo&lt;span class="w"&gt; &lt;/span&gt;-y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verify the Istio installation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Check the status of pods&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system

&lt;span class="c1"&gt;# Check the status of services and make sure an external LB is created for istio-ingressgateway&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;svc&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="installing-monitoring-prometheus-alertmanager"&gt;Installing monitoring: Prometheus / Alertmanager&lt;a class="headerlink" href="#installing-monitoring-prometheus-alertmanager" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this article we will perform the installation of a minimalist monitoring system for Istio, focusing only on monitoring Istio itself, without monitoring servers, CPU or RAM. We will use a Helm chart for simplified installation and maintain consistency with the rest of the article by using the &lt;code&gt;istio-system&lt;/code&gt; namespace.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;helm&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;prometheus&lt;span class="w"&gt; &lt;/span&gt;prometheus-community/prometheus&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;# prometheus_values.yaml&lt;/span&gt;
&lt;span class="s"&gt;rbac:&lt;/span&gt;
&lt;span class="s"&gt;create: true&lt;/span&gt;
&lt;span class="s"&gt;configmapReload:&lt;/span&gt;
&lt;span class="s"&gt;prometheus:&lt;/span&gt;
&lt;span class="s"&gt;enabled: false&lt;/span&gt;
&lt;span class="s"&gt;server:&lt;/span&gt;
&lt;span class="s"&gt;namespaces:&lt;/span&gt;
&lt;span class="s"&gt;- istio-system&lt;/span&gt;
&lt;span class="s"&gt;resources:&lt;/span&gt;
&lt;span class="s"&gt;limits:&lt;/span&gt;
&lt;span class="s"&gt;cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;requests:&lt;/span&gt;
&lt;span class="s"&gt;cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;global:&lt;/span&gt;
&lt;span class="s"&gt;scrape_interval: 1s&lt;/span&gt;
&lt;span class="s"&gt;scrape_timeout: 1s&lt;/span&gt;
&lt;span class="s"&gt;evaluation_interval: 2s&lt;/span&gt;
&lt;span class="s"&gt;serverFiles:&lt;/span&gt;
&lt;span class="s"&gt;prometheus.yml:&lt;/span&gt;
&lt;span class="s"&gt;rule_files:&lt;/span&gt;
&lt;span class="s"&gt;- /etc/config/recording_rules.yml&lt;/span&gt;
&lt;span class="s"&gt;- /etc/config/alerting_rules.yml&lt;/span&gt;
&lt;span class="s"&gt;scrape_configs:&lt;/span&gt;
&lt;span class="s"&gt;- job_name: &amp;#39;istiod&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;kubernetes_sd_configs:&lt;/span&gt;
&lt;span class="s"&gt;- role: pod&lt;/span&gt;
&lt;span class="s"&gt;relabel_configs:&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]&lt;/span&gt;
&lt;span class="s"&gt;action: keep&lt;/span&gt;
&lt;span class="s"&gt;regex: true&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape_slow]&lt;/span&gt;
&lt;span class="s"&gt;action: drop&lt;/span&gt;
&lt;span class="s"&gt;regex: true&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;regex: (https?)&lt;/span&gt;
&lt;span class="s"&gt;target_label: __scheme__&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;target_label: __metrics_path__&lt;/span&gt;
&lt;span class="s"&gt;regex: (.+)&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;regex: (\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})&lt;/span&gt;
&lt;span class="s"&gt;replacement: &amp;#39;[\$2]:\$1&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;target_label: __address__&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;regex: (\\d+);((([0-9]+?)(\\.|$)){4})&lt;/span&gt;
&lt;span class="s"&gt;replacement: \$2:\$1&lt;/span&gt;
&lt;span class="s"&gt;target_label: __address__&lt;/span&gt;
&lt;span class="s"&gt;- action: labelmap&lt;/span&gt;
&lt;span class="s"&gt;regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)&lt;/span&gt;
&lt;span class="s"&gt;replacement: __param_$1&lt;/span&gt;
&lt;span class="s"&gt;- action: labelmap&lt;/span&gt;
&lt;span class="s"&gt;regex: __meta_kubernetes_pod_label_(.+)&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_namespace]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;target_label: namespace&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_name]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;target_label: pod&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_phase]&lt;/span&gt;
&lt;span class="s"&gt;regex: Pending|Succeeded|Failed|Completed&lt;/span&gt;
&lt;span class="s"&gt;action: drop&lt;/span&gt;
&lt;span class="s"&gt;- source_labels: [__meta_kubernetes_pod_node_name]&lt;/span&gt;
&lt;span class="s"&gt;action: replace&lt;/span&gt;
&lt;span class="s"&gt;target_label: node&lt;/span&gt;
&lt;span class="s"&gt;alertmanager:&lt;/span&gt;
&lt;span class="s"&gt;enabled: true&lt;/span&gt;

&lt;span class="s"&gt;kube-state-metrics:&lt;/span&gt;
&lt;span class="s"&gt;enabled: false&lt;/span&gt;

&lt;span class="s"&gt;prometheus-node-exporter:&lt;/span&gt;
&lt;span class="s"&gt;enabled: false&lt;/span&gt;

&lt;span class="s"&gt;prometheus-pushgateway:&lt;/span&gt;
&lt;span class="s"&gt;enabled: false&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrole&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--verb&lt;span class="o"&gt;=&lt;/span&gt;get,list,watch&lt;span class="w"&gt; &lt;/span&gt;--resource&lt;span class="o"&gt;=&lt;/span&gt;pods,endpoints,services,nodes,namespaces
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrolebinding&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--clusterrole&lt;span class="o"&gt;=&lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--serviceaccount&lt;span class="o"&gt;=&lt;/span&gt;istio-system:prometheus-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To test the monitoring system, we can use the following command to redirect http://localhost:9090 to the Prometheus pod:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl --namespace istio-system port-forward $(kubectl get pods --namespace istio-system -l &amp;quot;app.kubernetes.io/name=prometheus,app.kubernetes.io/instance=prometheus&amp;quot; -o jsonpath=&amp;quot;{.items[0].metadata.name}&amp;quot;) 9090
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then open a web browser at the address: http://localhost:9090&lt;/p&gt;
&lt;p&gt;&lt;img alt="Prometheus" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_prometheus0.png" /&gt;&lt;/p&gt;
&lt;h3 id="installing-an-application"&gt;Installing an Application&lt;a class="headerlink" href="#installing-an-application" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To simulate an application in our demo, we will use &amp;ldquo;whoami&amp;rdquo; which simply returns the HTTP request it receives.
If it works with &amp;ldquo;whoami&amp;rdquo;, we can apply the same principle to all deployments on the Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create a YAML file for the deployment, as follows:&lt;/p&gt;
&lt;p&gt;This YAML code describes the configuration of a Kubernetes deployment for an application named &amp;ldquo;whoami&amp;rdquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deployment &amp;ldquo;whoami&amp;rdquo;&lt;/strong&gt;: Defines a deployment with the name &amp;ldquo;whoami&amp;rdquo; that uses the Docker image &amp;ldquo;traefik/whoami:latest&amp;rdquo;. It indicates that the deployment should have 1 &amp;ldquo;replica&amp;rdquo; (scaling to 1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service &amp;ldquo;whoami&amp;rdquo;&lt;/strong&gt;: Defines a Kubernetes service named &amp;ldquo;whoami&amp;rdquo; that connects to pods with the label &amp;ldquo;app: whoami&amp;rdquo;. Exposes port 80 of the service, which redirects to port 80 of the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl create ns whoami&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl label namespace whoami istio-injection=enabled&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl apply -n whoami -f - &amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;# whoami_deployment.yaml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;apps/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Deployment&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;sidecar.istio.io/proxyCPU&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;500m&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;sidecar.istio.io/proxyMemory&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;512Mi&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;master&lt;/span&gt;
&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;traefik/whoami:latest&lt;/span&gt;
&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500m&lt;/span&gt;
&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;512Mi&lt;/span&gt;
&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500m&lt;/span&gt;
&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;512Mi&lt;/span&gt;
&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Service&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And expose this service via Istio:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubectl apply -n whoami -f - &amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;apiVersion&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networking.istio.io/v1alpha3&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami-gateway&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# The selector matches the ingress gateway pod labels.&lt;/span&gt;
&lt;span class="c1"&gt;# If you installed Istio using Helm following the standard documentation, this would be &amp;quot;istio=ingress&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;istio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ingressgateway&lt;/span&gt;
&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTP&lt;/span&gt;
&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networking.istio.io/v1alpha3&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;VirtualService&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;gateways&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami-gateway&lt;/span&gt;
&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/&lt;/span&gt;
&lt;span class="nt"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;whoami&lt;/span&gt;
&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;15&lt;/span&gt;
&lt;span class="nt"&gt;perTryTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2s&lt;/span&gt;
&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Testing the application&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;INGRESS_HOST&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubectl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ingressgateway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{.status.loadBalancer.ingress[0].ip}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;HHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scaleto0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://$INGRESS_HOST/&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GMT&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;570&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;envoy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;upstream&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1073&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;envoy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Downscaling the application&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;scale&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;--replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once the application is cut off (down), it should no longer work: Application unavailability&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;HHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;whoami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleto0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://$INGRESS_HOST:$INGRESS_PORT/&amp;quot;&lt;/span&gt;

&lt;span class="nx"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m m-Double"&gt;1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Unavailable&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;
&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Thu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GMT&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;istio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;envoy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Prometheus" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_app503.png" /&gt;&lt;/p&gt;
&lt;h2 id="event-driven-ansible"&gt;Event Driven Ansible&lt;a class="headerlink" href="#event-driven-ansible" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Following Redhat documentation, we will build a container image to serve our rulebook. Here is the Dockerfile that creates a container to run the &amp;ldquo;ansible-rulebook&amp;rdquo; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Dockerfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;FROM debian:latest&lt;/span&gt;

&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;apt-get --assume-yes install openjdk-17-jdk python3-pip python3-psycopg &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;pip3 install ansible ansible-rulebook ansible-runner kubernetes --break-system-packages&lt;/span&gt;

&lt;span class="s"&gt;RUN mkdir /app &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;useradd -u 1001 -ms /bin/bash ansible &amp;amp;&amp;amp; \&lt;/span&gt;
&lt;span class="s"&gt;chown ansible:root /app&lt;/span&gt;

&lt;span class="s"&gt;WORKDIR /app&lt;/span&gt;
&lt;span class="s"&gt;USER ansible&lt;/span&gt;

&lt;span class="s"&gt;RUN ansible-galaxy collection install ansible.eda&lt;/span&gt;

&lt;span class="s"&gt;ENTRYPOINT [&amp;quot;ansible-rulebook&amp;quot;, &amp;quot;-r&amp;quot;, &amp;quot;/app/rules.yaml&amp;quot;, &amp;quot;-i&amp;quot;, &amp;quot;/app/inventory.yml&amp;quot;, &amp;quot;-S&amp;quot;, &amp;quot;/app&amp;quot;]&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can build and save our image in Google&amp;rsquo;s artifact-registry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creating the repository&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;artifacts&lt;span class="w"&gt; &lt;/span&gt;repositories&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;scaleto0-demo-repo&lt;span class="w"&gt; &lt;/span&gt;--repository-format&lt;span class="o"&gt;=&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--location&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--description&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Docker scaleto0 Demo repository&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Creating the image&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;builds&lt;span class="w"&gt; &lt;/span&gt;submit&lt;span class="w"&gt; &lt;/span&gt;--region&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--tag&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;-docker.pkg.dev/&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;/scaleto0-demo-repo/rulebook-scaleto0-demo:v1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This container will be deployed in Kubernetes. We will overload the &lt;code&gt;/app/rules.yaml&lt;/code&gt; file to include our rules and playbooks, including those for scaling.&lt;/p&gt;
&lt;h2 id="implementing-eda-in-the-cluster"&gt;Implementing EDA in the Cluster&lt;a class="headerlink" href="#implementing-eda-in-the-cluster" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="building-a-playbook-for-scaling"&gt;Building a playbook for scaling&lt;a class="headerlink" href="#building-a-playbook-for-scaling" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ansible, a powerful language for manipulating infrastructure components, allows us to create a &amp;ldquo;playbook&amp;rdquo; that will allow us to scale the Kubernetes deployment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;scale-deployment.yaml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;- hosts: localhost&lt;/span&gt;
&lt;span class="s"&gt;connection: local&lt;/span&gt;
&lt;span class="s"&gt;gather_facts: false&lt;/span&gt;
&lt;span class="s"&gt;tasks:&lt;/span&gt;
&lt;span class="s"&gt;- name: Scale deployment up&lt;/span&gt;
&lt;span class="s"&gt;kubernetes.core.k8s_scale:&lt;/span&gt;
&lt;span class="s"&gt;api_version: v1&lt;/span&gt;
&lt;span class="s"&gt;kind: Deployment&lt;/span&gt;
&lt;span class="s"&gt;name: &amp;quot;{{ deployment_name }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;namespace: &amp;quot;{{ namespace }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;replicas: &amp;quot;{{ num_replicas }}&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;wait_timeout: 60&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can test the playbook with the command below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;deployment_name&amp;quot;:&amp;quot;whoami&amp;quot;, &amp;quot;namespace&amp;quot;:&amp;quot;whoami&amp;quot;, &amp;quot;num_replicas&amp;quot;: &amp;quot;1&amp;quot;}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scale-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, let&amp;rsquo;s create a &lt;code&gt;rules.yaml&lt;/code&gt; file to define the interface between monitoring and the scaling playbook.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;
&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;
&lt;span class="n"&gt;gather_facts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;
&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run_playbook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="n"&gt;extra_vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;deployment_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whoami&lt;/span&gt;
&lt;span class="n"&gt;num_replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="n"&gt;ungrouped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;ansible_connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Injecting the playbook into Kubernetes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;ns&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook-config&lt;span class="w"&gt; &lt;/span&gt;--from-file&lt;span class="o"&gt;=&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Starting the container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;ansible-rulebook&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;# ansible-rulebook_deployment.yaml&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: apps/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: Deployment&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;name: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;spec:&lt;/span&gt;
&lt;span class="s"&gt;selector:&lt;/span&gt;
&lt;span class="s"&gt;matchLabels:&lt;/span&gt;
&lt;span class="s"&gt;app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;replicas: 1&lt;/span&gt;
&lt;span class="s"&gt;template:&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;labels:&lt;/span&gt;
&lt;span class="s"&gt;app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;spec:&lt;/span&gt;
&lt;span class="s"&gt;containers:&lt;/span&gt;
&lt;span class="s"&gt;- name: master&lt;/span&gt;
&lt;span class="s"&gt;image: $REGION-docker.pkg.dev/$PROJECT_ID/scaleto0-demo-repo/rulebook-scaleto0-demo:v1&lt;/span&gt;
&lt;span class="s"&gt;resources:&lt;/span&gt;
&lt;span class="s"&gt;requests:&lt;/span&gt;
&lt;span class="s"&gt;cpu: 100m&lt;/span&gt;
&lt;span class="s"&gt;memory: 128Mi&lt;/span&gt;
&lt;span class="s"&gt;limits:&lt;/span&gt;
&lt;span class="s"&gt;cpu: 500m&lt;/span&gt;
&lt;span class="s"&gt;memory: 512Mi&lt;/span&gt;
&lt;span class="s"&gt;ports:&lt;/span&gt;
&lt;span class="s"&gt;- containerPort: 5000&lt;/span&gt;
&lt;span class="s"&gt;volumeMounts:&lt;/span&gt;
&lt;span class="s"&gt;- name: config&lt;/span&gt;
&lt;span class="s"&gt;mountPath: /app&lt;/span&gt;
&lt;span class="s"&gt;volumes:&lt;/span&gt;
&lt;span class="s"&gt;- name: config&lt;/span&gt;
&lt;span class="s"&gt;configMap:&lt;/span&gt;
&lt;span class="s"&gt;name: ansible-rulebook-config&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: v1&lt;/span&gt;
&lt;span class="s"&gt;kind: Service&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;name: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;labels:&lt;/span&gt;
&lt;span class="s"&gt;app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;spec:&lt;/span&gt;
&lt;span class="s"&gt;ports:&lt;/span&gt;
&lt;span class="s"&gt;- port: 5000&lt;/span&gt;
&lt;span class="s"&gt;targetPort: 5000&lt;/span&gt;
&lt;span class="s"&gt;selector:&lt;/span&gt;
&lt;span class="s"&gt;app: ansible-rulebook&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Granting rights to &lt;code&gt;ansible-rulebook&lt;/code&gt; to interact with Kubernetes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;# ansible-rulebook_clusterrole.yaml&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="s"&gt;kind: ClusterRole&lt;/span&gt;
&lt;span class="s"&gt;metadata:&lt;/span&gt;
&lt;span class="s"&gt;creationTimestamp: &amp;quot;2024-05-31T19:01:36Z&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;name: deployment-scaler&lt;/span&gt;
&lt;span class="s"&gt;rules:&lt;/span&gt;
&lt;span class="s"&gt;- apiGroups:&lt;/span&gt;
&lt;span class="s"&gt;- apps&lt;/span&gt;
&lt;span class="s"&gt;resources:&lt;/span&gt;
&lt;span class="s"&gt;- deployments/scale&lt;/span&gt;
&lt;span class="s"&gt;- deployments&lt;/span&gt;
&lt;span class="s"&gt;verbs:&lt;/span&gt;
&lt;span class="s"&gt;- get&lt;/span&gt;
&lt;span class="s"&gt;- list&lt;/span&gt;
&lt;span class="s"&gt;- patch&lt;/span&gt;
&lt;span class="s"&gt;- update&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, mapping the ansible-rulebook role to the deployment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;clusterrolebinding&lt;span class="w"&gt; &lt;/span&gt;deployment-scaler&lt;span class="w"&gt; &lt;/span&gt;--clusterrole&lt;span class="o"&gt;=&lt;/span&gt;deployment-scaler&lt;span class="w"&gt; &lt;/span&gt;--serviceaccount&lt;span class="o"&gt;=&lt;/span&gt;ansible-rulebook:default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="integration-with-supervision"&gt;Integration with Supervision&lt;a class="headerlink" href="#integration-with-supervision" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alertmanager needs to be connected to Ansible rulebook. To do this, let&amp;rsquo;s update the Alertmanager configuration to have it send its alerts to the rulebook.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;patch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;prometheus-alertmanager&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;merge&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;data:&lt;/span&gt;
&lt;span class="s2"&gt;alertmanager.yml: |&lt;/span&gt;
&lt;span class="s2"&gt;global: {}&lt;/span&gt;
&lt;span class="s2"&gt;receivers:&lt;/span&gt;
&lt;span class="s2"&gt;- name: default-receiver&lt;/span&gt;
&lt;span class="s2"&gt;webhook_configs:&lt;/span&gt;
&lt;span class="s2"&gt;- url: http://ansible-rulebook.ansible-rulebook.svc.cluster.local:5000/endpoint&lt;/span&gt;
&lt;span class="s2"&gt;route:&lt;/span&gt;
&lt;span class="s2"&gt;group_interval: 5s&lt;/span&gt;
&lt;span class="s2"&gt;group_wait: 10s&lt;/span&gt;
&lt;span class="s2"&gt;receiver: default-receiver&lt;/span&gt;
&lt;span class="s2"&gt;repeat_interval: 5m&lt;/span&gt;
&lt;span class="s2"&gt;templates:&lt;/span&gt;
&lt;span class="s2"&gt;- /etc/alertmanager/*.tmpl&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Restart the monitoring pods to take into account the new configmap:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n istio-system rollout restart statefulset/prometheus-alertmanager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="creating-the-supervision-alert"&gt;Creating the Supervision Alert&lt;a class="headerlink" href="#creating-the-supervision-alert" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s create a supervision alert that will detect 503 errors in Prometheus and trigger the Ansible &amp;ldquo;rulebook&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Since Prometheus is installed with Helm, we will update the static configuration to inject the alert. In a production environment, alerts would be injected via the operator and &amp;ldquo;PrometheusRules&amp;rdquo; CRDs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;patch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;configmap&lt;span class="w"&gt; &lt;/span&gt;prometheus-server&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;merge&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;data:&lt;/span&gt;
&lt;span class="s2"&gt;alerting_rules.yml: |&lt;/span&gt;
&lt;span class="s2"&gt;groups:&lt;/span&gt;
&lt;span class="s2"&gt;- name: DeploymentDown&lt;/span&gt;
&lt;span class="s2"&gt;rules:&lt;/span&gt;
&lt;span class="s2"&gt;- alert: DeploymentDown&lt;/span&gt;
&lt;span class="s2"&gt;expr: sum by (destination_service_name) (rate(istio_requests_total{response_code=\&amp;quot;503\&amp;quot;}[3s])) &amp;gt; 0&lt;/span&gt;
&lt;span class="s2"&gt;for: 2s&lt;/span&gt;
&lt;span class="s2"&gt;labels:&lt;/span&gt;
&lt;span class="s2"&gt;severity: page&lt;/span&gt;
&lt;span class="s2"&gt;annotations:&lt;/span&gt;
&lt;span class="s2"&gt;description: &amp;#39;No upstream on {{ \$labels.destination_service_name }}&amp;#39;&lt;/span&gt;
&lt;span class="s2"&gt;summary: &amp;#39;{{ \$labels.destination_service_name }} down&amp;#39;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Restart the monitoring pods to take into account the new configmap:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n istio-system rollout restart deployment/prometheus-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After generating a few calls to the application without pods, we should find the alert in Prometheus:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Architecture" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_alert0.png" /&gt;&lt;/p&gt;
&lt;h2 id="testing-zero-scaling"&gt;Testing Zero Scaling&lt;a class="headerlink" href="#testing-zero-scaling" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Basic test:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Start the &amp;ldquo;whoami&amp;rdquo; application with a single pod and observe that the application is accessible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access the &amp;ldquo;whoami&amp;rdquo; service via a browser or curl: Verify that the result is correct and keep the window open to view the status of the application.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;INGRESS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;svc&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;istio-system&lt;span class="w"&gt; &lt;/span&gt;istio-ingressgateway&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{.status.loadBalancer.ingress[0].ip}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
watch&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;-HHost:whoami.scaleto0.demo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&lt;/span&gt;&lt;span class="nv"&gt;$INGRESS_HOST&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Watch App" src="/images/2024-scaleto0-with-event-driven-ansible/2024-scaleto0-with-event-driven-ansible_watch_app.png" /&gt;
* Make sure you get an HTTP 200 response and that the content returned corresponds to the &amp;ldquo;whoami&amp;rdquo; application.
* Verify that the &amp;ldquo;whoami&amp;rdquo; pod is running in the Kubernetes cluster.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;whoami&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pod
NAME&lt;span class="w"&gt; &lt;/span&gt;READY&lt;span class="w"&gt; &lt;/span&gt;STATUS&lt;span class="w"&gt; &lt;/span&gt;RESTARTS&lt;span class="w"&gt; &lt;/span&gt;AGE
whoami-5d696b585d-twv98&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;/2&lt;span class="w"&gt; &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4m58s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Testing zero scaling:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Set the &amp;ldquo;whoami&amp;rdquo; application to zero pods and trigger HTTP requests to the application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;kubectl -n whoami scale deployment whoami --replicas=0&lt;/code&gt; command to reduce the number of pods to zero.&lt;/li&gt;
&lt;li&gt;Make several HTTP requests to the &amp;ldquo;whoami&amp;rdquo; application (via a browser or curl).&lt;/li&gt;
&lt;li&gt;Make sure the requests return an HTTP 503 (service unavailable) response.&lt;/li&gt;
&lt;li&gt;Verify that Prometheus monitoring detects the absence of a pod and generates an alert.&lt;/li&gt;
&lt;li&gt;Verify that the &amp;ldquo;DeploymentDown&amp;rdquo; alert is generated by Alertmanager and sent to the Ansible webhook.&lt;/li&gt;
&lt;li&gt;Verify that the Ansible playbook &amp;ldquo;scale-deployment.yaml&amp;rdquo; is executed and a new &amp;ldquo;whoami&amp;rdquo; pod is created&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl -n ansible-rulebook logs deployment/ansible-rulebook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From my side I observed that the service responds in 25 seconds after a cut-off.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Event-based autoscaling provides a perfect balance between resource optimization and the immediate availability of applications. By combining Event Driven Ansible with tools like Istio, Prometheus, and Alertmanager, you can implement a robust and efficient system to ensure optimal performance and reduced costs.&lt;/p&gt;
&lt;h3 id="key-benefits-of-this-approach"&gt;Key Benefits of This Approach&lt;a class="headerlink" href="#key-benefits-of-this-approach" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Immediate availability:&lt;/strong&gt; Pods are launched automatically on the first HTTP calls, ensuring a quick response to requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource optimization:&lt;/strong&gt; Pods are automatically shut down when they are no longer in use, reducing resource costs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency and reliability:&lt;/strong&gt; The system works automatically and reliably, without human intervention.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; The solution can be easily adapted to the evolving needs of applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="questions-for-reflection"&gt;Questions for Reflection&lt;a class="headerlink" href="#questions-for-reflection" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Have you already implemented similar solutions for zero scaling in your Kubernetes environments?&lt;/li&gt;
&lt;li&gt;How can you adapt this solution to your specific needs and integrate it into your DevOps pipeline?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="additional-resources"&gt;Additional Resources&lt;a class="headerlink" href="#additional-resources" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;https://chimbu.medium.com/installing-istio-not-anthos-service-mesh-on-gke-autopilot-2b78f1bbe90a&lt;/li&gt;
&lt;li&gt;https://developers.redhat.com/articles/2024/04/12/event-driven-ansible-rulebook-automation#ansible_rulebook_cli_setup&lt;/li&gt;
&lt;li&gt;https://github.com/istio/istio/issues/10543#issuecomment-921179277&lt;/li&gt;
&lt;li&gt;https://medium.com/@letsretry/retry-between-region-using-ingress-controller-ingress-level-retry-e8c000580bfe&lt;/li&gt;
&lt;/ul&gt;</content><category term="devops"/><category term="devops"/><category term="kubernetes"/><category term="Ansible"/><category term="Istio"/></entry><entry><title>Génération de texte pour les Merges-Requests automatique avec un LLM</title><link href="https://www.ops-chronicles.cloud/fr/generate-mr-text.html" rel="alternate"/><published>2024-05-29T00:00:00+02:00</published><updated>2024-05-29T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-05-29:/fr/generate-mr-text.html</id><summary type="html">&lt;p&gt;Générer automatiquement le texte des merges-request Gitlab à partir du git-diff&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#1-configuration-de-vertex-ai"&gt;1. Configuration de Vertex AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-construction-dun-script-et-integration-dans-une-image-docker"&gt;2. Construction d&amp;rsquo;un script et intégration dans une image docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3-integration-dans-gitlab-ci"&gt;3. Intégration dans GitLab CI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Notre métier consiste à installer des applications en respéctant l&amp;rsquo;état de l&amp;rsquo;art. Lorsque l&amp;rsquo;installation de l&amp;rsquo;application se fait via la mise à jour de code Gitlab, la documentation de ce code est primordiale. Toute mise à jour du code d&amp;rsquo;infrastructure est documenté dans une demande Gitlab de merge-request.&lt;/p&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui, je vous propose d&amp;rsquo;automatiser une des tâches de documentation: la rédaction de descriptions pour les merge-requests. 
J&amp;rsquo;ai pu remarquer qu&amp;rsquo;au début des projets, on se force à écrire de la documentation et des descriptions de changements clair et complet. Cependant, avec la pression ou les changements qui sont à effectuer rapidement, avec le temps, il arrive qu&amp;rsquo;on se retrouve à rédiger des notes trop brèves, non-exhaustives, ou ne reflétant pas l’intégralité des changements effectués dans le code.&lt;/p&gt;
&lt;p&gt;Du coup, nos MR ressemblent à ça et c&amp;rsquo;est assez vide.&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitlabCustomDomain" src="/images/2024-Generate-MR-text/2024-Generate-MR-text_1.png" /&gt;&lt;/p&gt;
&lt;p&gt;Alors que la documentation et ces descriptions sont très importantes pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les échanges avec l&amp;rsquo;équipe (Review, double checks, &amp;hellip;)&lt;/li&gt;
&lt;li&gt;l&amp;rsquo;audibilité des changements (savoir qui a changé quoi)&lt;/li&gt;
&lt;li&gt;La gestion de la documentation du projet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Heureusement, avec Vertex AI et GitLab CI, on peut automatiser cette tâche !&lt;/p&gt;
&lt;p&gt;Dans cet article, nous allons explorer comment utiliser Vertex AI pour générer automatiquement du texte de merge-request à partir des changements effectués dans une branche GitLab.&lt;/p&gt;
&lt;h2 id="1-configuration-de-vertex-ai"&gt;1. Configuration de Vertex AI&lt;a class="headerlink" href="#1-configuration-de-vertex-ai" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tout d&amp;rsquo;abord, il faut configurer l&amp;rsquo;API Vertex AI capable de générer du texte à partir de données textuelles. Dans cet article, j&amp;rsquo;ai utilisé le modèle &amp;ldquo;gemini-1.5-flash-001&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Il faut bien sûr activer l&amp;rsquo;api Vertex AI en suivant la documentation.
On peut ensuite utiliser VertexAI via la console Google Cloud pour construire le meilleur prompt
Après quelques textes voici le prompt que j&amp;rsquo;ai mis en place :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;senior&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DevOps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;engineer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reviewing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Terraform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;comprehensive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;french&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;focusing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;overall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;purpose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;affected&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;made&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;those&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;there&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;security&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;implications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;there&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;potential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;risks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;considerations&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On peut tester le prompt dans VertexAI, comme ci-dessous en glissant-déposser un fichier &amp;ldquo;git-diff&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitlabCustomDomain" src="/images/2024-Generate-MR-text/2024-Generate-MR-text_2.png" /&gt;&lt;/p&gt;
&lt;h2 id="2-construction-dun-script-et-integration-dans-une-image-docker"&gt;2. Construction d&amp;rsquo;un script et intégration dans une image docker&lt;a class="headerlink" href="#2-construction-dun-script-et-integration-dans-une-image-docker" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pour commencer, on va créer un script python capable :&lt;/p&gt;
&lt;p&gt;Interroger Gitlab pour obtenir le &amp;ldquo;gitdiff&amp;rdquo; changement
Le soumettre à VertexAI
Mettre a jour la MR Gitlab avec le résultat de VertexAI
Voici le script : generate-mr-text.py&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai.generative_models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FinishReason&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai.preview.generative_models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;generative_models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gitlab&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;GCP_PROJECT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_PROJECT_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sandbox-mgoulin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GCP_LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_LOCATION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;us-central1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GCP_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gemini-1.5-flash-preview-0514&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;GITLAB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_HOST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://gitlab.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GITLAB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GITLAB_SSL_SKIP_VERIFY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_SSL_SKIP_VERIFY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;LLM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;As a senior DevOps engineer reviewing a merge request for a Terraform module.  Please provide a comprehensive description of the changes in french, focusing on the following:&lt;/span&gt;
&lt;span class="s2"&gt;* **What is the overall purpose of the changes?**&lt;/span&gt;
&lt;span class="s2"&gt;* **What specific resources are affected?**&lt;/span&gt;
&lt;span class="s2"&gt;* **What are the main changes made to those resources?**&lt;/span&gt;
&lt;span class="s2"&gt;* **Are there any security or performance implications of the changes?**&lt;/span&gt;
&lt;span class="s2"&gt;* **Are there any potential risks or considerations?**&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GCP_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GCP_LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;GCP_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LLM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;safety_settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;safety_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rep&lt;/span&gt;

&lt;span class="n"&gt;generation_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;max_output_tokens&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;temperature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;top_p&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;safety_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_HATE_SPEECH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_DANGEROUS_CONTENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_SEXUALLY_EXPLICIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_HARASSMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_mr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GITLAB_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GITLAB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssl_verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GITLAB_SSL_SKIP_VERIFY&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;prj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mergerequests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;generate-mr-text.py&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Use Gemini to update a merge request with a description&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-p&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--gitlab_project&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--gitlab_mr_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab_project&lt;/span&gt;
&lt;span class="n"&gt;mr_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab_mr_id&lt;/span&gt;

&lt;span class="n"&gt;mr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_mr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;#print(json.dumps(changes, indent=4))&lt;/span&gt;
&lt;span class="c1"&gt;#diff=find_diff()&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=== Start block generated by generate-mr-text.py ===&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="s2"&gt;=== Start block generated by generate-mr-text.py ===&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On peut intégrer rapidement ce script via Dockerfile et pip, pour cela, on a besoin de créer deux fichiers permettant d&amp;rsquo;installer le script dans un container docker. &lt;/p&gt;
&lt;p&gt;Fichier requirements.txt&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python-gitlab
google-cloud-aiplatform
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fichier : Dockerfile&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redhat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ubi8&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;38&lt;/span&gt;

&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;correct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenShift&lt;/span&gt;
&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1001&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1001&lt;/span&gt;

&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;
&lt;span class="n"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pip&amp;gt;=19.3.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La commande &lt;code&gt;docker build .&lt;/code&gt; permettra de construire le container et pour rester cohérent dans nos outils et rester dans le monde GCP, je propose de l&amp;rsquo;héberger dans &lt;code&gt;Artifact registry&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Pour résumé voici les commandes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cloud artifacts repositories create --project sandbox-mgoulin --repository-format=DOCKER --location europe-west9 generate-mr-text
docker build . -t europe-west9-docker.pkg.dev/sandbox-mgoulin/generate-mr-text/generate-mr-text:latest
docker push europe-west9-docker.pkg.dev/sandbox-mgoulin/generate-mr-text/generate-mr-text:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le container s&amp;rsquo;utilisera donc simplement de la façon suivante (d&amp;rsquo;autre variable d&amp;rsquo;environnement peuvent être ajouté dans le script).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker run \
-e GCP_PROJECT_ID=... \
-e GITLAB_TOKEN=... \
-e GOOGLE_APPLICATION_CREDENTIALS=... \
europe-west9-docker.pkg.dev/sandbox-mgoulin/generate-mr-text/generate-mr-text:latest \
python generate-mr-text.py -p  -m 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="3-integration-dans-gitlab-ci"&gt;3. Intégration dans GitLab CI&lt;a class="headerlink" href="#3-integration-dans-gitlab-ci" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Une fois le modèle Vertex AI configuré, le script crée, il faut l&amp;rsquo;intégrer dans le pipeline GitLab CI. Cela se fait en ajoutant une nouvelle étape dans le fichier : gitlab-ci.yml.&lt;/p&gt;
&lt;p&gt;Voici un exemple de code :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;merge_request_text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;europe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;west9&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="sr"&gt;/sandbox-mgoulin/generate-mr-text/g&lt;/span&gt;&lt;span class="n"&gt;enerate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$CI_PROJECT_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$vCI_MERGE_REQUEST_IID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fonctionnement du pipeline :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lorsque vous ouvrez une merge-request, le pipeline GitLab CI s&amp;rsquo;exécute.&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;étape merge_request_text lance le script écrit à l&amp;rsquo;étape 2.&lt;/li&gt;
&lt;li&gt;Le modèle génère un texte de merge-request, qui peut ensuite être utilisé pour créer la description de la merge-request.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Après la mise en place de ce job en pipeline nos Merge-Requests ont clairement changés. Voici un exemple pour illustrer la monté de version de nextcloud.&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitlabCustomDomain" src="/images/2024-Generate-MR-text/2024-Generate-MR-text_cover.png" /&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Revue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;demande&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fusion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Terraform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Objet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="nx"&gt;Cette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;demande&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fusion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mettre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;niveau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Terraform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cela&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;implique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;une&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;série&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modifications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assurer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compatibilité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stabilité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Ressources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;affectées&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="nx"&gt;Les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;changements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;affectent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;principalement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ressources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;suivantes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Dockerfile&lt;/span&gt;&lt;span class="p"&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;La&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PHP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modifiée&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;htaccess&lt;/span&gt;&lt;span class="p"&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;Un&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nouveau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htaccess&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ajouté&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;contenant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;directives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spécifiques&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notamment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gestion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;têtes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;directives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ressources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;statiques&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;règles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;réécriture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fonctionnement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextcloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt;&lt;span class="p"&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;L&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;utilisée&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conteneur&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;refléter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nouvelle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Principales&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modifications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;niveau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="p"&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;L&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;incluant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dernières&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fonctionnalités&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corrections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htaccess&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&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;Un&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nouveau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htaccess&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ajouté&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;garantir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;optimale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;meilleures&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pratiques&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Implications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;matière&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Sécurité&lt;/span&gt;&lt;span class="p"&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;La&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inclut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;correctifs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;importants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;La&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fichier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htaccess&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;contribue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;également&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;améliorer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appliquant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;têtes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;essentiels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Performance&lt;/span&gt;&lt;span class="p"&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;La&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nouvelle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;basée&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sur&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PHP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pourrait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entraîner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;améliorations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;particulier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gourmandes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ressources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Risques&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;considérations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Compatibilité&lt;/span&gt;&lt;span class="p"&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;Il&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;important&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tester&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;minutieusement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;assurer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toutes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configurations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;existantes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sont&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compatibles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;avec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Configurations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spécifiques&lt;/span&gt;&lt;span class="p"&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;Des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configurations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spécifiques&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ou&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;personnalisées&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pourraient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nécessiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ajustements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;supplémentaires&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Intégration&lt;/span&gt;&lt;span class="p"&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;Assurez&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;impacte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;négativement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;autres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ou&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;composants&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;système&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Sauvegardes&lt;/span&gt;&lt;span class="p"&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;Il&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crucial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;créer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sauvegardes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;complètes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;données&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fichiers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;données&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;avant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;effectuer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Conclusion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="nx"&gt;Cette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;demande&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fusion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;améliorer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sécurité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stabilité&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;du&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Nextcloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;effectuant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;une&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;niveau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Il&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;essentiel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;procéder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;à&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;des&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;complets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;avant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;valider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;garantir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;une&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;douceur&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;minimiser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;les&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;risques&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Avantages de l&amp;rsquo;automatisation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gain de temps: Plus besoin de rédiger manuellement les descriptions de merge-request.&lt;/li&gt;
&lt;li&gt;Cohérence: Le texte généré est toujours cohérent et suit un format standard.&lt;/li&gt;
&lt;li&gt;Précision: Le modèle Vertex AI peut identifier des informations importantes qui pourraient être oubliées.&lt;/li&gt;
&lt;li&gt;Meilleure communication: Des descriptions claires et précises facilitent la communication entre les membres de l&amp;rsquo;équipe.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En utilisant Vertex AI et GitLab CI, vous pouvez automatiser la génération de texte de merge-request et améliorer votre workflow de développement. Ce gain de temps et d&amp;rsquo;efficacité vous permettra de vous concentrer sur des tâches plus stratégiques.&lt;/p&gt;
&lt;p&gt;N&amp;rsquo;hésitez pas à tester cette solution et à l&amp;rsquo;adapter à vos besoins !&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="gitlab"/></entry><entry><title>Generation of text from Merge-Requests automatically in LLM</title><link href="https://www.ops-chronicles.cloud/generate-mr-text.html" rel="alternate"/><published>2024-05-29T00:00:00+02:00</published><updated>2024-05-29T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-05-29:/generate-mr-text.html</id><summary type="html">&lt;p&gt;A simple automation to generate text from Merge-Requests Gitlab to add some description&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#add-application-sources-with-correct-permissions-for-openshift"&gt;Add application sources with correct permissions for OpenShift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#install-the-dependencies"&gt;Install the dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#run-the-dockerfile"&gt;Run the dockerfile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#build-the-dockerfile"&gt;Build the dockerfile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_1"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_2"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_3"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_4"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_5"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_6"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_7"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_8"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_9"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_10"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_11"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_12"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_13"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_14"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_15"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_16"&gt;The command docker build -t generate-mr-text . will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_1"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;  This is an example, the implementation of the application may not be completely accurate. The purpose is to show you the general approach and the code may need to be adjusted to your specific needs. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Foreword:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The following guide aims to provide a comprehensive understanding of the process and code for generating a descriptive text from a Merge-Request.  The objective is to automate this task, relieving developers from the need to manually write lengthy descriptions. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before embarking on this endeavor, ensure you have the following prerequisites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A good understanding of the basic concept of merge-requests and its role in GitLab.&lt;/li&gt;
&lt;li&gt;A working knowledge of Python.&lt;/li&gt;
&lt;li&gt;A solid foundation in using GitLab&amp;rsquo;s API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the world of software development, merge-requests are crucial for integrating code changes into a main branch.  However, composing comprehensive descriptions for each merge-request can be time-consuming and sometimes tedious. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The objective is to create a Python script that automatically generates a descriptive text for each merge-request based on the changes included. This text can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A summary of the changes introduced.&lt;/li&gt;
&lt;li&gt;A link to the relevant commit.&lt;/li&gt;
&lt;li&gt;Any relevant context or background information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Implementation:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The script utilizes the GitLab API to retrieve information about the merge-request and its associated commits. This information is then processed and formatted into a comprehensive description.&lt;/p&gt;
&lt;p&gt;The Python code is presented below, along with explanations of each section.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai.generative_models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FinishReason&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vertexai.preview.generative_models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gitlab&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;GCP_PROJECT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_PROJECT_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sandbox-mgoulin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GCP_LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_LOCATION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;us-central1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GCP_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GCP_MODEL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gemin-1.5-flash-preview-0514&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;GITLAB_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_HOST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://gitlab.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GITLAB_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GITLAB_SSL_VERIFY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITLAB_SSL_VERIFY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;LLM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;As a senior Devops engineer reviewing a merge request for a Terraform module. Please provide a comprehensive description of the changes in french, focusing on the following:&lt;/span&gt;
&lt;span class="s2"&gt;* **What is the overall purpose of the changes?**&lt;/span&gt;
&lt;span class="s2"&gt;* **What specific resources are affected?**&lt;/span&gt;
&lt;span class="s2"&gt;* **What are the main changes made to those resources?**&lt;/span&gt;
&lt;span class="s2"&gt;* **Are there any security or performance implications of the changes?**&lt;/span&gt;
&lt;span class="s2"&gt;* **Are there any potential risks or considerations?**&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GCP_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GCP_LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GCP_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LLM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;safety_settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;safety_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;

&lt;span class="n"&gt;generation_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;max_output_tokens&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;temperature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;top_p&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;safety_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_HATE_SPEECH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_DANGER_OR_VIOLENCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_SEXUAL_CONTENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HARM_CATEGORY_HARASSMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MEDIUM_AND_ABOVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_mr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GITLAB_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GITLAB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssl_verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GITLAB_SSL_VERIFY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mergerequests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;generate-mr-text.py&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Use Gemini to update a merge request with a description&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-p&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--gitlab_project&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--gitlab_mr_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab_project&lt;/span&gt;
&lt;span class="n"&gt;mr_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab_mr_id&lt;/span&gt;
&lt;span class="n"&gt;mr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_mr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;#print(json.dumps(changes, indent=4))&lt;/span&gt;
&lt;span class="c1"&gt;#diff = find_diff()&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=== START BLOCK GENERATED BY generate-mr-text.py ===&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;=== START BLOCK GENERATED BY generate-mr-text.py ===&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;=== END BLOCK GENERATED BY generate-mr-text.py ===&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;In this script:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Import necessary modules&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get Environment variables:&lt;/strong&gt; this step retrieves the necessary information for connecting to GitLab and Vertex AI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define the prompt:&lt;/strong&gt;  This sets up the instructions for the language model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define the &lt;code&gt;generate&lt;/code&gt; function:&lt;/strong&gt; This function uses the Vertex AI client library to connect to the Gemini model and generate text based on the provided input.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define the &lt;code&gt;find_mr&lt;/code&gt; function:&lt;/strong&gt;  This function retrieves the Merge-Request from GitLab using the specified project and merge-request ID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define the argument parser:&lt;/strong&gt; This section creates a user-friendly interface for passing project and merge-request ID as arguments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieve the Merge-Request:&lt;/strong&gt;  The script uses the argument parser to get the project and merge-request ID and retrieves the relevant Merge-Request from GitLab.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generate the description:&lt;/strong&gt; The changes are fetched, processed, and used to generate a description.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update the Merge-Request:&lt;/strong&gt; The generated description is then added to the merge-request description field in GitLab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How to use the script:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set up the environment variables:&lt;/strong&gt; Replace the placeholders in the script with your actual values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install the required libraries:&lt;/strong&gt; Ensure that you have installed all the necessary libraries using &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run the script:&lt;/strong&gt;  Execute the script using a command similar to &lt;code&gt;python generate-mr-text.py -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Next Steps:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is just a basic example of what can be achieved with this approach. There are many other ways to enhance this script, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integrating the script into a CI/CD pipeline to automate the description generation process completely.&lt;/li&gt;
&lt;li&gt;Customizing the prompt to generate more detailed descriptions.&lt;/li&gt;
&lt;li&gt;Adding error handling to make the script more robust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By leveraging the power of the Vertex AI Gemini model, this script simplifies the process of generating descriptive text for merge-requests. This automation streamlines the development workflow and ensures that all merge-requests are well-documented.&lt;/p&gt;
&lt;h2 id="add-application-sources-with-correct-permissions-for-openshift"&gt;Add application sources with correct permissions for OpenShift&lt;a class="headerlink" href="#add-application-sources-with-correct-permissions-for-openshift" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;USER 0
ADD . .
RUN chown -R 1001:0 ./*
USER 1001&lt;/p&gt;
&lt;h2 id="install-the-dependencies"&gt;Install the dependencies&lt;a class="headerlink" href="#install-the-dependencies" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RUN pip install -r requirements.txt  &amp;amp;&amp;amp; \
    pip install -r requirements-dev.txt &lt;/p&gt;
&lt;h2 id="run-the-dockerfile"&gt;Run the dockerfile&lt;a class="headerlink" href="#run-the-dockerfile" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; \
    pip install -r requirements.txt &amp;amp;&amp;amp; \
    pip install -r requirements-dev.txt &lt;/p&gt;
&lt;h2 id="build-the-dockerfile"&gt;Build the dockerfile&lt;a class="headerlink" href="#build-the-dockerfile" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; \
    pip install -r requirements.txt &amp;amp;&amp;amp; \
    pip install -r requirements-dev.txt &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;**The command &lt;span class="sb"&gt;`docker build -t generate-mr-text .`&lt;/span&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:**

```bash
docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_1"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_1" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_2"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_2" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_3"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_3" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_4"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_4" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_5"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_5" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_6"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_6" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_7"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_7" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_8"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_8" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_9"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_9" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_10"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_10" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_11"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_11" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_12"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_12" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_13"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_13" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_14"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_14" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_15"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_15" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_16"&gt;The command &lt;code&gt;docker build -t generate-mr-text .&lt;/code&gt;  will generate the docker image. Then, you can run the docker image and specify the project and the merge request id as follows:&lt;a class="headerlink" href="#the-command-docker-build-t-generate-mr-text-will-generate-the-docker-image-then-you-can-run-the-docker-image-and-specify-the-project-and-the-merge-request-id-as-follows_16" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt;  &lt;/span&gt;generate-mr-text&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_project&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;**The command &lt;code&gt;docker run -it  generate-mr-text -p &amp;lt;gitlab_project&amp;gt; -m &amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt;  will run the container and generate a description for the specified merge-request.  **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to replace &lt;code&gt;&amp;lt;gitlab_project&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;gitlab_mr_id&amp;gt;&lt;/code&gt; with your project and merge-request id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The docker image will be built with the following steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADD . .&lt;/strong&gt;: Copy all files from the current directory to the Docker image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN chown -R 1001:0 ./&lt;/strong&gt;*: Change the ownership of all files in the image to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USER 1001&lt;/strong&gt;: Switch to user 1001.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Install the Python dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUN pip install &amp;ndash;upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; pip install -r requirements-dev.txt&lt;/strong&gt;: Update pip and install the Python dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The docker image will be built and tagged with the name &lt;code&gt;generate-mr-text&lt;/code&gt;. You can then run the container using the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id="_1"&gt;&lt;a class="headerlink" href="#_1" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;</content><category term="devops"/><category term="devops"/><category term="gitlab"/></entry><entry><title>Initialisation d'un blog avec Pelican sur GitLab Pages!</title><link href="https://www.ops-chronicles.cloud/fr/blog-on-gitlab-pages.html" rel="alternate"/><published>2024-05-28T00:00:00+02:00</published><updated>2024-05-28T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-05-28:/fr/blog-on-gitlab-pages.html</id><summary type="html">&lt;p&gt;Comment creer un blog avec Gitlab Pages et Pelican&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#un-blog-as-code"&gt;Un blog as Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#creation-du-depot-git"&gt;Création du dépôt Git&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#les-fichiers"&gt;Les fichiers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ajout-dun-themes"&gt;Ajout d&amp;rsquo;un themes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#ajout-de-domaines-pour-les-pages"&gt;Ajout de domaines pour les pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#contribuer"&gt;Contribuer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Un des défis quand on cherche à partager du contenu, c&amp;rsquo;est de le faire simplement et avec efficience. &lt;/p&gt;
&lt;p&gt;Je suis à la recherche d&amp;rsquo;un moyen d&amp;rsquo;écrire des articles autour du Devops et de publier ces articles sur un blog. L&amp;rsquo;idée est simplement de décrire comment on pratique l&amp;rsquo;installation d&amp;rsquo;applications et l&amp;rsquo;automatisation des tâches dans une DSI.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc essayé diverses méthodes pour créer un blog :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.blogger.com"&gt;Blogger&lt;/a&gt; de Google en SaaS, mais la mise en forme du code et du texte n&amp;rsquo;est pas simple&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org"&gt;Wordpress&lt;/a&gt; dans des containers, mais il y a beaucoup de paramètres à configurer avant l&amp;rsquo;utilisation.&lt;/li&gt;
&lt;li&gt;Et enfin &lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt; sur GitLab Pages, a découvrir&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voici un petit exemple de ce que ça peut donner sur blogger. Mais j&amp;rsquo;ai eu beaucoup de mal à réaliser la mise en forme :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Blogger" src="/images/2024-static-blog-with-gitlab-pages/2024-static-blog-with-gitlab-pages_1.png" /&gt;&lt;/p&gt;
&lt;p&gt;Dans cet article, je vous propose de décrire l&amp;rsquo;installation de &lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt; et l&amp;rsquo;utilisation pour développer.&lt;/p&gt;
&lt;h2 id="un-blog-as-code"&gt;Un blog as Code&lt;a class="headerlink" href="#un-blog-as-code" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Gérer le contenu de mon blog comme du code permettra de fusionner la puissance d&amp;rsquo;un gestionnaire de version comme Git (et les pipelines qui compileront le contenu) avec l&amp;rsquo;efficience d&amp;rsquo;un site web statique (html).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Workflow efficace : Git permet de gérer les modifications du contenu du blog et de les partager avec d&amp;rsquo;autres contributeurs, tandis que Pelican génère automatiquement les pages web statiques à partir du contenu.&lt;/li&gt;
&lt;li&gt;Développement agile : Les branches Git permettent de tester de nouvelles fonctionnalités ou de corriger des bugs sans affecter le contenu principal du blog.&lt;/li&gt;
&lt;li&gt;Hébergement flexible : Git permet d&amp;rsquo;héberger le blog sur des plateformes comme Gitlab Pages, tandis que Pelican génère des pages web statiques compatibles avec ces plateformes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creation-du-depot-git"&gt;Création du dépôt Git&lt;a class="headerlink" href="#creation-du-depot-git" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Gitlab propose des modèles de projet déjà prévu pour Pelican. Il suffit de créer un nouveau projet Gitlab et de sélectionner le modèle : Pages/Pelican. &lt;/p&gt;
&lt;p&gt;Ce template va initialiser le dépôt git et tout le code nécessaire pour utiliser &lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitlabTemplate" src="/images/2024-static-blog-with-gitlab-pages/2024-static-blog-with-gitlab-pages_2.png" /&gt;&lt;/p&gt;
&lt;p&gt;Cette action a créé l&amp;rsquo;arborescence du dépôt comme décrite ci-dessous, il faudra maintenant cloner sur la station de travail le code pour l&amp;rsquo;éditer en local.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://gitlab.com/&amp;lt;USERNAME&amp;gt;/&amp;lt;REPO_NAME&amp;gt;.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="les-fichiers"&gt;Les fichiers&lt;a class="headerlink" href="#les-fichiers" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Voici un petit descritifs des fichiers initialisés :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/
- content/       # Fichier de contenu du site web: articles markdown, images, ...
- pelicanconf.py # Fichier de configuration de pelican
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sous gitlab voici ce que ca donne :&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitlabTemplate" src="/images/2024-static-blog-with-gitlab-pages/2024-static-blog-with-gitlab-pages_3.png" /&gt;&lt;/p&gt;
&lt;h3 id="ajout-dun-themes"&gt;Ajout d&amp;rsquo;un themes&lt;a class="headerlink" href="#ajout-dun-themes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Après quelques recherches, j&amp;rsquo;ai choisi d&amp;rsquo;utiliser le thème &lt;a href="https://github.com/jvanz/pelican-hyde"&gt;pelican-hyde&lt;/a&gt; suffisamment épuré et élégant pour mon besoin.&lt;/p&gt;
&lt;p&gt;Pour l&amp;rsquo;installer, on télécharge le thème, on l&amp;rsquo;ajoute au dépôt git :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl -L -O https://github.com/jvanz/pelican-hyde/archive/refs/heads/master.zip
unzip master.zip
git add pelican-hyde-master
rm -r master.zip
echo &amp;quot;THEME = &amp;#39;themes-hyde&amp;#39;&amp;quot; &amp;gt;&amp;gt; pelicanconf.py
git add pelicanconf.py


git commit -m &amp;quot;Add pelican-hyde-master themes&amp;quot;
git push origin master:feat/add-pelican-hyde-master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On ira ensuite sur Git pour créer la merge-request et la merger sur la branche master&lt;/p&gt;
&lt;h2 id="ajout-de-domaines-pour-les-pages"&gt;Ajout de domaines pour les pages&lt;a class="headerlink" href="#ajout-de-domaines-pour-les-pages" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On peut ajouter dans gitlab-pages des mapping de domaines. Cela permet de servir nos pages statiques sur nos domaines sans utiliser une URL trop compliqué.&lt;/p&gt;
&lt;p&gt;De mon côté, j&amp;rsquo;ai configuré le domaine comme ci-dessous :
&lt;img alt="GitlabCustomDomain" src="/images/2024-static-blog-with-gitlab-pages/2024-static-blog-with-gitlab-pages_4.png" /&gt;&lt;/p&gt;
&lt;h2 id="contribuer"&gt;Contribuer&lt;a class="headerlink" href="#contribuer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Et voilà, il suffit maintenant de rédiger des articles au format markdown et de les pousser dans Git. On peut très bien utiliser les relectures et contributions (PullRequest) pour bénéficier de toute la puissance de Git.&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="gitlab"/></entry><entry><title>Initialization of a Blog Using Pelican and GitLab Pages!</title><link href="https://www.ops-chronicles.cloud/blog-on-gitlab-pages.html" rel="alternate"/><published>2024-05-28T00:00:00+02:00</published><updated>2024-05-28T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2024-05-28:/blog-on-gitlab-pages.html</id><summary type="html">&lt;p&gt;How to create a blog using GitLab Pages and Pelican&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-code"&gt;Using code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#contribution-to-the-gitlab-git"&gt;Contribution to the GitLab Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#list-of-features"&gt;List of features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#add-content-to-git"&gt;Add Content to Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#theming"&gt;Theming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adding-themes"&gt;Adding themes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adding-features"&gt;Adding features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#automation"&gt;Automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adding-domains"&gt;Adding domains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#adding-custom-content"&gt;Adding custom content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We will see in this quick guide how to create a blog using Pelican, which will be hosted on GitLab Pages, offering free hosting and a simple way to deploy a static site.&lt;/p&gt;
&lt;p&gt;First, we will need to choose a place to store the blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.blogger.com"&gt;Blogger&lt;/a&gt;&lt;/strong&gt; from Google is free, with a simple interface, and easy to use. You will be able to store content in the form of text and code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://wordpress.org"&gt;Wordpress&lt;/a&gt;&lt;/strong&gt; offers more options for content creators.  This option is also very easy to configure.  It is free for personal use.&lt;/li&gt;
&lt;li&gt;And finally, &lt;strong&gt;&lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt;&lt;/strong&gt; for GitLab Pages, a more advanced option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will opt for Pelican for GitLab Pages, a more comprehensive solution.&lt;/p&gt;
&lt;h2 id="using-code"&gt;Using code&lt;a class="headerlink" href="#using-code" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start by creating the blog, we will need to install Pelican and create a blog using it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pelican
pelican-quickstart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will ask you several questions, like the title, author, description, etc. We will answer them to customize the initial blog setup.&lt;/p&gt;
&lt;p&gt;Once this is done, we will be able to generate the site using Pelican:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will generate the HTML files of the blog in the output folder.&lt;/p&gt;
&lt;h2 id="contribution-to-the-gitlab-git"&gt;Contribution to the GitLab Git&lt;a class="headerlink" href="#contribution-to-the-gitlab-git" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GitLab Pages lets you host a static site from a repository. To do this, we will need to create a GitLab repository and upload the content.&lt;/p&gt;
&lt;p&gt;The easiest way is to add the output folder as a new branch of the repository.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Initial commit&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;-M&lt;span class="w"&gt; &lt;/span&gt;main
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;git@gitlab.com:&amp;lt;USERNAME&amp;gt;/&amp;lt;REPO_NAME&amp;gt;.git
git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can then go to the GitLab repository and configure GitLab Pages. It will automatically detect the presence of the index.html file and serve your website.&lt;/p&gt;
&lt;h2 id="list-of-features"&gt;List of features&lt;a class="headerlink" href="#list-of-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We will find different features in Pelican:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lives&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pelicanconf&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Pelican&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;publishconf&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Pelican&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;publication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;makefile&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Makefile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;generating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;themes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;themes&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HTML&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lives&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The most important files are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pelicanconf.py&lt;/code&gt; for configuring the blog&amp;rsquo;s settings, including the theme, title, author, and more.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publishconf.py&lt;/code&gt; for configuring the blog&amp;rsquo;s settings for publication on GitLab Pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="add-content-to-git"&gt;Add Content to Git&lt;a class="headerlink" href="#add-content-to-git" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GitLab Pages allows you to easily host a static site from a repository. You can use any branch in your repository to host a site. For example, the content of the &lt;code&gt;main&lt;/code&gt; branch is used for the &lt;code&gt;https://&amp;lt;USERNAME&amp;gt;.gitlab.io/&amp;lt;REPO_NAME&amp;gt;&lt;/code&gt; URL. If you want to create a site on the &lt;code&gt;gh-pages&lt;/code&gt; branch, its content will be used for the &lt;code&gt;https://&amp;lt;USERNAME&amp;gt;.gitlab.io/&amp;lt;REPO_NAME&amp;gt;/gh-pages&lt;/code&gt; URL.&lt;/p&gt;
&lt;h2 id="theming"&gt;Theming&lt;a class="headerlink" href="#theming" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pelican is an easy way to host a static site on GitLab Pages.  It&amp;rsquo;s a popular solution for a variety of blog configurations, but it&amp;rsquo;s not limited to these. You can even install a new theme:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pelican-themes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command will list all available themes.  To use one of them, you simply need to copy the theme folder to the &lt;code&gt;themes&lt;/code&gt; directory and modify the &lt;code&gt;pelicanconf.py&lt;/code&gt; file to point to the theme.&lt;/p&gt;
&lt;h2 id="adding-themes"&gt;Adding themes&lt;a class="headerlink" href="#adding-themes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&amp;rsquo;ve set up the basic Pelican configuration, you can start adding content to your blog. This content is stored in the &lt;code&gt;content/&lt;/code&gt; directory in Markdown files. For example, you can create a new file named &lt;code&gt;content/posts/my-first-post.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The structure of the file is quite simple, it just includes the post&amp;rsquo;s title and the content, which can be formatted using Markdown.  You can use this structure for all of your posts.&lt;/p&gt;
&lt;h2 id="adding-features"&gt;Adding features&lt;a class="headerlink" href="#adding-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can add many features to your blog, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comments: You can use a comment system like Disqus to add comments to your blog posts.&lt;/li&gt;
&lt;li&gt;Analytics: You can use Google Analytics to track traffic to your blog.&lt;/li&gt;
&lt;li&gt;Social media: You can add social media buttons to your blog to allow readers to share your content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have added all the features you need, you can generate the site and deploy it to GitLab Pages.&lt;/p&gt;
&lt;h2 id="automation"&gt;Automation&lt;a class="headerlink" href="#automation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It can be repetitive to generate and deploy the site.  This can be done automatically with &lt;code&gt;make&lt;/code&gt;.  There are many automation solutions to do this, like Github actions or Gitlab CI/CD.&lt;/p&gt;
&lt;h2 id="adding-domains"&gt;Adding domains&lt;a class="headerlink" href="#adding-domains" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use a custom domain name to host your blog.  This can be done by modifying the GitLab Pages settings.&lt;/p&gt;
&lt;h2 id="adding-custom-content"&gt;Adding custom content&lt;a class="headerlink" href="#adding-custom-content" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can add custom content to your blog, such as a portfolio section, a contact page, or an about page.  Pelican makes it easy to create new pages.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pelican is a powerful tool for creating static blogs.  GitLab Pages provides a simple and free way to host them.  By combining these two tools, you can create a beautiful and functional blog in a few hours.  You can easily customize it using themes and features.&lt;/p&gt;</content><category term="devops"/><category term="devops"/><category term="gitlab"/></entry></feed>