<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Kubernetes on Valérian Pyckaert</title>
    <link>https://valerian-pyckaert.dev/tags/kubernetes/</link>
    <description>Recent content in Kubernetes on Valérian Pyckaert</description>
    <image>
      <title>Valérian Pyckaert</title>
      <url>https://valerian-pyckaert.dev/images/default-cover.svg</url>
      <link>https://valerian-pyckaert.dev/images/default-cover.svg</link>
    </image>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Mon, 08 Jun 2026 19:00:00 +0200</lastBuildDate>
    <atom:link href="https://valerian-pyckaert.dev/tags/kubernetes/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SOPS - Comment gérer les secrets dans un repo GitOps?</title>
      <link>https://valerian-pyckaert.dev/posts/sops/</link>
      <pubDate>Mon, 08 Jun 2026 19:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/sops/</guid>
      <description>Comment gérer les secrets dans un repo GitOps avec SOPS.</description>
      <content:encoded><![CDATA[<h2 id="le-problème-des-secrets-en-gitops">Le problème des secrets en GitOps</h2>
<p>Dans une approche GitOps, tout passe par le repo Git : les manifests Kubernetes, les configurations, et idéalement les secrets aussi. Mais comment gérer les secrets de manière sécurisée dans un repo Git ?</p>
<hr>
<h2 id="sops--secrets-operations">SOPS : Secrets OPerationS</h2>
<h3 id="principe--chiffrement-côté-fichier">Principe : chiffrement côté fichier</h3>
<p><a href="https://github.com/mozilla/sops">SOPS</a> s&rsquo;intègre aux KMS, par exemple sur GCP <a href="https://cloud.google.com/security/products/security-key-management?hl=en">Cloud Key Management</a> via le principe d&rsquo;<a href="https://docs.cloud.google.com/kms/docs/envelope-encryption?hl=en">Envelope Encryption</a> : il génère une clé symétrique éphémère (DEK) pour chiffrer les secrets, et délègue à GCP KMS le chiffrement de cette DEK.</p>
<p>Le KMS ne voit jamais les secrets eux-mêmes, et le fichier commité dans Git contient les valeurs chiffrées ainsi que la DEK chiffrée.</p>
<p>En local, un <code>gcloud auth application-default login</code> suffit alors qu&rsquo;en production sur GKE il est plutot préférable d&rsquo;utiliser le <code>Workload Identity</code> pour lier le <code>ServiceAccount</code> Kubernetes au compte de service GCP.</p>
<p>Chaque opération est enfin tracée dans <a href="https://cloud.google.com/logging/docs/audit">Cloud Audit Logs</a>.</p>
<h3 id="limitations">Limitations</h3>
<ul>
<li>Nécessite une infrastructure de gestion de clés externe (KMS ou PGP/Age). Cela introduit une dépendance sur AWS KMS, GCP KMS, etc.</li>
<li>La rotation de clé implique de re-chiffrer tous les fichiers concernés (<code>sops updatekeys</code>).</li>
<li>SOPS ne crée pas de Secret Kubernetes directement. Il faut un outil intermédiaire : Helm Secrets, ArgoCD avec plugin, Kustomize + plugin, ou un job de déchiffrement en pipeline CI/CD.</li>
<li>Le déchiffrement se fait souvent dans le pipeline CI/CD ou sur le poste du développeur. La valeur en clair transite quelque part, ce qui élargit la surface d&rsquo;attaque.</li>
<li>Contrairement à Sealed Secrets + un opérateur GitOps, SOPS seul ne met pas à jour les secrets dans le cluster automatiquement.</li>
</ul>
<p><em>C&rsquo;est l&rsquo;outil que j&rsquo;ai le plus utilisé dans un contexte professionnel, je trouve que c&rsquo;est un bon compromis pour gérer les secrets en GitOps à condition d&rsquo;accepter la complexité de gestion des clés et de mettre en place les bonnes pratiques pour éviter les erreurs humaines (ex: commit de secrets en clair) qui <code>spoiler</code> finiront par arriver</em></p>
<h3 id="sops--argocd">SOPS + ArgoCD</h3>
<p>Sops s&rsquo;intègre très bien avec ArgoCD via le plugin <a href="https://argocd-vault-plugin.readthedocs.io/en/stable/">argocd-vault-plugin</a>.</p>
<p>Sinon il est aussi possible d&rsquo;installer Helm Secrets comme <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/">Config Management Plugin</a>.</p>
<h3 id="exemple-dutilisation">Exemple d&rsquo;utilisation</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Chiffrer un fichier de secrets</span>
</span></span><span class="line"><span class="cl">sops -e secrets.yaml &gt; secrets.enc.yaml
</span></span><span class="line"><span class="cl"><span class="c1"># Déchiffrer un fichier de secrets</span>
</span></span><span class="line"><span class="cl">sops -d secrets.enc.yaml &gt; secrets.yaml
</span></span><span class="line"><span class="cl"><span class="c1"># Mettre à jour les clés de chiffrement</span>
</span></span><span class="line"><span class="cl">sops updatekeys secrets.enc.yaml
</span></span><span class="line"><span class="cl"><span class="c1"># Editer un fichier de secrets chiffré</span>
</span></span><span class="line"><span class="cl">sops edit secrets.enc.yaml
</span></span></code></pre></div><hr>
<h2 id="les-alternatives-et-leurs-limites">Les alternatives et leurs limites</h2>
<h3 id="sealed-secrets--côté-cluster-clé-non-portable">Sealed Secrets : côté cluster, clé non portable</h3>
<p>Les Sealed Secrets(<a href="https://github.com/bitnami-labs/sealed-secrets">GitHub</a>) de Bitnami permettent de chiffrer les secrets côté cluster.</p>
<p>Sealed Secrets est composé en 2 parties:</p>
<ul>
<li>Un controlleur côté cluster (installable via <a href="https://bitnami-labs.github.io/sealed-secrets">Helm</a>)</li>
<li>Une CLI côté client : <a href="https://kubeseal.com/">kubeseal</a> (<code>brew install kubeseal</code>)</li>
</ul>
<p>kubeseal utilise une clé asymétrique pour chiffrer les secrets que seul le contrôleur peut déchiffrer.
Ces secrets chiffrés sont ensuite encodés dans une ressource SealedSecret.</p>
<p><strong>Limitations</strong></p>
<p>Voici selon moi les principales limitations de Sealed Secrets :</p>
<ul>
<li>La clé de chiffrement est stockée dans le cluster, ce qui rend les secrets non portables entre clusters.</li>
<li>En cas de compromission du cluster, les secrets peuvent être à risque.</li>
<li>En cas de disaster recovery, la restauration du cluster doit inclure la restauration de la clé de chiffrement pour pouvoir accéder aux secrets.</li>
<li>Les secrets sont uniquement utilisable dans un contexte Kubernetes, pas de possibilité de les utiliser pour d&rsquo;autres usages (CI/CD, etc).</li>
</ul>
<p><em>Je n&rsquo;ai pas eu l&rsquo;occasion d&rsquo;utiliser Sealed Secrets dans un milieu professionnel, mais j&rsquo;ai pu l&rsquo;expérimenter dans des projets personnels.</em></p>
<h3 id="vault--puissant-mais-lourd-à-opérer">Vault : puissant mais lourd à opérer</h3>
<p><a href="https://developer.hashicorp.com/vault">Hashicorp Vault</a> est un serveur centralisé de gestion de secrets. Ils sont stockés, chiffrés et distribués dynamiquement via une API et les applications les récupèrent au runtime.</p>
<p><strong>Limitations</strong></p>
<ul>
<li>Complexité opérationnelle élevée : Vault est un service à part entière qu&rsquo;il faut déployer, configurer, superviser et maintenir.</li>
<li>Single point of failure potentiel : Si Vault est down, toutes les applications qui récupèrent leurs secrets dynamiquement peuvent être impactées.</li>
<li>C&rsquo;est un outil (trop) complet : Les concepts sont nombreux et nécessitent un temps de montée en compétence réel avant d&rsquo;opérer Vault sereinement.</li>
</ul>
<p><em>C&rsquo;est un outil que j&rsquo;ai eu l&rsquo;occasion d&rsquo;utiliser mais je n&rsquo;ai jamais eu la &ldquo;chance&rdquo; de le déployer et l&rsquo;opérer moi même de A à Z, je ne me sens pas légitime pour en parler plus en détail dans cet article.</em></p>
<h2 id="a-retenir">A Retenir</h2>
<p>SOPS est un outil de chiffrement de secrets qui peut s&rsquo;intégrer dans une approche GitOps. Comme tout outil, il vient avec ses avantages et ses inconvénients, il est important de les connaître pour faire un choix éclairé en fonction de son contexte et de ses besoins.</p>
<p>Pour résumer très grossièrement préférez <code>Sealed Secrets</code> pour démarrer et les cas d&rsquo;usage simples, <code>SOPS</code> pour les cas d&rsquo;usage plus complexes et les équipes qui ont déjà une bonne maîtrise de la gestion de clés, et <code>Vault</code> pour les besoins les plus avancés et les équipes qui ont les ressources pour l&rsquo;opérer sereinement.</p>
<hr>
<p><strong>Sources</strong></p>
<ul>
<li><a href="https://github.com/getsops/sops">SOPS GitHub</a></li>
<li><a href="https://blog.stephane-robert.info/docs/conteneurs/orchestrateurs/outils/sealed-secrets/">Blog de Stéphane Robert - Sealed Secrets</a></li>
<li><a href="https://developer.hashicorp.com/vault/docs">Vault Documentation</a></li>
<li><a href="https://argocd-vault-plugin.readthedocs.io/en/stable/backends/#sops">ArgoCD Vault Plugin</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Cmctl - La CLI pour Cert Manager</title>
      <link>https://valerian-pyckaert.dev/posts/cmctl/</link>
      <pubDate>Thu, 04 Jun 2026 19:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/cmctl/</guid>
      <description>Mes commandes préférées pour interagir avec Cert Manager via cmctl.</description>
      <content:encoded><![CDATA[<h2 id="quest-ce-que-cest-">Qu&rsquo;est-ce que c&rsquo;est ?</h2>
<p>CMCTL est un outil en ligne de commande pour interagir avec Cert Manager. CMCTL permet de gérer les ressources Cert Manager, de diagnostiquer les problèmes et d&rsquo;automatiser certaines tâches liées à la gestion des certificats.
Puisque nous l&rsquo;avons installé dans le précédent article il me parassait intéressant de partager mes commandes préférées pour interagir avec Cert Manager via cmctl.</p>
<h2 id="installation">Installation</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install cert-manager/cert-manager/cmctl
</span></span></code></pre></div><p>Sinon pour Windows via scoop</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">scoop install main/cmctl
</span></span></code></pre></div><h2 id="utilisation">Utilisation</h2>
<h3 id="commandes-préférées">Commandes préférées</h3>
<p>Afficher les certificats gérés par Cert Manager :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get certificates -A
</span></span></code></pre></div><p>Afficher les certificate requests en cours :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get certificaterequest -A
</span></span></code></pre></div><p>Renouveler un certificat manuellement :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cmctl renew  &lt;nom-du-certificat&gt;
</span></span></code></pre></div><p>Approuver/Désapprouver une demande de certificat manuellement :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cmctl approve -n istio-system mesh-ca --reason <span class="s2">&#34;pki-team&#34;</span> --message <span class="s2">&#34;this certificate is valid&#34;</span>
</span></span><span class="line"><span class="cl">Approved CertificateRequest <span class="s1">&#39;istio-system/mesh-ca&#39;</span>
</span></span><span class="line"><span class="cl">$ cmctl deny -n my-app my-app --reason <span class="s2">&#34;example.com&#34;</span> --message <span class="s2">&#34;violates policy&#34;</span>
</span></span><span class="line"><span class="cl">Denied CertificateRequest <span class="s1">&#39;my-app/my-app&#39;</span>
</span></span></code></pre></div><p>Inspecter un certificat en détail :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cmctl inspect &lt;nom-du-certificat&gt;
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>C&rsquo;est un article volontairement cours pour partager cet outil et les commandes que j&rsquo;utilise le plus souvent pour manipuler les ressources de Cert Manager.</p>
<hr>
<p>Sources</p>
<ul>
<li><a href="https://cert-manager.io/docs/reference/cmctl/">https://cert-manager.io/docs/reference/cmctl/</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TheForge #4 — Installation de Nginx Gateway Fabric &amp; Cert manager pour exposer les services sur Kubernetes</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-gateway/</link>
      <pubDate>Wed, 03 Jun 2026 19:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-gateway/</guid>
      <description>Apprenez à installer Nginx Gateway Fabric pour exposer vos services sur Kubernetes.</description>
      <content:encoded><![CDATA[<h2 id="ce-quon-va-faire">Ce qu&rsquo;on va faire</h2>
<blockquote>
<p>Nous allons installer Nginx Gateway Fabric pour exposer nos services Kubernetes sur internet, ainsi que Cert Manager pour gérer les certificats TLS.</p>
</blockquote>
<h2 id="architecture-cible">Architecture cible</h2>
<p>Je possède un nom de domaine chez Cloudflare, je vais donc utiliser Cloudflare comme DNS provider pour gérer les enregistrements DNS de mon domaine. Nginx Gateway Fabric sera configuré pour utiliser un LoadBalancer de type External, ce qui me permettra d&rsquo;obtenir une adresse IP publique pour exposer mes services.</p>
<p>Voici un schéma pour une meilleur compréhension :</p>
<pre class="mermaid">
  graph TD
    A[Internet] --&gt; B[Cloudflare DNS + Proxy]
    B --&gt; C[Enregistrement DNS pointant vers l&#39;IP du LoadBalancer GCP]
    C --&gt; D[LoadBalancer GCP]
    D --&gt; E[Nginx Gateway Fabric sur Kubernetes]
    E --&gt; F[HTTPRoute -&gt; Services Kubernetes]
    F --&gt; G[Cert Manager pour TLS]
</pre>

<hr>
<h2 id="étapes">Étapes</h2>
<h3 id="1-installation-de-la-crd-gateway-api">1. Installation de la CRD gateway API</h3>
<p>Les ingress controller ne sont plus supportés, il faut désormais ce tourner vers les gateway API. Il existe plusieurs implémentations, j&rsquo;ai choisi d&rsquo;installer Nginx Gateway Fabric qui est une solution robuste et bien supportée.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
</span></span></code></pre></div><p>ou via ArgoCD (la sync-wave à 0 permet de s&rsquo;assurer que les CRDs sont installées avant les autres ressources) :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gateway-api-crds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">argocd.argoproj.io/sync-wave</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/nginx/nginx-gateway-fabric.git</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="l">v2.6.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">config/crd/gateway-api/standard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ServerSideApply=true</span><span class="w">
</span></span></span></code></pre></div><h3 id="2-installation-de-nginx-gateway-fabric">2. Installation de Nginx Gateway Fabric</h3>
<p>Je vais aller assez vite sur l&rsquo;installation de Nginx Gateway Fabric, si vous avez suivi les articles précédents cela ne devrait pas poser de problème.</p>
<p>On ajoute dans notre repo de CD la configuration suivante :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-gateway-fabric</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">argocd.argoproj.io/sync-wave</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/nginx/charts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-gateway-fabric</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">2.6.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">releaseName</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-gateway-fabric</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span></code></pre></div><p>Après quelques secondes les ressources sont créées et nous avons notre gateway qui tourne.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n nginx-gateway
</span></span><span class="line"><span class="cl">NAME                                    READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">nginx-gateway-fabric-77c559bb4f-c7zlb   1/1     Running   <span class="m">0</span>          93s
</span></span></code></pre></div><h3 id="3-installation-de-cert-manager">3. Installation de Cert Manager</h3>
<p>Le principe est le même, voici la configuration à appliquer :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">argocd.argoproj.io/sync-wave</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://charts.jetstack.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="l">v1.20.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">releaseName</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">values</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        crds:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true
</span></span></span><span class="line"><span class="cl"><span class="sd">          keep: true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ServerSideApply=true</span><span class="w">
</span></span></span></code></pre></div><p>Après quelques secondes les ressources sont créées et nous avons notre cert manager qui tourne.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n cert-manager
</span></span><span class="line"><span class="cl">NAME                                       READY   STATUS              RESTARTS   AGE
</span></span><span class="line"><span class="cl">cert-manager-65dd558d9c-qzjw9              1/1     Running             <span class="m">0</span>          5m6s
</span></span><span class="line"><span class="cl">cert-manager-cainjector-77bd875bf4-6bcpn   1/1     Running             <span class="m">0</span>          5m5s
</span></span><span class="line"><span class="cl">cert-manager-startupapicheck-fhsng         0/1     ContainerCreating   <span class="m">0</span>          2m29s
</span></span><span class="line"><span class="cl">cert-manager-webhook-69666f4d4c-7wccg      1/1     Running             <span class="m">0</span>          5m6s
</span></span></code></pre></div><hr>
<h3 id="4-configuration-cloudflare">4. Configuration Cloudflare</h3>
<h4 id="récupération-de-lip-du-loadbalancer-gcp">Récupération de l&rsquo;IP du LoadBalancer GCP</h4>
<p>Maintenant que ma gateway est en place, je vais configurer Cloudflare pour pointer mon domaine vers l&rsquo;IP publique de mon LoadBalancer GCP. Je vais créer un enregistrement A dans Cloudflare qui pointe vers l&rsquo;IP du LoadBalancer, et je vais activer le proxy de Cloudflare pour bénéficier de la protection DDoS et du CDN.</p>
<p>Pour cela il faut récupérer l&rsquo;IP du LoadBalancer GCP qui a été créé pour exposer Nginx Gateway Fabric.
Vous pouvez le faire avec la commande suivante :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get svc -n nginx-gateway
</span></span><span class="line"><span class="cl"><span class="c1"># Noter l&#39;EXTERNAL-IP → à mettre dans Cloudflare DNS (enregistrement A)</span>
</span></span></code></pre></div><h4 id="création-dun-token-api-cloudflare">Création d&rsquo;un token API Cloudflare</h4>
<p>Pour automatiser la configuration de Cloudflare, je vais créer un token API avec les permissions nécessaires pour gérer les enregistrements DNS.
Dans Cloudflare → My Profile → API Tokens → Create Token :</p>
<p>Template : Edit zone DNS
Permissions : Zone / DNS / Edit
Zone : mon domaine (ici <code>valerian-pyckaert.dev</code>)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create secret generic cloudflare-api-token <span class="se">\
</span></span></span><span class="line"><span class="cl">  --namespace cert-manager <span class="se">\
</span></span></span><span class="line"><span class="cl">  --from-literal<span class="o">=</span>api-token<span class="o">=</span>&lt;TOKEN_CF&gt;
</span></span></code></pre></div><h3 id="5-configuration-de-cert-manager-pour-cloudflare">5. Configuration de Cert Manager pour Cloudflare</h3>
<p>Il s&rsquo;agit maintenant d&rsquo;ajouter une ressource <code>ClusterIssuer</code> pour utiliser le token API Cloudflare et gérer les certificats TLS automatiquement.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">acme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">email</span><span class="p">:</span><span class="w"> </span><span class="l">ton@email.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://acme-v02.api.letsencrypt.org/directory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privateKeySecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">solvers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">dns01</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cloudflare</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">apiTokenSecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cloudflare-api-token</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">api-token</span><span class="w">
</span></span></span></code></pre></div><p>Une fois cette configuration appliquée, Cert Manager pourra automatiquement obtenir et renouveler les certificats TLS pour les domaines gérés via Cloudflare.
Pour vérifier le bon fonctionnement :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get clusterissuer -A
</span></span><span class="line"><span class="cl">NAME               READY   AGE
</span></span><span class="line"><span class="cl">letsencrypt-prod   True    17h
</span></span><span class="line"><span class="cl">kubectl get certificate -A
</span></span><span class="line"><span class="cl">default     wildcard-cert   True    wildcard-cert   54m
</span></span></code></pre></div><h3 id="6-configuration-dune-httproute-pour-exposer-un-service">6. Configuration d&rsquo;une HTTPRoute pour exposer un service</h3>
<p>Maintenant que tout est en place, nous allons créer une ressource <code>HTTPRoute</code> pour exposer un service Kubernetes via Nginx Gateway Fabric et positionner le certificat généré par Cert Manager.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">main-gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cert-manager.io/cluster-issuer</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gatewayClassName</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">listeners</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">https</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;*.mondomaine.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">443</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">HTTPS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l">Terminate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">certificateRefs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">wildcard-cert</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HTTPRoute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">parentRefs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">main-gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hostnames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;app.mondomaine.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">backendRefs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span></code></pre></div><h2 id="résultat">Résultat</h2>
<p>Si vous avez suivi chaque étapes vous devriez avoir un résultat similaire à celui-ci :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ kubectl get gateway -A
</span></span><span class="line"><span class="cl">NAMESPACE   NAME           CLASS   ADDRESS         PROGRAMMED   AGE
</span></span><span class="line"><span class="cl">default     main-gateway   nginx   34.156.85.195   True         29m
</span></span><span class="line"><span class="cl">❯ kubectl get httproute -A
</span></span><span class="line"><span class="cl">NAMESPACE   NAME        HOSTNAMES                         AGE
</span></span><span class="line"><span class="cl">default     nginx-app   <span class="o">[</span><span class="s2">&#34;nginx.valerian-pyckaert.dev&#34;</span><span class="o">]</span>   29m
</span></span><span class="line"><span class="cl">❯ kubectl get certificate -A
</span></span><span class="line"><span class="cl">NAMESPACE   NAME            READY   SECRET          AGE
</span></span><span class="line"><span class="cl">default     wildcard-cert   True    wildcard-cert   21m
</span></span></code></pre></div><p>Votre service devrait être accessible via l&rsquo;URL <code>https://nginx.mondomaine.com</code> et le certificat TLS devrait être valide, en l&rsquo;occurence pour moi c&rsquo;était <code>https://nginx.valerian-pyckaert.dev</code> avec un certificat pour <code>valerian-pyckaert.dev</code> généré par Let&rsquo;s Encrypt.</p>
<p><img alt="Welcome nginx" loading="lazy" src="/the-forge-welcome-nginx.png"></p>
<p>Pour ce faire j&rsquo;ai enregistré un enregistrement A dans Cloudflare qui pointe vers l&rsquo;IP du LoadBalancer GCP :
<img alt="Cloudflare record" loading="lazy" src="/the-forge-cloudflare-record.png"></p>
<hr>
<h2 id="obstacles-rencontrés">Obstacles rencontrés</h2>
<h3 id="la-gateway-ne-peut-pas-accéder-au-namespace-demo-et-au-service-nginx">La gateway ne peut pas accéder au namespace <code>demo</code> et au service <code>nginx</code></h3>
<p>J&rsquo;avais installé ma ressource <code>HTTPRoute</code> dans le namespace <code>default</code> alors que mon service <code>nginx</code> était dans le namespace <code>demo</code>. Par défaut, la gateway n&rsquo;a pas accès aux ressources d&rsquo;autres namespaces, il faut donc lui accorder les permissions nécessaires.</p>
<p>Soit via un <code>ReferenceGrant</code> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ReferenceGrant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">allow-gateway-to-demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HTTPRoute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">to</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span></code></pre></div><p>Soit installé la ressource <code>HTTPRoute</code> dans le même namespace que le service <code>nginx</code> (ici <code>demo</code>).</p>
<h3 id="pods-en-pending">Pods en pending</h3>
<p>Par péché de rapidité j&rsquo;ai volontairement omis de spécifier les ressources <code>CPU</code> et <code>memory</code> de mes workloads, ce qui a pour conséquence que les pods sont restés en <code>pending</code> car le scheduler ne pouvait pas les placer sur un nœud. Il faut donc veiller à toujours spécifier les ressources de vos workloads pour éviter ce genre de problème.</p>
<hr>
<h2 id="pour-la-suite">Pour la suite</h2>
<p>Dans le prochain et dernier article de cette série <code>The Forge</code> je vous parlerai de SOPS et de comment gérer vos secrets de manière sécurisée dans Kubernetes. Nous verrons comment chiffrer vos secrets avec SOPS et les stocker dans Git pour une gestion centralisée et sécurisée de vos secrets.</p>
]]></content:encoded>
    </item>
    <item>
      <title>TheForge #3 — Monitoring Kubernetes avec Prometheus, Grafana et Loki</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-monitoring/</link>
      <pubDate>Sun, 31 May 2026 09:35:18 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-monitoring/</guid>
      <description>Installation d&amp;#39;une stack de monitoring complète sur Kubernetes avec Prometheus, Grafana et Loki.</description>
      <content:encoded><![CDATA[<hr>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Un cluster GKE up and running</li>
<li>Connaissance de base de Kubernetes et GitOps</li>
<li>ArgoCD installé et configuré</li>
</ul>
<hr>
<h2 id="étapes">Étapes</h2>
<h3 id="1-quest-ce-quon-va-installer-">1. Qu&rsquo;est ce qu&rsquo;on va installer ?</h3>
<table>
  <thead>
      <tr>
          <th>Outil</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prometheus</td>
          <td>Système de surveillance open-source qui collecte et stocke des métriques sous forme de séries temporelles en interrogeant périodiquement des endpoints HTTP.</td>
      </tr>
      <tr>
          <td>Kube State Metrics</td>
          <td>Service Kubernetes qui expose l&rsquo;état des objets du cluster (pods, deployments, nodes…) sous forme de métriques consommables par Prometheus.</td>
      </tr>
      <tr>
          <td>Grafana</td>
          <td>Plateforme de visualisation qui permet de créer des dashboards interactifs à partir de sources de données comme Prometheus.</td>
      </tr>
      <tr>
          <td>Alertmanager</td>
          <td>Composant de l&rsquo;écosystème Prometheus qui gère le routage, le regroupement et l&rsquo;envoi des alertes vers des canaux de notification (Slack, e-mail, PagerDuty…).</td>
      </tr>
      <tr>
          <td>Loki</td>
          <td>Système d&rsquo;agrégation de logs inspiré de Prometheus, conçu pour indexer uniquement les métadonnées des logs afin de réduire les coûts de stockage.</td>
      </tr>
      <tr>
          <td>Alloy</td>
          <td>Agent de collecte de données de Grafana Labs, successeur de Grafana Agent, capable de collecter métriques, logs et traces pour les acheminer vers différents backends.</td>
      </tr>
  </tbody>
</table>
<h3 id="1-installation-de-la-kube-prometheus-stack-prometheus--grafana--alertmanager">1. Installation de la kube-prometheus-stack (Prometheus + Grafana + Alertmanager)</h3>
<p>Si vous avez suivi l&rsquo;article précédent, rien de plus simple : il suffit d&rsquo;ajouter une nouvelle application dans <code>apps/</code> dans notre repo de CD.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kube-prometheus-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://prometheus-community.github.io/helm-charts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">kube-prometheus-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">86.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">releaseName</span><span class="p">:</span><span class="w"> </span><span class="l">kube-prometheus-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">valueFiles</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">values</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        grafana:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true
</span></span></span><span class="line"><span class="cl"><span class="sd">          adminPassword: admin
</span></span></span><span class="line"><span class="cl"><span class="sd">          service:
</span></span></span><span class="line"><span class="cl"><span class="sd">            type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="sd">        prometheus:
</span></span></span><span class="line"><span class="cl"><span class="sd">          prometheusSpec:
</span></span></span><span class="line"><span class="cl"><span class="sd">            retention: 7d
</span></span></span><span class="line"><span class="cl"><span class="sd">            resources:
</span></span></span><span class="line"><span class="cl"><span class="sd">              requests:
</span></span></span><span class="line"><span class="cl"><span class="sd">                cpu: 200m
</span></span></span><span class="line"><span class="cl"><span class="sd">                memory: 512Mi
</span></span></span><span class="line"><span class="cl"><span class="sd">              limits:
</span></span></span><span class="line"><span class="cl"><span class="sd">                memory: 2Gi
</span></span></span><span class="line"><span class="cl"><span class="sd">            storageSpec: {}
</span></span></span><span class="line"><span class="cl"><span class="sd">        alertmanager:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true
</span></span></span><span class="line"><span class="cl"><span class="sd">        kubeStateMetrics:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true
</span></span></span><span class="line"><span class="cl"><span class="sd">        nodeExporter:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">monitoring</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ServerSideApply=true</span><span class="w">
</span></span></span></code></pre></div><p>On push ce fichier <code>kube-prometheus-stack.yaml</code> et Argo se charge de déployer la stack de monitoring dans notre cluster.</p>
<p>Après quelques minutes d&rsquo;attente, vous devriez voir les pods de Prometheus, Grafana et Alertmanager en cours d&rsquo;exécution dans le namespace <code>monitoring</code> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n monitoring
</span></span><span class="line"><span class="cl">NAME                                                        READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">alertmanager-kube-prometheus-stack-alertmanager-0           2/2     Running   <span class="m">0</span>          2m30s
</span></span><span class="line"><span class="cl">kube-prometheus-stack-admission-create-ljf79                0/1     Pending   <span class="m">0</span>          41s
</span></span><span class="line"><span class="cl">kube-prometheus-stack-grafana-6ddb85bf55-j5shg              3/3     Running   <span class="m">0</span>          2m35s
</span></span><span class="line"><span class="cl">kube-prometheus-stack-kube-state-metrics-5fc57bf8f9-vhfhq   1/1     Running   <span class="m">0</span>          2m35s
</span></span><span class="line"><span class="cl">kube-prometheus-stack-operator-585c4957f7-2r772             1/1     Running   <span class="m">0</span>          2m35s
</span></span><span class="line"><span class="cl">prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   <span class="m">0</span>          2m30s
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>Grafana est désormais accessible via un port-forwarding :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
</span></span></code></pre></div><p>Rendez-vous sur <code>http://localhost:3000</code> et connectez-vous avec les identifiants <code>admin/admin</code>.</p>
<p>Cliquez ensuite sur &ldquo;Dashboards&rdquo; pour voir la liste des dashboards disponibles par défaut avec la chart :
<img alt="Dashboards Grafana" loading="lazy" src="/dashboard-grafana.png"></p>
<h3 id="3-collecte-des-logs-avec-loki">3. Collecte des Logs avec Loki</h3>
<p>Là encore il suffit de créer une nouvelle application ArgoCD qui pointe vers le chart Helm de Loki pour déployer un stack de collecte de logs dans notre cluster.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">loki</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://grafana.github.io/helm-charts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">loki</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">7.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">releaseName</span><span class="p">:</span><span class="w"> </span><span class="l">loki</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">values</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        deploymentMode: SingleBinary
</span></span></span><span class="line"><span class="cl"><span class="sd">        loki:
</span></span></span><span class="line"><span class="cl"><span class="sd">          auth_enabled: false
</span></span></span><span class="line"><span class="cl"><span class="sd">          commonConfig:
</span></span></span><span class="line"><span class="cl"><span class="sd">            replication_factor: 1
</span></span></span><span class="line"><span class="cl"><span class="sd">          storage:
</span></span></span><span class="line"><span class="cl"><span class="sd">            type: filesystem
</span></span></span><span class="line"><span class="cl"><span class="sd">          schemaConfig:
</span></span></span><span class="line"><span class="cl"><span class="sd">            configs:
</span></span></span><span class="line"><span class="cl"><span class="sd">              - from: &#34;2024-01-01&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                store: tsdb
</span></span></span><span class="line"><span class="cl"><span class="sd">                object_store: filesystem
</span></span></span><span class="line"><span class="cl"><span class="sd">                schema: v13
</span></span></span><span class="line"><span class="cl"><span class="sd">                index:
</span></span></span><span class="line"><span class="cl"><span class="sd">                  prefix: index_
</span></span></span><span class="line"><span class="cl"><span class="sd">                  period: 24h
</span></span></span><span class="line"><span class="cl"><span class="sd">        singleBinary:
</span></span></span><span class="line"><span class="cl"><span class="sd">          replicas: 1
</span></span></span><span class="line"><span class="cl"><span class="sd">          persistence:
</span></span></span><span class="line"><span class="cl"><span class="sd">            size: 10Gi
</span></span></span><span class="line"><span class="cl"><span class="sd">        read:
</span></span></span><span class="line"><span class="cl"><span class="sd">          replicas: 0
</span></span></span><span class="line"><span class="cl"><span class="sd">        write:
</span></span></span><span class="line"><span class="cl"><span class="sd">          replicas: 0
</span></span></span><span class="line"><span class="cl"><span class="sd">        backend:
</span></span></span><span class="line"><span class="cl"><span class="sd">          replicas: 0
</span></span></span><span class="line"><span class="cl"><span class="sd">        gateway:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: true
</span></span></span><span class="line"><span class="cl"><span class="sd">        chunksCache:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: false
</span></span></span><span class="line"><span class="cl"><span class="sd">        resultsCache:
</span></span></span><span class="line"><span class="cl"><span class="sd">          enabled: false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">monitoring</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ServerSideApply=true</span><span class="w">
</span></span></span></code></pre></div><p>Enfin il faut indiquer à Grafana où trouver Loki pour pouvoir visualiser les logs dans notre dashboard.</p>
<p>Pour cela, on ajoute une datasource Loki dans Grafana en utilisant un ConfigMap Kubernetes :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">values</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        grafana:
</span></span></span><span class="line"><span class="cl"><span class="sd">          additionalDataSources:
</span></span></span><span class="line"><span class="cl"><span class="sd">            - name: Loki
</span></span></span><span class="line"><span class="cl"><span class="sd">              type: loki
</span></span></span><span class="line"><span class="cl"><span class="sd">              access: proxy
</span></span></span><span class="line"><span class="cl"><span class="sd">              url: http://loki-gateway.monitoring.svc.cluster.local
</span></span></span><span class="line"><span class="cl"><span class="sd">              jsonData:
</span></span></span><span class="line"><span class="cl"><span class="sd">                maxLines: 1000</span><span class="w">
</span></span></span></code></pre></div><p>Et voilà le résultat :</p>
<p><img alt="Logs loki" loading="lazy" src="/loki.png"></p>
<h3 id="4-collecte-des-logs-de-notre-application">4. Collecte des logs de notre application</h3>
<p>Pour l&rsquo;heure, les logs de notre application nginx ne sont pas collectés par Loki.
Nous allons déployer Alloy en tant que DaemonSet dans notre cluster pour collecter les logs de tous les pods et les envoyer à Loki, il s&rsquo;agit ici encore d&rsquo;ajouter une nouvelle application ArgoCD dans notre repo de CD :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">alloy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://grafana.github.io/helm-charts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">alloy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">1.16.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">releaseName</span><span class="p">:</span><span class="w"> </span><span class="l">alloy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">values</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        alloy:
</span></span></span><span class="line"><span class="cl"><span class="sd">          configMap:
</span></span></span><span class="line"><span class="cl"><span class="sd">            content: |
</span></span></span><span class="line"><span class="cl"><span class="sd">              discovery.kubernetes &#34;pods&#34; {
</span></span></span><span class="line"><span class="cl"><span class="sd">                role = &#34;pod&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">              }
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              discovery.relabel &#34;pods&#34; {
</span></span></span><span class="line"><span class="cl"><span class="sd">                targets = discovery.kubernetes.pods.targets
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                rule {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  source_labels = [&#34;__meta_kubernetes_namespace&#34;]
</span></span></span><span class="line"><span class="cl"><span class="sd">                  target_label  = &#34;namespace&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                }
</span></span></span><span class="line"><span class="cl"><span class="sd">                rule {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  source_labels = [&#34;__meta_kubernetes_pod_name&#34;]
</span></span></span><span class="line"><span class="cl"><span class="sd">                  target_label  = &#34;pod&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                }
</span></span></span><span class="line"><span class="cl"><span class="sd">                rule {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  source_labels = [&#34;__meta_kubernetes_pod_container_name&#34;]
</span></span></span><span class="line"><span class="cl"><span class="sd">                  target_label  = &#34;container&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                }
</span></span></span><span class="line"><span class="cl"><span class="sd">                rule {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  source_labels = [&#34;__meta_kubernetes_pod_label_app&#34;]
</span></span></span><span class="line"><span class="cl"><span class="sd">                  target_label  = &#34;app&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                }
</span></span></span><span class="line"><span class="cl"><span class="sd">              }
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              loki.source.kubernetes &#34;pods&#34; {
</span></span></span><span class="line"><span class="cl"><span class="sd">                targets    = discovery.relabel.pods.output
</span></span></span><span class="line"><span class="cl"><span class="sd">                forward_to = [loki.write.default.receiver]
</span></span></span><span class="line"><span class="cl"><span class="sd">              }
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              loki.write &#34;default&#34; {
</span></span></span><span class="line"><span class="cl"><span class="sd">                endpoint {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  url = &#34;http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                }
</span></span></span><span class="line"><span class="cl"><span class="sd">              }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">monitoring</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span></code></pre></div><p>La configuration utilise la syntaxe River d&rsquo;Alloy pour :</p>
<ul>
<li>Découvrir tous les pods Kubernetes</li>
<li>Extraire les labels namespace, pod, container et app</li>
<li>Envoyer les logs vers votre Loki gateway</li>
<li>Alloy sera déployé comme DaemonSet et collectera les logs de votre nginx. Dans Grafana, vous pourrez les consulter avec :</li>
</ul>
<blockquote>
<p>{namespace=&ldquo;demo&rdquo;, app=&ldquo;nginx&rdquo;}</p>
</blockquote>
<p>De retour sur Grafana, la liste des filtres disponibles est désormais bien plus fournie :
<img alt="Filtres Loki" loading="lazy" src="/filtres-loki.png"></p>
<p><img alt="Logs Loki" loading="lazy" src="/logs-loki.png"></p>
<hr>
<h2 id="résultat">Résultat</h2>
<p>Dans cet article nous avons vu comment installer une stack de monitoring complète sur Kubernetes avec Prometheus, Grafana et Loki, le tout déployé en GitOps via ArgoCD.
Nous avons également vu comment collecter les logs de nos pods sur notre cluster avec Alloy et les visualiser dans Grafana.</p>
<p>Si vous avez suivi toutes les étapes de cet article vous devriez avoir les workloads suivantes :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n monitoring
</span></span><span class="line"><span class="cl">NAME                                                        READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">alertmanager-kube-prometheus-stack-alertmanager-0           2/2     Running   <span class="m">0</span>          21m
</span></span><span class="line"><span class="cl">alloy-jb2nf                                                 2/2     Running   <span class="m">0</span>          5m22s
</span></span><span class="line"><span class="cl">kube-prometheus-stack-grafana-bf74d765f-4hzlt               3/3     Running   <span class="m">0</span>          23m
</span></span><span class="line"><span class="cl">kube-prometheus-stack-kube-state-metrics-6b747b7c5f-hjjwd   1/1     Running   <span class="m">0</span>          23m
</span></span><span class="line"><span class="cl">kube-prometheus-stack-operator-59c5d9f8c-q6phj              1/1     Running   <span class="m">0</span>          23m
</span></span><span class="line"><span class="cl">kube-prometheus-stack-prometheus-node-exporter-zhmfp        1/1     Running   <span class="m">0</span>          23m
</span></span><span class="line"><span class="cl">loki-0                                                      2/2     Running   <span class="m">0</span>          32m
</span></span><span class="line"><span class="cl">loki-canary-55ccd5d799-wkc7t                                1/1     Running   <span class="m">0</span>          32m
</span></span><span class="line"><span class="cl">loki-gateway-6df7c96bc9-l92d2                               1/1     Running   <span class="m">0</span>          32m
</span></span><span class="line"><span class="cl">prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   <span class="m">0</span>          21m
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n demo
</span></span><span class="line"><span class="cl">NAME                     READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">nginx-7489fb554f-hffjv   1/1     Running   <span class="m">0</span>          33m
</span></span></code></pre></div><hr>
<p>L&rsquo;essentiel des travaux est disponible sur <a href="https://github.com/Bernedotcom2312/theforge-cd">mon repo GitHub</a></p>
<h2 id="ce-qui-vient-ensuite">Ce qui vient ensuite</h2>
<p>Nous verrons comment exposer notre application à travers la Gateway API dans le prochain article de la série, stay tuned !</p>
<hr>
<p><strong>Sources :</strong></p>
<ul>
<li><a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack Helm Chart</a></li>
<li><a href="https://github.com/grafana/helm-charts/tree/main/charts/loki">Loki Helm Chart</a></li>
<li><a href="https://github.com/grafana/alloy/blob/main/operations/helm/charts/alloy/README.md">Alloy Helm Chart</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TheForge #2 — Installation d&#39;ArgoCD pour le déploiement continu sur Kubernetes</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-cd/</link>
      <pubDate>Thu, 28 May 2026 19:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-cd/</guid>
      <description>Installation d&amp;#39;ArgoCD pour le déploiement continu sur Kubernetes.</description>
      <content:encoded><![CDATA[<h2 id="ce-quon-fait">Ce qu&rsquo;on fait</h2>
<blockquote>
<p>Installation d&rsquo;ArgoCD pour le déploiement continu sur Kubernetes via Terraform et Helm.</p>
</blockquote>
<hr>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Cluster GKE up and running</li>
<li>Connaissance de base de Kubernetes et GitOps</li>
<li>Outils : <code>terraform</code>, <code>gcloud</code>, <code>kubectl</code>, <code>helm</code>&hellip;</li>
</ul>
<hr>
<h2 id="étapes">Étapes</h2>
<h3 id="1-installation-dargocd">1. Installation d&rsquo;ArgoCD</h3>
<p>Pour cette partie j&rsquo;ai choisi d&rsquo;installer ArgoCD via Terraform en utilisant le provider Helm. Cela nous permettra de gérer l&rsquo;installation d&rsquo;ArgoCD de manière déclarative et reproductible.
Argo sera ensuite en charge de déployer les workloads sur notre cluster Kubernetes.</p>
<p>Ajout des providers nécessaires dans notre configuration Terraform :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">provider</span> <span class="s2">&#34;kubernetes&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  host</span>                   <span class="o">=</span> <span class="s2">&#34;https://${google_container_cluster.primary.endpoint}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  token</span>                  <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">google_client_config</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="k">access_token</span>
</span></span><span class="line"><span class="cl"><span class="n">  cluster_ca_certificate</span> <span class="o">=</span> <span class="k">base64decode</span><span class="p">(</span><span class="k">google_container_cluster</span><span class="p">.</span><span class="k">primary</span><span class="p">.</span><span class="k">master_auth</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="k">cluster_ca_certificate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">provider</span> <span class="s2">&#34;helm&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  kubernetes</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">    host</span>                   <span class="o">=</span> <span class="s2">&#34;https://${google_container_cluster.primary.endpoint}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    token</span>                  <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">google_client_config</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="k">access_token</span>
</span></span><span class="line"><span class="cl"><span class="n">    cluster_ca_certificate</span> <span class="o">=</span> <span class="k">base64decode</span><span class="p">(</span><span class="k">google_container_cluster</span><span class="p">.</span><span class="k">primary</span><span class="p">.</span><span class="k">master_auth</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="k">cluster_ca_certificate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>Ne pas oublier <em>terraform init</em> pour installer les providers.
Ensuite, nous allons ajouter une ressource Helm pour installer ArgoCD :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;helm_release&#34; &#34;argocd&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  name</span>             <span class="o">=</span> <span class="s2">&#34;argocd&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  repository</span>       <span class="o">=</span> <span class="s2">&#34;https://argoproj.github.io/argo-helm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  chart</span>            <span class="o">=</span> <span class="s2">&#34;argo-cd&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  version</span>          <span class="o">=</span> <span class="s2">&#34;9.5.15&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  namespace</span>        <span class="o">=</span> <span class="s2">&#34;argocd&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  create_namespace</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">  set</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    {
</span></span><span class="line"><span class="cl"><span class="n">      name</span>  <span class="o">=</span> <span class="s2">&#34;dex.enabled&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      value</span> <span class="o">=</span> <span class="s2">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">    }<span class="p">,</span>
</span></span><span class="line"><span class="cl">    {
</span></span><span class="line"><span class="cl"><span class="n">      name</span>  <span class="o">=</span> <span class="s2">&#34;notifications.enabled&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      value</span> <span class="o">=</span> <span class="s2">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">    }<span class="p">,</span>
</span></span><span class="line"><span class="cl">    {
</span></span><span class="line"><span class="cl"><span class="n">      name</span>  <span class="o">=</span> <span class="s2">&#34;applicationSet.enabled&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      value</span> <span class="o">=</span> <span class="s2">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">    }<span class="p">,</span>
</span></span><span class="line"><span class="cl">    {
</span></span><span class="line"><span class="cl"><span class="n">      name</span>  <span class="o">=</span> <span class="s2">&#34;server.extraArgs[0]&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      value</span> <span class="o">=</span> <span class="s2">&#34;--insecure&#34;</span>
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">  depends_on</span> <span class="o">=</span> <span class="p">[</span><span class="k">google_container_node_pool</span><span class="p">.</span><span class="k">primary_nodes</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p><em>Ici nous installons ArgoCD dans le namespace <code>argocd</code> en désactivant certaines fonctionnalités que nous n&rsquo;utiliserons pas pour le moment (Dex, notifications, ApplicationSet) et en configurant le serveur ArgoCD pour qu&rsquo;il fonctionne en mode non sécurisé (insecure) pour simplifier les choses dans un environnement de développement.</em></p>
<hr>
<h3 id="2-création-de-la-root-application-argocd-app-of-apps-pattern">2. Création de la Root Application ArgoCD (App of Apps Pattern)</h3>
<p>Le pattern &ldquo;App of Apps&rdquo; est une approche recommandée pour organiser les applications dans ArgoCD. Il consiste à créer une application principale (root application) qui référence d&rsquo;autres applications (child applications).</p>
<p>Cela permet de structurer les déploiements de manière hiérarchique et de faciliter la gestion des dépendances entre les applications.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;kubernetes_manifest&#34; &#34;argocd_app&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  manifest</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">    apiVersion</span> <span class="o">=</span> <span class="s2">&#34;argoproj.io/v1alpha1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    kind</span>       <span class="o">=</span> <span class="s2">&#34;Application&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    metadata</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">      name</span>      <span class="o">=</span> <span class="s2">&#34;root-app&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      namespace</span> <span class="o">=</span> <span class="s2">&#34;argocd&#34;</span>
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl"><span class="n">    spec</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">      project</span> <span class="o">=</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      source</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">        repoURL</span>        <span class="o">=</span> <span class="s2">&#34;https://github.com/Bernedotcom2312/theforge-cd.git&#34;</span><span class="c1"> #Changez pour le votre
</span></span></span><span class="line"><span class="cl"><span class="n">        targetRevision</span> <span class="o">=</span> <span class="s2">&#34;main&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">        path</span>           <span class="o">=</span> <span class="s2">&#34;apps&#34;</span>
</span></span><span class="line"><span class="cl">      }
</span></span><span class="line"><span class="cl"><span class="n">      destination</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">        server</span>    <span class="o">=</span> <span class="s2">&#34;https://kubernetes.default.svc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">        namespace</span> <span class="o">=</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">      }
</span></span><span class="line"><span class="cl"><span class="n">      syncPolicy</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">        automated</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">          prune</span>    <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">          selfHeal</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl">        }
</span></span><span class="line"><span class="cl">      }
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">  depends_on</span> <span class="o">=</span> <span class="p">[</span><span class="k">helm_release</span><span class="p">.</span><span class="k">argocd</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p><em>Cette configuration crée une application ArgoCD nommée <code>root-app</code> qui pointe vers un dépôt Git contenant les définitions des applications à déployer.
Le champ <code>syncPolicy</code> est configuré pour permettre le déploiement automatique et de réparer les ressources qui ne sont pas conformes à la configuration (selfHeal).</em></p>
<h3 id="3-application-de-la-configuration-terraform">3. Application de la configuration Terraform</h3>
<p>Maintenant que nous avons ajouté la configuration pour installer ArgoCD et créer la root application, nous pouvons appliquer notre configuration Terraform :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">terraform apply
</span></span></code></pre></div><p>Cette commande va provisionner ArgoCD sur notre cluster GKE et créer la root application qui référencera les applications à déployer depuis notre dépôt Git.</p>
<hr>
<h2 id="résultat">Résultat</h2>
<p>ArgoCD est maintenant installé sur notre cluster GKE et prêt à être utilisé pour le déploiement continu de nos applications.</p>
<p>Vous pouvez vérifier que les pods ArgoCD sont en cours d&rsquo;exécution avec la commande suivante :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n argocd
</span></span><span class="line"><span class="cl">NAME                                                READY   STATUS    RESTARTS      AGE
</span></span><span class="line"><span class="cl">argocd-application-controller-0                     1/1     Running   <span class="m">0</span>             94s
</span></span><span class="line"><span class="cl">argocd-applicationset-controller-6c6b88d4bc-krjc5   1/1     Running   <span class="m">0</span>             95s
</span></span><span class="line"><span class="cl">argocd-dex-server-79fc985b7b-rbz6w                  1/1     Running   <span class="m">2</span> <span class="o">(</span>77s ago<span class="o">)</span>   96s
</span></span><span class="line"><span class="cl">argocd-notifications-controller-6c44f9dbbf-v4777    1/1     Running   <span class="m">0</span>             96s
</span></span><span class="line"><span class="cl">argocd-redis-7979d55d5b-ft6rc                       1/1     Running   <span class="m">0</span>             96s
</span></span><span class="line"><span class="cl">argocd-repo-server-584c965f87-tzvsg                 1/1     Running   <span class="m">0</span>             95s
</span></span><span class="line"><span class="cl">argocd-server-5f7d4885d5-vkccj                      1/1     Running   <span class="m">0</span>             95s
</span></span></code></pre></div><p>Accès au dashboard ArgoCD :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl port-forward svc/argocd-server -n argocd 8080:80
</span></span></code></pre></div><p>Rendez vous sur <code>http://localhost:8080</code>.
Vous devriez avoir un warning de sécurité concernant le mode &ldquo;insecure&rdquo; que nous avons configuré pour le serveur ArgoCD, mais c&rsquo;est acceptable pour un environnement de développement, nous verrons comment sécuriser cela dans une prochaine étape.</p>
<p>Pour se connecter, utilisez les identifiants suivants :</p>
<ul>
<li>Username : admin</li>
<li>Password : le mot de passe est généré automatiquement et stocké dans le secrets <code>argocd-initial-admin-secret</code> dans le namespace <code>argocd</code>. Vous pouvez le récupérer avec la commande suivante :</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl -n argocd get secret argocd-initial-admin-secret -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s2">&#34;{.data.password}&#34;</span> <span class="p">|</span> base64 -d
</span></span></code></pre></div><p>Notre application d&rsquo;exemple (Nginx) devrait également être déployée et visible dans l&rsquo;interface d&rsquo;ArgoCD.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -n demo
</span></span><span class="line"><span class="cl">NAME                    READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">nginx-78b9f9966-bh7vb   1/1     Running   <span class="m">0</span>          110s
</span></span></code></pre></div><p>Un dernier port-forward pour accéder à l&rsquo;application Nginx :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl port-forward svc/nginx -n demo 8081:80
</span></span></code></pre></div><p>Et voilà le résultat en accèdant au port 8081 de votre localhost :</p>
<p><img alt="Welcome page nginx" loading="lazy" src="/welcome_nginx.png"></p>
<h2 id="ce-qui-vient-ensuite">Ce qui vient ensuite</h2>
<p>La prochaine brique de notre plateforme Kubernetes : le monitoring.</p>
<hr>
<p><strong>Sources</strong></p>
<ul>
<li><a href="https://argo-cd.readthedocs.io/en/stable/">ArgoCD Documentation</a></li>
<li><a href="https://registry.terraform.io/providers/hashicorp/helm/latest/docs">Terraform Helm Provider</a></li>
<li><a href="https://blog.stephane-robert.info/docs/pipeline-cicd/argocd/premiere-application/">Blog Stéphane Robert</a></li>
<li><a href="https://github.com/Bernedotcom2312/theforge-infra">Mon Repo Infra</a></li>
<li><a href="https://github.com/Bernedotcom2312/theforge-cd">Mon Repo CD</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TheForge #1 — Création d&#39;un cluster Kubernetes sur GCP avec Terraform</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-cluster/</link>
      <pubDate>Wed, 27 May 2026 13:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-cluster/</guid>
      <description>Dans ce premier article de la série TheForge, nous allons créer un cluster Kubernetes sur Google Cloud Platform (GCP) en utilisant Terraform. Nous verrons les étapes nécessaires pour configurer notre environnement, provisionner le cluster et valider son bon fonctionnement.</description>
      <content:encoded><![CDATA[<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Un compte GCP avec les permissions nécessaires pour créer des ressources.
<ul>
<li>API Kubernetes Engine activée</li>
<li>Billing account configuré</li>
</ul>
</li>
<li>Terraform installé sur votre machine</li>
<li>Quelques connaissances sur Git</li>
</ul>
<hr>
<h2 id="étapes">Étapes</h2>
<h3 id="1-creation-du-repository-git">1. Creation du repository Git</h3>
<p>Ici pas question de réinventer la roue, j&rsquo;utilise un template fournit par Hashicorp(Terraform) pour le bootstrap de mon cluster.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/hashicorp-education/learn-terraform-provision-gke-cluster
</span></span><span class="line"><span class="cl">mv learn-terraform-provision-gke-cluster theforge-infra
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> theforge-infra
</span></span><span class="line"><span class="cl">git remote set-url origin git@github.com:mon-repo.git      
</span></span></code></pre></div><p><em>Nous ferons le tour d&rsquo;horizon des ressources juste après.</em></p>
<hr>
<h3 id="2-configuration-gcloud">2. Configuration GCLOUD</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install --cask google-cloud-sdk
</span></span><span class="line"><span class="cl">gcloud init
</span></span><span class="line"><span class="cl">gcloud auth application-default login
</span></span></code></pre></div><hr>
<h3 id="3-architecture-du-repo">3. Architecture du repo</h3>
<table>
  <thead>
      <tr>
          <th>Fichier</th>
          <th>Rôle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>vpc.tf</code></td>
          <td>Déclare le <strong>VPC</strong> et un <strong>subnet</strong> pour isoler le cluster, ainsi que les variables <code>project_id</code> / <code>region</code> et le provider Google.</td>
      </tr>
      <tr>
          <td><code>gke.tf</code></td>
          <td>Crée le <strong>cluster GKE</strong> (version 1.27.x, default node pool supprimé) et un <strong>node pool séparé</strong> de 2 nœuds <code>n1-standard-1</code>.</td>
      </tr>
      <tr>
          <td><code>versions.tf</code></td>
          <td>Fixe le provider Google à la version <code>7.33.0</code> et Terraform <code>&gt;= 0.14</code>.</td>
      </tr>
      <tr>
          <td><code>outputs.tf</code></td>
          <td>Expose la région, le project ID, le nom et l&rsquo;endpoint du cluster.</td>
      </tr>
      <tr>
          <td><code>terraform.tfvars</code></td>
          <td>Variables d&rsquo;entrée (<code>project_id</code> à remplacer, région <code>us-central1</code>).</td>
      </tr>
      <tr>
          <td><code>kubernetes-dashboard-admin.rbac.yaml</code></td>
          <td>Manifest RBAC Kubernetes pour un accès admin au dashboard (à appliquer post-déploiement).</td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="4-fine-tuning-de-la-configuration-terraform">4. Fine tuning de la configuration Terraform</h3>
<h4 id="ajout-du-project-id-et-de-la-région">Ajout du Project ID et de la région</h4>
<p>Quelques modifications à apporter à la configuration de base, notamment le <code>project_id</code> et la <code>region</code> dans le fichier <code>terraform.tfvars</code> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="n">project_id</span> <span class="o">=</span> <span class="s2">&#34;MON_PROJECT_ID&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">region</span>     <span class="o">=</span> <span class="s2">&#34;europe-west1&#34;</span>
</span></span></code></pre></div><p>Puis on valide que Terraform est correctement configuré et peut communiquer avec GCP :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">terraform init
</span></span></code></pre></div><blockquote>
<p>Terraform has been successfully initialized!</p>
</blockquote>
<h4 id="mise-à-jour-de-la-version-de-terraform-et-des-providers">Mise à jour de la version de Terraform et des providers</h4>
<p>Le template utilise une version de Terraform et du provider Google qui sont un peu anciennes, il est recommandé de les mettre à jour pour bénéficier des dernières fonctionnalités et corrections de bugs.
Dans <code>versions.tf</code>, on peut mettre à jour la version de Terraform et du provider Google :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">terraform</span> {
</span></span><span class="line"><span class="cl"><span class="n">  required_version</span> <span class="o">=</span><span class="n"> &#34;&gt;</span><span class="o">=</span> <span class="m">1</span><span class="p">.</span><span class="m">15</span><span class="err">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">required_providers</span> {
</span></span><span class="line"><span class="cl"><span class="n">    google</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">      source</span>  <span class="o">=</span> <span class="s2">&#34;hashicorp/google&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      version</span> <span class="o">=</span> <span class="s2">&#34;~&gt; 7.33.0&#34;</span>
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>Sources :</p>
<ul>
<li><a href="https://registry.terraform.io/providers/hashicorp/google/latest">Version provider Google</a></li>
<li><a href="https://endoflife.date/terraform">Version Terraform</a></li>
</ul>
<h4 id="mise-à-jour-de-la-version-de-gke">Mise à jour de la version de GKE</h4>
<p>Par défaut, le template utilise aussi une version de GKE qui n&rsquo;est plus supportée (1.27.x).
Il faut donc mettre à jour la version du cluster dans <code>gke.tf</code> pour une version plus récente, par exemple <code>1.36.x</code> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_container_cluster&#34; &#34;primary&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  name</span>     <span class="o">=</span> <span class="s2">&#34;theforge-cluster&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  location</span> <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">region</span><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">  # Autres configurations...
</span></span></span><span class="line"><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">  # Mise à jour de la version du cluster
</span></span></span><span class="line"><span class="cl"><span class="n">  min_master_version</span> <span class="o">=</span> <span class="s2">&#34;1.36&#34;</span>
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>Ensuite, on peut relancer <code>terraform plan</code> pour vérifier que la configuration est valide avec la nouvelle version.</p>
<p>Sources :</p>
<ul>
<li><a href="https://docs.cloud.google.com/kubernetes-engine/docs/release-schedule?hl=fr">Versions GKE disponibles</a></li>
</ul>
<h4 id="séparation-des-ressources-dans-des-fichiers">Séparation des ressources dans des fichiers</h4>
<p>Pour une meilleure organisation, j&rsquo;ai choisi de remanier quelque peu la structure du repo en séparant les ressources dans des fichiers dédiés (<code>variables.tf</code>, <code>providers.tf</code>).
J&rsquo;envisage en effet de réutiliser ce repository à l&rsquo;avenir pour d&rsquo;autres projets, à ce titre je souhaite le rendre plus modulaire et facilement maintenable.</p>
<hr>
<h2 id="résultat">Résultat</h2>
<p>Une fois satisfait de votre configuration il ne reste qu&rsquo;à lancer la commande <code>terraform apply</code> pour provisionner le cluster sur GCP.
Après quelques minutes d&rsquo;attente, le cluster devrait être opérationnel et vous devriez voir les outputs suivants :</p>
<h3 id="connexion-au-cluster">Connexion au cluster</h3>
<p>Google Cloud SDK (gcloud) est nécessaire pour récupérer les credentials du cluster et pouvoir interagir avec lui via kubectl.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud container clusters get-credentials <span class="s2">&#34;moncluster&#34;</span> --region europe-west1 --project <span class="s2">&#34;monprojet&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; kubeconfig entry generated for moncluster.</span>
</span></span></code></pre></div><h3 id="verification-du-status-des-pods">Verification du status des pods</h3>
<p>Une fois le cluster déployé vous devriez voir les pods du cluster en cours d&rsquo;exécution :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods -A
</span></span><span class="line"><span class="cl">NAMESPACE         NAME                                                             READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">gke-managed-cim   kube-state-metrics-0                                             2/2     Running   <span class="m">0</span>          35m
</span></span><span class="line"><span class="cl">gmp-system        collector-jc4t8                                                  2/2     Running   <span class="m">0</span>          30m
</span></span><span class="line"><span class="cl">gmp-system        collector-jdjzw                                                  2/2     Running   <span class="m">0</span>          30m
</span></span></code></pre></div><h3 id="repo-disponible-sur-github">Repo disponible sur GitHub</h3>
<p>L&rsquo;ensemble de la configuration Terraform utilisée pour ce projet est disponible sur GitHub : <a href="https://github.com/Bernedotcom2312/theforge-infra">theforge-infra</a>.</p>
<hr>
<h2 id="erreurs-rencontrées">Erreurs rencontrées</h2>
<h3 id="pas-de-billing-account-configuré">Pas de billing account configuré</h3>
<blockquote>
<p>ERROR: (gcloud.services.enable) FAILED_PRECONDITION: Billing account for project &lsquo;X&rsquo; is not found</p>
</blockquote>
<p>Renseignez un billing account valide pour votre projet et c&rsquo;est réglé.</p>
<h3 id="api-kubernetes-désactivée">API Kubernetes désactivée</h3>
<blockquote>
<p>Error: Error retrieving available container cluster versions: googleapi: Error 403: Kubernetes Engine API has not been used in project deft-accord-496812-k9 before or it is disabled.</p>
</blockquote>
<p>Elle était assez explicite : l&rsquo;API Kubernetes Engine n&rsquo;était pas activée sur le projet GCP.
Je l&rsquo;ai activée via la console GCP, puis relancé <code>terraform plan</code> pour valider que tout est bon.
Vous pouvez aussi l&rsquo;activer avec la CLI :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud services <span class="nb">enable</span> container.googleapis.com
</span></span></code></pre></div><h3 id="problème-de-provisioning-du-cluster">Problème de provisioning du cluster</h3>
<blockquote>
<p>Google Compute Engine: Not all instances running in IGM after 35m11.660099086s. Expected 1, running 0, transitioning 1. Current errors: [GCE_STOCKOUT]: Instance &lsquo;gke-deft-accord-496812-k-default-pool-19ca1c15-p6ls&rsquo; creation failed: The zone &lsquo;projects/deft-accord-496812-k9/zones/europe-west1-c&rsquo; does not have enough resources available to fulfill the request. Try a different zone, or try again later.</p>
</blockquote>
<p>Résolution : changer la zone de déploiement du cluster dans <code>gke.tfs</code> pour une zone moins sollicitée, par exemple <code>europe-west1-b</code> ou <code>europe-west1-d</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_container_cluster&#34; &#34;primary&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  name</span>     <span class="o">=</span> <span class="s2">&#34;${var.project_id}-gke&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  location</span> <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">region</span>
</span></span><span class="line"><span class="cl"><span class="n">  node_locations</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;europe-west1-b&#34;, &#34;europe-west1-d&#34;</span><span class="p">]</span><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">  # Autres configurations...
</span></span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><h3 id="plugin-gke-introuvable">Plugin GKE introuvable</h3>
<blockquote>
<p>couldn&rsquo;t get current server API group list: Get &ldquo;https://35.233.30.210/api?timeout=32s&rdquo;: getting credentials: exec: executable gke-gcloud-auth-plugin.exe not found</p>
</blockquote>
<p>Solution : installer le plugin d&rsquo;authentification GKE pour kubectl, qui est nécessaire pour se connecter au cluster GKE.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud components install gke-gcloud-auth-plugin
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>The Forge Project</title>
      <link>https://valerian-pyckaert.dev/posts/the-forge-project/</link>
      <pubDate>Fri, 22 May 2026 08:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/the-forge-project/</guid>
      <description>Projet fil rouge : construire une plateforme Kubernetes complète sur GCP, from scratch, avec les outils indispensables en 2026. Chaque article documente une étape, les obstacles rencontrés et comment je les ai résolus.</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Stack</strong> : GCP · GKE · Terraform · Helm · ArgoCD · SOPS · Kube-Prom-Stack · Nginx Gateway Fabric · cert-manager</p>
</blockquote>
<hr>
<h2 id="pourquoi-ce-projet-">Pourquoi ce projet ?</h2>
<p>Je me lance en freelance DevOps / Platform Engineer / Cloud Engineer. Pour montrer ce que je sais faire (et documenter ce que j&rsquo;apprends en chemin), je démarre un projet fil rouge public : construire une plateforme Kubernetes complète sur GCP, from scratch, avec les outils que j&rsquo;estime indispensables en 2026.</p>
<p>Chaque article de la série documente <strong>une étape</strong>, les <strong>obstacles rencontrés</strong> et comment je les ai résolus. Format court, orienté pratique.</p>
<p>Je vais tenter de garder une approche pragmatique orientée &ldquo;best practices&rdquo; (mais pas dogmatique) pour que ce soit utile à d&rsquo;autres personnes qui veulent se lancer dans la même aventure.</p>
<hr>
<h2 id="ce-quon-va-construire">Ce qu&rsquo;on va construire</h2>
<p>Un cluster GKE avec la stack suivante, déployée en GitOps :</p>
<table>
  <thead>
      <tr>
          <th>Composant</th>
          <th>Rôle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Terraform</strong></td>
          <td>Provisionning du cluster et bootstrap</td>
      </tr>
      <tr>
          <td><strong>Helm + ArgoCD</strong></td>
          <td>GitOps, tout passe par là après le bootstrap</td>
      </tr>
      <tr>
          <td><strong>SOPS</strong></td>
          <td>Gestion des secrets chiffrés dans Git</td>
      </tr>
      <tr>
          <td><strong>kube-prometheus-stack</strong></td>
          <td>Monitoring &amp; alerting (Prometheus, Grafana, Alertmanager)</td>
      </tr>
      <tr>
          <td><strong>Nginx Gateway Fabric</strong></td>
          <td>Ingress via la Gateway API</td>
      </tr>
      <tr>
          <td><strong>cert-manager + Let&rsquo;s Encrypt</strong></td>
          <td>TLS automatique</td>
      </tr>
  </tbody>
</table>
<p>Je me focalise sur ce qui me semble essentiel pour adresser la majorité des besoins.
A termes j&rsquo;envisage d&rsquo;ajouter d&rsquo;autres composants destinés à des usages plus complexes : KEDA, Karpenter, Dapr, &hellip;</p>
<hr>
<h2 id="les-étapes-prévues">Les étapes prévues</h2>
<ol>
<li><strong>Création du cluster GKE via Terraform</strong></li>
<li><strong>Bootstrap Helm &amp; ArgoCD via Terraform</strong></li>
<li><strong>Déploiement de kube-prometheus-stack</strong> via ArgoCD</li>
<li><strong>Déploiement de Nginx Gateway Fabric / Gateway API</strong> via ArgoCD</li>
<li><strong>Déploiement de cert-manager + Let&rsquo;s Encrypt</strong> via ArgoCD</li>
<li><strong>Mise en place de SOPS</strong> pour la gestion des secrets</li>
</ol>
<p><em>J&rsquo;ajouterai probablement d&rsquo;autres étapes au fur et à mesure</em>* (ex: gestion des node pools, autoscaling, etc.) selon les besoins et les obstacles rencontrés*</p>
<hr>
<h2 id="principes-de-base">Principes de base</h2>
<ul>
<li><strong>Tout dans Git</strong> — aucune modification manuelle sur le cluster après le bootstrap</li>
<li><strong>Least privilege</strong> — les SA GCP auront le minimum requis</li>
<li><strong>Les secrets ne sont jamais en clair</strong> dans le repo</li>
<li><strong>KISS</strong> — Keep It Simple Stupid. Pas de sur-optimisation, pas de microservices inutiles, pas de complexité superflue</li>
</ul>
<hr>
<h2 id="prochaine-étape">Prochaine étape</h2>
<p>→ <a href="#">Création du cluster GKE avec Terraform</a> <em>(à venir)</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Kubernetes v1.36</title>
      <link>https://valerian-pyckaert.dev/posts/kubernetes-haru/</link>
      <pubDate>Tue, 19 May 2026 08:00:00 +0200</pubDate>
      <guid>https://valerian-pyckaert.dev/posts/kubernetes-haru/</guid>
      <description>Kubernetes v1.36 : les nouveautés à connaître</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Date de sortie :</strong> 22 avril 2026<br>
<strong>Nom de code :</strong> ハル (Haru — « Printemps » en japonais)</p>
</blockquote>
<hr>
<p>Kubernetes v1.36 est sorti fin avril 2026 sous le nom de code <em>Haru</em> (printemps en japonais), et c&rsquo;est une release dense.</p>
<p>Voici mon tour d&rsquo;horizon des changements les plus significatifs.</p>
<hr>
<h2 id="-sécurité--des-fondations-qui-arrivent-enfin-à-maturité">🔐 Sécurité : des fondations qui arrivent enfin à maturité</h2>
<h3 id="user-namespaces---stable">User Namespaces : 🟢 Stable</h3>
<p><strong>Le problème :</strong></p>
<p>Par défaut, un processus qui tourne en tant que <code>root</code> (UID 0) dans un conteneur est aussi <code>root</code> sur le nœud hôte. En cas de fuite de conteneur (<em>container escape</em>), l&rsquo;attaquant dispose immédiatement de privilèges root sur la machine, ce qui compromet l&rsquo;ensemble des workloads du nœud.</p>
<p><strong>La solution :</strong></p>
<p>Les <strong>User Namespaces</strong> (isolation des UID/GID entre le pod et le nœud) passent en <strong>GA</strong> (Stable). Kubernetes remape automatiquement les UID/GID du conteneur vers des UID/GID non privilégiés sur le nœud hôte. Un processus <code>root</code> dans le conteneur (UID 0) est vu comme un utilisateur sans privilège sur le nœud (ex : UID 65534).</p>
<p><strong>Exemple :</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app-with-userns</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">secure-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hostUsers</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">  </span><span class="c"># Active les User Namespaces</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">myregistry/my-app:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">securityContext</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">runAsUser</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">         </span><span class="c"># root dans le conteneur...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">allowPrivilegeEscalation</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">capabilities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">drop</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;ALL&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;256Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Avec <code>hostUsers: false</code>, le UID 0 dans le conteneur est remappé vers un UID non privilégié sur le nœud (ex : 65536). Même en cas d&rsquo;évasion, l&rsquo;attaquant n&rsquo;a aucun privilège sur l&rsquo;hôte.</p>
</blockquote>
<h3 id="fine-grained-kubelet-api-authorization---stable">Fine-Grained Kubelet API Authorization : 🟢 Stable</h3>
<p><strong>Le problème :</strong></p>
<p>Kubelet est l&rsquo;agent qui tourne sur chaque machine de votre cluster et qui s&rsquo;assure que vos applications fonctionnent. Jusqu&rsquo;ici, les permissions pour accéder à cet agent étaient soit tout ou rien — un peu comme une serrure unique pour toute une maison.</p>
<p><strong>La solution :</strong></p>
<p>Il est maintenant possible de définir des permissions très précises : cet outil de monitoring peut seulement lire les logs, cet autre outil peut redémarrer des pods mais pas lire les données, etc.</p>
<p><strong>Pourquoi c&rsquo;est important :</strong></p>
<p>Si un outil tiers est compromis, il ne peut pas profiter de ses accès pour faire n&rsquo;importe quoi sur vos machines. C&rsquo;est le principe du moindre privilège appliqué là où ça compte vraiment.</p>
<p><strong>Exemple :</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Exemple de Role pour accéder uniquement aux logs des pods</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pod-log-reader</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;pods/log&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></div><h3 id="admission-policies-immuables---alpha">Admission Policies immuables : 🔴 Alpha</h3>
<p><strong>Le problème :</strong></p>
<p>Un administrateur mal intentionné (ou simplement qui fait une erreur) peut supprimer des règles de sécurité critiques sur un cluster.</p>
<p><strong>La solution :</strong></p>
<p>Certaines politiques de sécurité peuvent maintenant être déclarées comme permanentes dans des fichiers de configuration. Personne ne peut les supprimer via l&rsquo;API, il faudrait accès direct au cluster pour les retirer.</p>
<p><strong>Exemple :</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">policy.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PodSecurityPolicy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="kc">no</span>-<span class="l">privileged</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">privileged</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">immutable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><h3 id="declarative-validation---stable">Declarative Validation : 🟢 Stable</h3>
<p><strong>Le problème :</strong></p>
<p>Quand on déploie une application sur Kubernetes, on peut définir des règles pour valider les configurations (ex : &ldquo;tous les pods doivent avoir un label env&rdquo;). Historiquement, ces validations passaient par des services externes appelés webhooks, qui rajoutaient de la complexité et pouvaient tomber en panne.</p>
<p><strong>La solution :</strong></p>
<p>Ces règles de validation peuvent maintenant être écrites directement dans la configuration Kubernetes, sans service intermédiaire. Kubernetes les vérifie lui-même, instantanément.</p>
<p><strong>Pourquoi c&rsquo;est important :</strong></p>
<p>Moins de webhooks à maintenir, moins de latence, et une validation plus robuste au niveau de l&rsquo;API Server.</p>
<hr>
<h2 id="-gestion-des-ressources--vers-plus-de-finesse">⚙️ Gestion des ressources : vers plus de finesse</h2>
<h3 id="in-place-vertical-scaling-au-niveau-pod---beta">In-Place Vertical Scaling au niveau Pod : 🟡 Beta</h3>
<p><strong>Le problème :</strong></p>
<p>Imaginez une application qui tourne en production et qui commence à manquer de mémoire.
Jusqu&rsquo;ici, la seule solution était de l&rsquo;arrêter, changer sa configuration, et la redémarrer soit manuellement, soit via le Vertical Pod Autoscaler (VPA).</p>
<p><strong>La solution :</strong></p>
<p>Il est maintenant possible d&rsquo;augmenter (ou réduire) la mémoire et le CPU alloués à une application en direct, sans interruption. Kubernetes ajuste les ressources à chaud.</p>
<p><em>J&rsquo;aime énormément cette feature pour les applications qui ont des besoins variables ou qui sont difficiles à dimensionner à l&rsquo;avance. C&rsquo;est un vrai gain de flexibilité et cette feature sera un atout pour réduire encore un peu plus notre facture cloud.</em></p>
<h3 id="tiered-memory-protection-with-memory-qos---beta">Tiered Memory Protection with Memory QoS : 🟡 Beta</h3>
<p>Kubernetes v1.36 affine le modèle d&rsquo;allocation mémoire : les pods critiques peuvent désormais bénéficier de garanties mémoire plus fortes, tandis que les pods best-effort sont soumis à une pression mémoire plus agressive. Une feature qui change la vie en production sur des nœuds fortement chargés.</p>
<h3 id="mutable-pod-resources-for-suspended-jobs---beta">Mutable Pod Resources for Suspended Jobs : 🟡 Beta</h3>
<p><strong>Le problème :</strong></p>
<p>Kubernetes permet de mettre en pause les ressources de type Job mais si on veut modifier les ressources allouées avant de reprendre le job, ce n&rsquo;était pas possible.</p>
<p><strong>La solution :</strong> Pendant qu&rsquo;un job est en pause, on peut maintenant modifier ses ressources (plus de mémoire, moins de CPU…) avant de le relancer.</p>
<hr>
<h2 id="-observabilité-et-scalabilité-de-lapi">📊 Observabilité et scalabilité de l&rsquo;API</h2>
<h3 id="psi-metrics---stable">PSI Metrics : 🟢 Stable</h3>
<p>Les métriques <strong>PSI (Pressure Stall Information)</strong> qui mesurent la pression réelle subie par les workloads sur le CPU, la mémoire et l&rsquo;I/O passent en <strong>GA</strong>. Ces métriques Linux sont bien plus précises que les métriques CPU classiques pour détecter une saturation réelle. Si vous utilisez Prometheus, commencez à les intégrer dès maintenant.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-promql" data-lang="promql"><span class="line"><span class="cl"><span class="c1"># Exemple : pression mémoire sur les nœuds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nv">container_memory_psi_full_total</span><span class="w">
</span></span></span></code></pre></div><p>Cf. <a href="https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/">PSI Metrics Documentation</a> pour plus de détails.</p>
<hr>
<h2 id="-stockage">💾 Stockage</h2>
<h3 id="volume-group-snapshots---stable">Volume Group Snapshots : 🟢 Stable</h3>
<p>Les <strong>snapshots groupés de volumes</strong> (capturer atomiquement plusieurs PVCs en une seule opération) atteignent la <strong>GA</strong>. Indispensable pour les bases de données distribuées où la cohérence des snapshots entre plusieurs volumes est critique.</p>
<hr>
<h2 id="-réseau--gateway-api-v15">🌐 Réseau : Gateway API v1.5</h2>
<p>En parallèle de la release Kubernetes, <strong>Gateway API v1.5</strong> est sorti avec plusieurs features passant en stable. Le projet continue de s&rsquo;imposer comme le successeur d&rsquo;Ingress, et l&rsquo;outil <a href="https://kubernetes.io/blog/2026/03/20/ingress2gateway-1-0-release/">Ingress2Gateway 1.0</a> facilite désormais la migration automatisée de vos ressources Ingress existantes.</p>
<p><em>Je prévois de faire un article dédié sur Gateway API dans les prochaines semaines, c&rsquo;est une évolution majeure pour la gestion du trafic dans Kubernetes et il y a beaucoup à dire sur les nouvelles possibilités offertes par cette API.</em></p>
<hr>
<h2 id="-dépréciations-et-suppressions-à-surveiller">🗑️ Dépréciations et suppressions à surveiller</h2>
<h3 id="service-externalips">Service ExternalIPs</h3>
<p>Dépréciés, suppression imminente en v1.37. Si vous utilisez des ExternalIPs pour exposer vos services, il est temps de migrer vers des solutions plus modernes comme les LoadBalancers ou la Gateway API.</p>
<p><strong>Comment vérifier si vous êtes concernés ?</strong></p>
<p>Rien de plus simple avec <a href="https://jqlang.org/">JQ</a> et kubectl :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get svc --all-namespaces -o json <span class="p">|</span> jq <span class="s1">&#39;.items[] | select(.spec.externalIPs != null) | {namespace: .metadata.namespace, name: .metadata.name, externalIPs: .spec.externalIPs}&#39;</span>
</span></span></code></pre></div><hr>
<h2 id="-ce-que-ça-change-pour-vos-clusters">🔄 Ce que ça change pour vos clusters</h2>
<table>
  <thead>
      <tr>
          <th>Domaine</th>
          <th>Nouveauté</th>
          <th>Statut</th>
          <th>Impact</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Sécurité</td>
          <td>User Namespaces</td>
          <td><strong>GA</strong></td>
          <td>Activer en prod dès maintenant</td>
      </tr>
      <tr>
          <td>Sécurité</td>
          <td>Kubelet API Authorization</td>
          <td><strong>GA</strong></td>
          <td>Affiner les RBAC Kubelet</td>
      </tr>
      <tr>
          <td>Observabilité</td>
          <td>PSI Metrics</td>
          <td><strong>GA</strong></td>
          <td>Intégrer dans vos dashboards</td>
      </tr>
      <tr>
          <td>Stockage</td>
          <td>Volume Group Snapshots</td>
          <td><strong>GA</strong></td>
          <td>Utiliser pour les BDD distribuées</td>
      </tr>
      <tr>
          <td>Validation</td>
          <td>Declarative Validation (CEL)</td>
          <td><strong>GA</strong></td>
          <td>Réduire vos admission webhooks</td>
      </tr>
      <tr>
          <td>Ressources</td>
          <td>In-Place Pod Resize (pod-level)</td>
          <td>Beta</td>
          <td>Tester sur staging</td>
      </tr>
      <tr>
          <td>Ressources</td>
          <td>Memory QoS étagé</td>
          <td>Beta</td>
          <td>Utile sur nœuds saturés</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Kubernetes v1.36 <em>Haru</em> est une release accès sur une gestion plus fine des ressources et sur la sécurité. Plusieurs features clés passent en GA(Stable) et peuvent désormais être utilisées en production.</p>
<p>Le gros point d&rsquo;attention concerne le décommissionnement à venir des ExternalIPs, qui peut impacter les clusters qui les utilisent pour exposer des services.</p>
<hr>
<p><em>Sources : <a href="https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/">Kubernetes v1.36 Release Notes</a> · <a href="https://kubernetes.io/blog/">Kubernetes Blog</a>, <a href="https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/">PSI Metrics Documentation</a>, <a href="https://facebookmicrosites.github.io/psi/docs/overview">PSI Overview</a></em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
