Cet article vient en complément de notre vidéo youtube juste à côté. Si vous l’avez vu, vous pouvez directement partir au cœur de l’article

Pour les autres, vous pouvez soit regarder la vidéo (c’est quand même bien une vidéo), soit continuer tranquillement votre lecture.


Une SPA pour un outil DevOps ? Pour quoi faire ?

Quand on est le « DevOps guy » la plupart du temps, on utilise et intègre de produits logiciels open source ou non. Ces produits logiciels qui vont recouvrir les nombreux aspects inhérents au développement, au développement, au déploiement et à la maintenance de plateformes logicielles.

Mais, il peut arriver parfois qu’on ne puisse pas utiliser le logiciel tiers que l’on souhaite. Et cela parce que le contexte actuel ne permet pas de le faire en toute sécurité ou facilement, ou tout simplement que l’outil qu’on aurait besoin n’existe tout simplement pas. Au vu de la multitude d’entreprises différentes, ce n’est pas rare du tout de tomber au moins une fois dans ce cas.

Dans ce cas pas le choix, il faut mettre la main à la patte et mettre à profit cette fois-ci ses compétences de développeur et créer les fameux outils maisons.

Dans cette catégorie d’outil maison, on peut y mettre les outils sous forme d’API HTTP. C’est pratique, car actionnable facilement sans rien avoir d’autres à installer qu’un client http. Et en plus, il existe des clients http comme swagger ou postman qui permette d’avoir une interface ce qui rend plus accessible l’API.

Le problème, c’est que même avec ce genre de clients, on n’est pas dispensé de gérer les requêtes et les réponses dans leur format de communication, la plupart du temps du JSON. Déjà pour quelqu’un qui a l’habitude, c’est fastidieux alors si une personne non technique doit y toucher, c’est pire.

Par contre, ce que tout le monde, c’est faire, c’est interagir avec une page web.

Donc faisons une page web !

Attend comment on fait ça ? Quand tu n’y connais pas trop en développement web juste la base HTML, CSS, JavaScript et que tu plonges dans ce qui se fait maintenant, on déchante vite. L’évolution constante de la technologie web peut être déconcertante pour ceux qui ne suivent pas régulièrement les dernières tendances.

Désormais, il ne s’agit plus seulement de créer une structure avec HTML, styliser avec CSS, et ajouter des interactions avec JavaScript… ou peut-être pas ?!

Déjà, c’est pratique une API, car ça fait page web. Suffit juste d’envoyer du HTML au lieu du JSON. Ensuite pour customiser ce HTML, on utilise le templating. Chose à laquelle on est habitué en tant que « devops guy » lorsque l’on fait des charts helm ou des playbook ansible.

La seule chose qui nous manque, c’est la possibilité d’y intégrer une expérience utilisateur moderne. Le Graal de l’expérience utilisateur moderne, on fait souvent référence à la « Single Page Application ». Mais créer ce genre d’expérience sans framework moderne, c’est « overkill » pour un pauvre outil maison

Bien évidemment, y’a une solution !

Une API en Golang pour gérer des déploiements « canary » sur Kubernetes

Rentrons dans le vif du sujet, on va ajouter une interface Web à une API faite en Golang.

Cette API permet de créer et de gérer des canary. Un canary consiste en un déploiement d’une nouvelle version d’une application à côté de la version de production, ce qui permet de tester cette nouvelle version dans des conditions réelles.

Cette stratégie à son utilité, mais doit être utilisé correctement. Je ne vais pas discuter ici des inconvénients et des limites, je vous invite à aller voir la vidéo ci-contre pour en savoir plus.

Cette API maison a été créer spécifiquement pour cet article (et la vidéo qui va avec). Je l’appelle « API DevOps » car elle est intégrée à Kubernetes. Pour bien comprendre la suite, il faut donc avoir quelques notions sur Kubernetes notamment sur le fonctionnement des label, service et deployment.

D’abord voyons un peu ce que peut faire cette API. Les fonctionnalités s’articulent ici autour de 4 points de terminaisons :

  • GET /app
  • GET /app/<name>
  • POST /app/<name>/create_canary {"tag": "<tag>", "replicas": <replicas>}
  • GET /app/<name>/set_canary?enabled=true

Le premier endpoint permet tout simplement de récupérer les applications que l’on peut gérer avec cette outil, en analysant tous les deployment Kubernetes qui ont le label app=<name> et l’annotation devops-tool-htmx: "true".

Voilà le genre de réponse que l’on peut récupérer

[
  "nginx"
]

Le deuxième renvoie l’état d’une application. Cela comprend tous les déploiements, original et canary, que l’on identifie par un label track différent de main. S’ajoute à ça un booléen qui nous dira si les déploiements canary sont « activés ». C’est-à-dire que le service kubernetes associé à l’application envoie du trafic au pod des déploiements canary (ou pas)

La réponse est en JSON dont voici un exemple:

{
  "canaryEnabled": false,
  "deployments": [
    {
      "name": "nginx",
      "image": "nginx:1.25.0-alpine",
      "track": "main",
      "replicas": 3,
      "availableReplicas": 3
    },
    {
      "name": "nginx-canary-nostalgic-hodgkin",
      "image": "nginx:1.25.1-alpine",
      "track": "canary",
      "replicas": 1,
      "availableReplicas": 1
    }
  ],
  "endpoints": [
    {
      "targetPod": "nginx-796c45cc86-pnqcv",
      "ip": "10.244.0.2"
    },
    {
      "targetPod": "nginx-796c45cc86-lp7hb",
      "ip": "10.244.0.3"
    },
    {
      "targetPod": "nginx-796c45cc86-ndclv",
      "ip": "10.244.0.4"
    }
  ]
}

Les deux autres points de terminaison permettent d’un côté de créer un déploiement canary, copie du déploiement principal, en changeant la version et le nombre de replicas et de l’autre d’activer les déploiements canary. Ces deux points de terminaison renvoient le même JSON ci-dessus à ceci près qu’il contiendra la modification en question.

Maintenant, c’est parti pour faire du front (lol)

HTMX is the way

On veut ici ajouter une web interface en mode Single Page Application. On veut vraiment être le moins disruptif possible sur notre API, les fonctionnalités doivent être les mêmes. Et surtout, il faut s’assurer de ne pas avoir de régression sur la partie API.

Cette « single page » sera accessible sur le endpoint /. Et c’est à partir de là que l’on va pouvoir appeler les autres endpoints en utilisant les fonctionnalités de HTMX.

HTMX est une librairie frontend qui permet d’interagir avec le serveur en échangeant du HTML plutôt que du JSON. On peut avec HTMX créer une navigation moderne sans avoir à écrire une seule ligne de javascript uniquement des attributs HTML spécifique.

<body>
    <main class="container">
        <div hx-trigger="load" hx-get="/app">
        </div>
        <div id="app">
        </div>
    </main>
</body>

Deux attributs HTMX apparaissent, hx-trigger et hx-get. Le premier sert à définir quand déclencher un appel http, ici quand la page est chargé. L’autre attribut indique la méthode et le path de l’appel déclenché.

Si vous avez suivi, on appelle ici le premier endpoint de mon API, or cet endpoint normalement me renvoie un JSON. En l’état, gérer une réponse en JSON ne peut pas se faire sans rajouter du code client. Dans ce cas, demandons tous simplement au serveur de renvoyer du HTML. Pour se faire voila ce qu’on peut ajouter à notre handler :

htmx, _ := strconv.ParseBool(c.Get("HX-Request", "false"))
if htmx {
	return c.Render("list", fiber.Map{
		"Apps": apps,
	})
}

Quand le header HX-Request est présent et à true, on va plutôt renvoyer du html rendu grâce au moteur de templating de go. Voici par exemple le template dans le cas présent :

<select hx-get="/app" hx-target="#app" hx-swap="outerHTML" autocomplete="off">
    <option value="" disabled selected>Choose an app</option>
    {{range $a := .Apps}}
    <option value="{{ $a }}">{{ $a }}</option>
    {{end}}
</select>

Ce select contient une liste d’options correspondant à la liste que j’aurais reçu à partir du JSON. HTMX intégrera ce bout de html directement dans ma page sans effectuer de rechargement. Et à partir de là, je peux continuer ma navigation, car ce bout de html contient aussi des attributs HTMX.

Le hx-get va trigger un appel http quand je vais changer la valeur du select, un appel sur /app?name=<value> (value étant la valeur courante du select) que je vais rediriger sur /app/<value> qui est le deuxième endpoint que j’ai décris plus tôt.

Et de la même façon, on renverra du HTML rendu à partir d’un template et remplit des mêmes valeurs que si on était en JSON. Ce bout d’HTML est intégré à la page web. D’ailleurs, les deux attributs hx-target et hx-swap permettent de contrôler où et comment on va intégrer ce qui a été renvoyer à notre page.

Le bouton "Enable Canary » et le form en bas de page permettent de continuer à interagir avec le reste des endpoints que j’ai décris

HTMX, Hypermedia, REST

Avec HTMX, on envoie du HTML par opposition à l’envoi de JSON. De ce fait, on est capable d’envoyer au client non seulement des données, mais aussi toutes autres actions qui sont possibles. Ce qu’on a envoyé est un Hypermedia, un document qui contient des liens vers d’autres documents. En appelant ce genre d’endpoint, on est en capacité de découvrir des interactions supplémentaires sans connaissance.

Et par définition, c’est ce qui constitue une vraie API REST, la réponse HTML se décrit elle-même.

Si je reprends mon exemple d’API, quand dans ma réponse JSON j’ai "canaryEnabled": false. D’accord j’ai l’information sur l’état de mon application. Mais je n’ai aucune information sur comment je pourrais interagir avec cet état. Est-ce que je peux mettre cette valeur à true ? Comment je pourrais faire ?

En envoyant du html, j’envoie un bouton qui contient toutes ces informations. Mais aussi un bouton qui changera selon l’etat de la variable. Ça n’a de sens que d’envoyer un bouton qui permettra de change la valeur (Exemple juste en dessous)

<button hx-get="/app/{{ .Name }}/set_canary?enabled={{ not .CanaryEnabled }}" hx-target="#app"
 hx-swap="outerHTML">
    {{if .CanaryEnabled}}Disable{{else}}Enable{{end}} Canary
</button>

Pour moi, quand on cherche à construire des interfaces destinées à des humains, comme ici, ça a plus de sens de considérer d’envoyer des hypermedia, et comme on parle d’interface web, d’envoyer du HTML. Évitant ainsi de devoir faire passer des clients web lourd, pour atteindre ce même objectif.

HTMX is the my way

Comme je l’ai dit, je ne suis pas développeur web, je n’ai pas forcément beaucoup de vison de tout ce qui a pu être fait dans ce domaine. Lorsque j’ai découvert HTMX, un peu par hasard, deux choses m’ont conquis :

D’abord, la simplicité. Juste ajouter des attributs html et pouvoir pratiquement tout faire avec est complètement fou. Je n’ai pas besoin de me coltiner l’enfer des frameworks javascript pour produire quoi que ce soit

Ensuite toute cette histoire d’hypermedia pour des applications web, ça tombe juste complètement sous le sens. Et dans un monde dans lequel l’API JSON domine, cette petite piqure de rappel fait du bien.

Toutes les sources pour explorer cet outil sont bien évidemment disponibles https://github.com/inpulse-tv/devops-tool-htmx

Références

  • htmx : https://htmx.org/
  • Hypermedia and rest : https://htmx.org/essays/#hypermedia-and-rest

Catégories : DevOps

0 commentaire

Laisser un commentaire

Avatar placeholder

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Une Single-Page Application pour un outil DevOps avec HTMX+Golang

par Laurent G. temps de lecture : 8 min
0