Toute l'actualité devOps dans le média Mcorbin
Kubernetes, Cloud, Observability... Quand l'écart technique et de pratiques tue le débat
Pourquoi certaines technologies créent des débats sans fins et souvent stériles ? Je pense que c’est parce que l’écart s’est trop creusé entre les différents partis. Et je vais commencer cet article par une anecdote. Il y a de ça quelques années, je présentais sur Twitter un de mes projets open source, projet évoluant dans le monde du monitoring/observability. Une personne "très connue" de la communauté tech Française est rapidement venue troller en disant que le projet était inutile, que des tonnes d’alternatives existaient déjà, et qu’il fallait à un moment arrêter de réinventer la roue. Je n’étais bien sûr pas d’accord avec ces affirmations pour de nombreuses raisons mais lors du débat qui a suivi une chose m’a marqué: j’étais en totale déconnexion en terme de technique et pratiques avec mon interlocuteur. Je décrivais mon projet en expliquant ce qu’il apportait en terme de bonnes pratiques de monitoring par rapport à d’autres produits, et donc parlais de blackbox vs whitebox monitoring, de labeling, de formats d’expositions, de service discovery, de l’intêret d’avoir des outils "API first", de hot reload… là où mon interlocuteur était bloqué à "on n’a jamais fait mieux que Nagios pour monitorer des infras". Bref, Dunning-Kruger tambourinait très fort à la porte. La discussion s’est évidemment enlisée car au final toutes les pratiques "modernes" de monitoring n’était il paraît que de la hype inutile. Un pattern courant Cela ne vous rappelle rien ? Des sujets sur le cloud et le Kubernetes sont également victimes de ce phénomène. Trop hypes, trop chers, inutiles, "de toute façon je fais mieux en roulant mon infra sous les aisselles"… Le point commun de tout ça ? Expliquer l’apport en terme de pratiques et de fiabilité technique en apportant des faits ne permet plus de convaincre, car la communauté tech est disloquée en plusieurs mondes difficilement réconciliables. J’ai beaucoup essayé sur ce blog ou ailleurs (podcast, conférences…) de parler d’abord pratiques: qu’est ce que ça veut dire avoir une prod de qualité ? Comment on rend des développeurs autonomes dans leurs tâches quotidiennes sur la production, sans avoir des ops dans leurs pattes ? Comment on arrive à déployer 10, 20, 50, 100… fois par jour en prod de manière fiable ? Comment lutter contre l’alert fatigue et avoir une gestion d’incidents efficace ? Qu’apporte une approche "self-service" dans un département tech ? Comment on arrive à ne (presque) plus faire de run ? Comment je fiabilise au maximum ma prod en terme de tolérance aux pannes et sécurité sans avoir à restreindre les fonctionnalités de la plateforme ? Je pourrai rajouter de nombreuses choses à cette liste. Toute cette vision pourrait se résumer à "comment produire de la qualité en tant qu’ops pour tirer la boîte vers le haut". Il y a un travail organisationnel et d’alignement pour porter ce type de vision mais faire les (bons) choix techniques est également important notamment sur les projets non triviaux. J’insiste sur le "non triviaux" pour ceux au fond de la salle qui me sortiront le fameux "oui mais moi j’ai juste un wordpress à déployer". Cool, dans ce cas là n’importe quelle solution fera l’affaire. L’ops est un milieu conservateur Les ops adorent généralement critiquer les développeurs et notamment les différentes "hypes" du moment des dev: nouveaux frameworks, outils, pratiques. Et c’est vrai qu’il y a parfois de l’abus dans certains écosystèmes. Mais il faut également remercier les développeurs pour cela. Le monde de l’ops est un milieu extrêmement conservateur (même si cela s’améliore avec la nouvelle génération), où les nouvelles technologies et pratiques sont souvent vus avec méfiance voir haine. Et dans de nombreuses boîtes c’est clairement les dev (souvent des dev ayant également un pied dans l’infra, soyons honnête) qui ont réussi à pousser de nouvelles manières de bosser. Quand en 2023 vos ops sont toujours à se demander si il faut faire du conteneur en production, voir débattent encore de l’intérêt de systemd, la meilleure chose à faire est en effet de les bypass dans le but de retrouver de la productivité. Et c’est ce conservatisme que l’on retrouve dans les longs "débats" tech dans les réseaux sociaux. Je me rappelle il y a longtemps un manager infra disant quelque chose comme "Des machines virtuelles à la demande? J’en ai jamais eu besoin dans le passé je vois pas pourquoi on investirait là dedans". Pourtant l’apport aurait été énorme pour les projets (qui eux étaient demandeurs), mais son manque d’expérience des pratiques modernes ne lui permettait même pas d’entrevoir ce que cela allait apporter. Remplacez machines virtuelles par cloud, Kubernetes ou autre. Combien "d’anti cloud" ou "d’anti Kubernetes" ont réellement une expérience en production avec ? Je me suis par exemple rendu compte récemment que j’avais débattu pendant des années avec des gens sur le sujet de Kubernetes alors qu’ils ne l’avaient en fait jamais utilisé. Certains vont probablement me dire "oui mais le contexte c’est important, certains contextes techniques sont difficiles etc". Et c’est vrai. Et j’ai travaillé dans le passé dans ce type de contexte. Mais jamais je ne serais aller faire la leçon à tout ceux qui m’ont permis de progresser en leur disant "Ah non, vu que moi je bosse dans une organisation broken personne n’a le droit de faire mieux", voir, comme on me l’a déjà dit "ce que tu décris est impossible, tu mens sur tes pratiques". Je reste quelqu’un d’optimiste. Je pense que globalement tout le monde est capable de progresser dans le bon contexte, et on a tous des expertises différentes. Je suis nul sur des sujets comme la virtualisation, le stockage, ou le choix de hardware. On ne me verra jamais aller tenter d’imposer mon point de vue sur ces sujets avec des personnes bossant dessus depuis des années, par contre j’écouterai avec attention ce qu’ils ont à dire si je me retrouvais à travailler sur ce type de technologie. Savoir où s’arrêtent ses connaissances est je pense très important en tant qu’ingénieur. Mais je suis aujourd’hui assez fatigué de discuter avec des gens qui ne souhaitent ni écouter ni progresser mais qui par contre ont des convictions fortes sans ni l’expérience ni l’expertise pour les soutenir. Je pense pourtant qu’il est important de ne pas se décourager et de continuer démonter le FUD sur les réseaux pour une seule raison: les sysadmin/SRE juniors. Un junior peut vite se laisser influencer par certains discours "anti hype", où l’ops est vu comme quelqu’un à contre courant, incarnation du pragmatisme qui peut tout faire à coup de scripts bash et insensible au "marketing" (le mot marketing incluant tout ce qui est sorti après 2005). Dommage de laisser quelqu’un commencer sa carrière avec cet état d’esprit. Cette vision passéiste (et anti modernité) doit disparaître, et c’est aussi notre rôle de faire en sorte que cela arrive.

Kubernetes, Cloud, Observability... Quand l'écart...
Pourquoi certaines technologies créent des débats sans fins et souvent stériles...
Source: mcorbin
Tracing avec Opentelemetry: pourquoi c'est le futur (et pourquoi ça remplacera les logs)
Le tracing, c’est génial mais souvent sous-exploité aujourd’hui dans notre industrie. Venez découvrir pourquoi vous devez mettre des traces dans vos applications. Cet article est second article de ma série sur l'observability. Retrouvez le premier article sur les métriques ici. C’est quoi le tracing ? Le tracing est une technique permettant de suivre au sein d’un même service ET également entre services (donc dans un système distribué) le "parcours" d’une action. Exemple: Une client HTTP envoie une requête à une application appelée App A App A reçoit la requete, déclenche une autre requête vers une base de données, puis envoie également une requête HTTP à une application App B App B reçoit la requête, fait également une requête à une base de données, et renvoie une réponse à App A App A retourne la réponse au client initial On voit ici qu’une action utilisateur (la requête HTTP) a déclenché de nombreuses actions côté applicatif. Il est souvent difficile de suivre dans les systèmes d’aujourd’hui, notamment microservices, ce type d’actions. C’est là que le tracing intervient: il nous permet de suivre avec détails ce qu’il se passe sur nos systèmes. Opentelemetry est une implémentation standard du tracing. Le fait que ce soit un standard est très important. Traditionnellement les APM (cloud type Newrelic, Datadog…) fournissaient leurs propres agents et SDK pour ce type de besoins. Avec Opentelemetry votre code applicatif n’est plus lié à un vendor particulier et donc n’est pas "lock-in". Si vous utilisez Opentelemetry pour les traces, vous aurez la possibilité de facilement changer de technologie de stockage (cloud ou on premise) sans changer votre application, notamment grâce à l’Opentelemetry collector qui est un composant pouvant recevoir et ensuite transférer les traces à différents systèmes Tout un écosystème s’est développé autour d’Opentelemetry et des traces et l’outillage aujourd’hui est très bon dans tous les langages "mainstream". Des gestionnaires de traces comme Grafana Tempo vous permettent par exemple d’avoir de très belles représentations d’une action sur un système distribué (image tiré de cet article). D’autres visualisations sont possibles comme des "services map" montrant sous forme de graphe les communications entre services. Traces et Spans On va donc générer des traces composées de spans pour suivre nos requêtes et actions. La documentation officielle d’Opentelemetry explique bien le concept que je vais résumer ici. Prenons l’exemple précédent. Il va être possible avec Opentelemetry de mesurer le temps d’exécution de chaque étape du parcours de notre action. On veut donc mesurer le temps d’exécution de la requête par le client HTTP original. Puis le temps passé dans l’application A, où l’on peut mesurer le temps de traitement de la requête HTTP par le serveur, qui sera lui même composé du temps d’exécution de la requête SQL et de l’appel HTTP à l’application B. Même chose pour l’application B où l’on mesurera le temps de traitement de la requête HTTP reçue et de la requête SQL exécutée. Voici une représentation de cela, avec le temps en abscisse: Il serait également possible de représenter cette série d’action comme un arbre: On voit ici que chaque action a comme noeud suivant l’action (ou les actions) suivante (et inversement ces actions ont comme parent l’action précédente). On pourrait également représenter l’exécution de la toute première requête HTTP (HTTP Client) en JSON: { "name": "HTTP Client", "context": { "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2", "span_id": "0x5fb397be34d26b51" }, "start_time": "2023-08-19T17:52:58.114304Z", "end_time": "2023-08-19T17:55:98.114304Z", "parent_id": null, "attributes": { "http.route": "/foo", "http.request.method": "GET", "http.response.status_code": 200, "server.port": 443, "url.full": "https://app-a.mcorbin.fr/foo" } } En fait, ce JSON est la représentation d’une span opentelemetry. Comme dit précédemment une trace permet de suivre l’exécution d’actions liées entre elles. Une trace est tout simplement une liste de spans partageant le même trace_id. Dans cet exemple, ma span a une trace_id égale à 0x5b8aa5a2d2c872e8321cf37308d69df2. Elle a une également une span_id (0x5fb397be34d26b51, permettant de l’identifier de manière unique). Ma span a également une date de début et de fin (start_time et end_time). La span démarre avant l’exécution de l’appel HTTP, et se termine lorsque la réponse est reçue. On peut comme cela déduire le temps d’exécution total ce cette action. Vient ensuite le champ parent_id. Rappelez-vous de la représentation en arbre des actions: ici, c’est la première action à être exécutée donc la span n’a aucun parent: c’est ce qu’on appelle la root span. Viennent ensuite les attributes dont nous allons parler dans la section suivante. Spans et attributes Une span avec juste un nom (comme HTTP Client) et un temps d’exécution serait inutile: il serait impossible d’en faire quelque chose et de comprendre l’action réalisée. C’est là qu’interviennent les attributes. Dans la span représentée précédemment, les attributes seront liés à la requête HTTP réalisée: route, méthode, status de la réponse, port, url cible… cela permet de comprendre immédiatement la requête qui a été exécutée. Représentons maintenant la span suivante de notre arbre: le serveur HTTP recevant la requête dans l’application A: { "name": "HTTP server", "context": { "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2", "span_id": "0x8ad397bae4d26489" }, "parent_id": "0x5fb397be34d26b51", "start_time": "2023-08-19T17:53:01.114304Z", "end_time": "2023-08-19T17:55:50.114304Z", "attributes": { "http.route": "/foo/:id", "http.request.method": "GET", "http.response.status_code": 200, "url.path": "/foo/123456", "url.scheme": "https", "server.port": 443, "server.address": "app-a.mcorbin.fr", "client.address": "10.36.1.2", "client.port": 39874 } } Regardons déjà les ID de trace et de span. Le trace_id est le même que la span HTTP CLient: en effet, les deux spans font partie de la même série d’action (de la même trace). Le span_id est par contre unique à la span. Ici, le parent_id n’est pas null: il est égal à 0x5fb397be34d26b51 qui est la valeur de la span précédente (HTTP Client). Cela est logique car c’est bien cette span qui est parente dans l’arbre. Les attributs donnent également ici des informations sur la requête HTTP reçue. On peut voir quelques similitudes avec la span HTTP Client. Conventions de nommage On a vu l’importance des attributs pour donner du contexte aux spans. Dans notre exemple du début d’article on aurait par exemple une span pour la requête SQL du service A. Cette span ressemblerait probablement à quelque chose comme: { "name": "SQL query", "context": { "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2", "span_id": "0x1bc187ba44dc6aea" }, "parent_id": "0x5fb397be34d26b51", "start_time": "2023-08-19T17:53:08.114304Z", "end_time": "2023-08-19T17:53:49.114304Z", "attributes": { "db.name": "users", "db.statement": "SELECT id, email, password FROM users WHERE =", "db.operation": "SELECT" } } Ici les attributs seront spécifiques à SQL et nous permettent aussi de facilement identifier l’action réalisée. Il est important de standardiser les attributs. Nous allons en effet pouvoir faire des recherches sur les traces. Cela serait très difficile si un service nommait un attribute db.statement et l’autre database.statement: il faut des conventions de nommage. Le standard Opentelemetry fournit déjà une liste de noms et types d’attributs (let attributes sont typés) standards. Vous pouvez trouver cette liste ici. Tous les attributs techniques "classiques" (en lien avec des technologies comme les base de données, les cloud providers, des protocoles comme HTTP ou gRPC) sont déjà standardisés. Des librairies existent également pour construire ces attributs standards (comme la lib semconv en Golang). Il est également très important de standardiser vos attributs métiers. Avoir des attributs techniques attachés aux spans est intéressant, mais il est encore plus intéressant d’attacher du contexte métier à vos spans. Cela peut être des attributs comme <company-name>.user.id, <company-name>.organization.id ou tout autre attribut métier important. Rappelez vous que les traces sont intéressantes notamment pour découvrir des problèmes de performance: peut être allez-vous vous rendre comme que c’est toujours les requêtes venant d’un utilisateur spécifique qui sont lentes grâce à un attribut contenant son ID. Sans cet attribut, l’investigation du problème pourrait être beaucoup plus lente. Resource Il est commun dans une span d’avoir également un bloc resource contenant des attributs communs à une application: { "resources": { "service.name": "App-A", "service.version": "0.0.1" } } Ces attributs sont souvent utilisés par les base de données de traces pour stocker ensemble les spans d’un même service, car toutes les spans émises par une instance d’un service donné auront les mêmes ressources. Scope Un autre bloc présent dans une span est le bloc scope. Voici par exemple le contenu de scope pour une span émise par le package Golang otelgin, permettant d’ajouter le support d’Opentelemetry au framework Gin: { "scope": { "name": "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin", "version": "0.42.0", "attributes": {} } } Cela permet de voir que cette span a été générée par cette librairies. La documentation officielle d’Opentelemetry explique bien cela. Status Il est également possible d’ajouter à une span un status avec un message permettant de très rapidement filtrer les spans en erreur. Une span représentant une erreur de validation de requête HTTP pourrait avoir comme status: { "status": { "message": "Invalid HTTP Body", "status": "error" } } Events, et pourquoi les traces peuvent remplacer les logs On arrive à un concept que j’adore dans le tracing: les events. On a étudié précédemment des exemples de spans représentant une action avec une date de début, une date de fin et des attributs. Il est également possible d’ajouter à une span des messages arbitraires avec un timestamp fixe associé et des attributs: ce sont les events. On peut voir un event comme un log, c’est exactement le même principe. Reprenons notre exemple de serveur HTTP recevant une requête. Il est commun d’avoir dans des handlers HTTP des logs applicatifs de ce type: logger.Infof("received request from user %s to perform action Foo", userID) logger.Infof("user %s is in trial mode", userID) // traitement de la requête logger.Infof("successfully performed action Foo for user %s", userID) Ces logs sont il est vrai de faible qualité (c’est juste pour l’exemple) mais on voit souvent ce genre de patterns avec de nombreux logs ajoutés "au cas où" et loggant toute sorte de choses en production. Mais si vous faites du tracing, ces logs peuvent complètement disparaître: L’user ID et mode peuvent être simplement attaché à la span comme un attribute Des events attachés à la span peuvent remplacer les logs Un exemple: { "name": "HTTP server", "context": { "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2", "span_id": "0x8ad397bae4d26489" }, "parent_id": "0x5fb397be34d26b51", "start_time": "2023-08-19T17:53:01.114304Z", "end_time": "2023-08-19T17:55:50.114304Z", "attributes": { "user.mode": "trial", "user.id": " 86a7d17f-ab35-4312-88ea-9414a15e450b", "http.route": "/foo/:id", "http.request.method": "GET", "http.response.status_code": 200, "url.path": "/foo/123456", "url.scheme": "https", "server.port": 443, "server.address": "app-a.mcorbin.fr", "client.address": "10.36.1.2", "client.port": 39874 }, "events": [ { "name": "successfully performed action Foo", "timestamp": "2023-08-19T17:55:20.114304Z", "attributes": { "user.id": " 86a7d17f-ab35-4312-88ea-9414a15e450b", } } ] } Comme vous pouvez le voir, un log est devenu un event attaché à ma span et le reste des attributs (user.id et user.mode). Et ça, c’est GENIAL. Pourquoi c’est genial ? Reprécisons pourquoi le tracing est intéressant: suivre des actions entre services. Les logs sont très souvent utilisés pour ça également. Qui n’a jamais utilisé les logs pour suivre le parcours d’une requête dans une application, ou même entre applications via des recherches plus ou moins complexes ? En ayant des attributs métiers dans mes spans ET en ajoutant des events lorsque j’ai besoin d’avoir un timestamp exact associé à un message, j’ai le meilleur des deux mondes: Je bénéficie de tout l’écosystème des traces pour suivre le parcours de mes actions Je peux, une fois ma requête identifiée, avoir facilement tous les "logs" (events) associés à cette requête car ils seront tout simplement attachés aux spans? C’est un énorme gain de temps. Je n’ai pas à jongler entre différents outils pour essayer de trouver une information. Du moment que j’ai un ID de trace (ou que je peux le retrouver via une recherche sur un attribut), j’ai accès à toutes les informations sur cette trace (depuis tous mes services) d’une manière unifiée. Je n’ai plus à galérer à corréler des logs entre eux, ma trace et mes spans le font pour moi ! Je prévois dans le futur une fusion des outils de gestion de traces et de logs. Pourquoi garder les deux alors que les traces font déjà un boulot LARGEMENT meilleur tout en étant un format standard ? Je pense que si je devais reconstruire un système d’information "from scratch" aujourd’hui je: Garderai les logs pour les erreurs et certains logs très importants (type logs "légaux" à conserver). Et encore: le SDK Opentelemetry permet d’attacher des erreurs aux spans sous forme d’events donc c’est également super d’avoir les erreurs attachées aux spans. Tout le reste: des traces avec des attributs et events de qualité. Je suis sûr qu’on pourrait même faire une sorte de "log level" pour les traces, ajoutant certains events à des spans en fonction d’une configuration globale, comme un logger. Sampling Cela me permet de rebondir sur le sujet du sampling. On entend toujours dire lorsqu’on parle des traces "conserver 100 % des traces est trop coûteux en stockage car trop de volume", et donc qu’il faut faire du sampling (par exemple, ne conserver que 5 % des traces). Je ne pense pas que ce soit une bonne idée. Certains outils comme Grafana Tempo ont pour objectif de pouvoir garder 100 % des traces en les stockant sur S3 pour baisser les coûts. Mais le lien entre traces et logs est aussi important. Les logs aussi sont souvent coûteux à stocker (je vois les utilisateurs Elasticsearch sourire en lisant ça). Pourtant, des applications qui loggent comme des porcs sans que personne ne lève un sourcil ne posent aucun problème dans de nombreuses entreprises, et personne ne veut faire du sampling sur les logs car cela baisse les capacités d’investigations de problèmes. Pourquoi ne pas voir les traces comme un remplaçant des logs et donc avec un transfert des coûts depuis les logs vers les traces ? Au final, l’information est quasiment la même, les deux sont des hashmaps clé/valeur. Ne vous dites pas "j’aurai 40TB de traces en plus de mes 40TB de logs", mais plutôt "j’aurai 40TB de traces mais plus que 2TB de logs, et une meilleure capacité à investiguer des problèmes". C’est vraiment vers ça que doit pour moi se tourner l’industrie (et les gens construisant des outils de gestion de logs). Pour aller plus loin Il y a d’autres champs disponibles dans les spans. Les Links permettent de lier des spans entre elles même si elles font partie d’autres traces (donc d’avoir aussi des relations entre traces et non seulement entre spans d’une même trace). Le champ Kind permet d’ajouter une valeur spéficiant si l’émetteur est un client, un serveur, ou consumer de messages (d’une queue de message par exemple)… Bref, la définition des spans est vaste et le mieux est d’explorer la documentation. Propagators Les traces permettent donc de suivre entre systèmes des actions. Mais comment réaliser cela quand par exemple un client envoie un requête HTTP à un serveur, ce serveur publiant ensuite un message dans une queue de message (ou Kafka par exemple) qui sera ensuité consommé par un autre service ? Nous voulons pouvoir suivre l’action jusqu’à sa fin. C’est là que les propagators interviennent: c’est une convention pour passer des informations de tracing entre systèmes. Le plus connu est le propagator Trace context: c’est un standard du W3C. Dans le cas d’un client HTTP faisant un requête sur un serveur, les informations de la span courante côté client seront passées au serveur via un header HTTP appelé Traceparent, par exemple: Traceparent: 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01 Le format est <version>-<trace-id>-<parent-span-id>-<flags> Le serveur récupèrera ces informations et les utilisera pour ses propres spans (notamment en mettant dans trace_id l’ID de la trace et dans parent_id l’ID de la span parente du client). Pour de l’asynchrone, pareil: les informations de la trace seront passées dans le message et récupérées par le consommateur. Quand vous utilisez Kafka, il est courant d’utiliser les headers du protocol Kafka pour cela. Il est très important de ne jamais "casser la chaine" du tracing ou alors le contexte est perdu, et votre trace incomplète. Cela demande une certaine discipline dans le code applicatif pour être sûr que le contexte est toujours passé entre fonctions et à chaque I/O. Si un service intermédiaire n’implémente pas le tracing et casse la chaine, c’est game over. Générer des métriques depuis les traces ? Pourquoi as-ton besoin de traces quand on a des métriques ? Car il n’est pas possible avec des traces brutes d’avoir une vue aggrégée du comportement d’un système. Les traces permettent d’avoir accès aux détails sur la performance d’une requête unique: cela est impossible avec des métriques type Prometheus. Les métriques fournissent des informations comme par error le nombre de requêtes par seconde sur un service, et du nombre d’erreur (error rate). Elles vont fournir aussi des quantiles pour mesurer la latence (p50, p99…). Et c’est sur des métriques que l’on pourra définir des SLO et calculer par exemple des choses comme un error budget ou burn rate (peut être que je ferai un article sur ce sujet plus tard). Néanmoins, tout cela pourrait théoriquement être dérivé depuis les traces. Si vous gardez 100 % de vos traces, il est possible de recalculer tout ce que vous voulez depuis ces traces. L’outillage pour réaliser cela "at scale" est un peu limité pour le moment. Je montrerai dans un prochain article comment réaliser cela en utilisant Mirabelle, un outil que j’ai conçu et sur lequel je viens de rajouter le support d’Opentelemetry traces. Cela n’est pas forcément une idée nouvelle. A l’époque de protocoles comme statsd les métriques étaient dérivées d’événéments. L’industrie a ensuite changé son fusil d’épaule avec des outils comme Prometheus (et du pull pour récupérer les métriques) mais je pense que l’on va revenir dans les prochaines années à des systèmes orientés "push", basés sur Opentelemetry, et j’espère les traces. Je rêve d’un monde où j’ai juste à générer des traces pour remplacer la majorité de mes métriques et de mes logs. Conclusion Je pense que le tracing est le futur de l’observabilité. Je n’ai rien d’autre à dire pour conclure cet article ;)

Tracing avec Opentelemetry: pourquoi c'est le futur...
Le tracing, c’est génial mais souvent sous-exploité aujourd’hui dans...
Source: mcorbin
Les développeurs doivent-ils apprendre/connaître l'infra (docker, cloud, Kubernetes...) ?
Je tenterai dans cet article de répondre à cette question. Tout a commencé sur un réseau social bien connu où un développeur conseillait à ses camarades d’apprendre les notions d’infrastructures (docker, cloud, Kubernetes). Il en est ressorti des discussions intéressantes et ça m’a donné envie de reparler du sujet. Apprendre l’infrastructure est toujours intéressant De manière générale apprendre de nouveaux trucs est toujours intéressant. Je pense personnellement que les meilleurs profils du marché sont des profils ayant une forte expertise dans plusieurs domaines, ce qui leur permet d’avoir une vue d’ensemble des problématiques de l’entreprise. Cela facilite également grandement la capacité à proposer puis implémenter une vision technique et à faire le pont entre différentes équipes. C’est un peu mon cas où j’ai toujours travaillé à la frontière du dev et de l’ops et donc suis capable d’intervenir et accompagner sur les deux tableaux selon les besoins. Par exemple, je considère aujourd’hui qu’il est important qu’un Sysadmin/DevOps/SRE (le nom du job est un détail) doit savoir coder. Quand je dis doit savoir coder, je ne dis pas être capable de faire du shell dégueulasse, je dis être en capacité d’être mis dans une équipe de dev et de s’en sortir dans un temps raisonnable, et donc de maîtriser à minima les bases de l’ingéniérie logicielle (design patterns, concurrence et parallélisme, tests et mocks, maîtrise basique du DDD). Soyez rassuré si vous êtes dans ces métiers mais manquez de compétences en développement: cela s’apprend. Dans mon entreprise nous formons en interne au développement par exemple. C’est aussi là que l’on se rend compte qu’avoir des équipes avec des profils venant des deux mondes est intéressant (nous recrutons également des profils purs dev que nous formons à l’infrastructure). Mais revenons aux développeurs. Apprendre l’infra sera donc en effet un gros plus pour eux. J’adore personnellement ce genre de profils. Mais apprendre la data (analyse, machine learning, stockage…) serait également un gros plus, ou bien apprendre la programmation système ou kernel, ou le réseau… La connaissance n’est jamais perdue et on ne peut de toute façon pas être expert en tout. Donc apprendre l’infrastructure est intéressant mais ce n’est pas obligatoire. Il y a pourtant certaines bases à maîtriser. Gérer son app du dev à la prod Je suis convaincu qu’une entreprise produisant du logiciel (je parle du type d’entreprise que je connais: services en ligne, sites web, solutions SaaS, services Cloud, logiciels édveloppés et utilisés en interne type SI grands groupes…) ne peut être efficace que si les développeurs peuvent travailler en autonomie du développement à la production jusqu’à l’exploitation. Qu’est ce que cela veut dire ? Les équipes infrastructure ne doivent pas être un point de blocage ou de friction lorsqu’un développeur doit déployer en production. Une mise en prod doit devenir quelque chose de simple à réaliser, qui peut être fait de manière répétable et à tout moment. Cela ne veut pas dire qu’on va donner les accès admin de la production aux dev: on va au contraire faire en sorte qu’un déploiement soit une procédure standard et automatisée. Dans ma boîte actuelle on déploie ~80 fois en production par jour, heureusement que je ne suis pas contacté à chaque fois pour valider le déploiement, hein ? Par exemple, à chaque fois qu’une nouvelle release est réalisée pour une application, ou qu’un commit est merge sur la branche principale, la plateforme d’intégration continue peut lancer automatiquement (ou via un action manuelle d’un dev comme cliquer sur un bouton) le déploiement. Ici, les dev n’ont pas accès à la prod: ils ont accès à une abstraction permettant de déclencher l’action de mise en production de manière fiable. De la même manière un rollback doit être une simple action accessible également aux dev. Il est également important ici pour les dev de connaître la façon dont le déploiement va se réaliser, notamment son déroulement, quel que soit le type d’infrastructure utilisé (PaaS, Kubernetes…): On a très souvent plusieurs instances d’une application en production pour la tolérance aux pannes, et on met à jour généralement ces instances une par une à la nouvelle version, en arrêtant le déploiement en cas de soucis. Cela permet d’éviter les downtime en cas de problème. Lorsqu’une ancienne version de l’application est arrêtée, la plateforme de déploiement envoie généralement un signal (SIGTERM) à l’application pour lui dire de s’éteindre. L’application doit capter ce signal et se stopper proprement, sans perdre aucune requête HTTP (par exemple celles en cours de traitement): l’ordre d’arrêt des composants internes de l’application est donc importante (il serait dommage de stopper son threadpool de base de données alors que le serveur HTTP reçoit toujours des requêtes). L’application doit également démarrer proprement: les applications exposent habituellement un endpoint HTTP /health indiquant qu’elle est prête à accepter du traffic réseau. Là aussi l’ordre de démarrage des composants de l’application est imoortant. Il est courant d’exécuter des changements de schémas de base de données au démarrage des applications: il faut garder en tête qu’il faut pouvoir rollback. On voit dans ces quelques exemples que l’infrastructure a un impact sur la conception de l’application et que cette dernière doit avoir un certain comportement pour fonctionner sans problème. Il est extrêmement courant d’avoir des applications perdant des requêtes HTTP (donc retournant des erreurs aux clients finaux) during les mises à jour par exemple car les contraintes infrastructures ne sont pas comprises. De même, si l’application a un problème en production (par exemple une application HTTP commence à retourner des erreurs 500), ce ne sont pas les équipes infrastructure qui doivent être alertées en premier. Pourquoi ? Car ce sont les équipes de dev qui ont la connaissance de l’application. En tant que SRE, que puis-je faire si un bug applicatif cause un problème dans une application sur lequel je n’ai jamais travaillé, voir dont je ne connais ni le langage ni le métier ? Il n’y a donc aucun intêret à avoir un intermédiaire "ops" qui se contentera de toute façon de retransmettre l’alerte aux équipes dev concernées, cela fera perdre du temps à tout le monde. Cela veut dire que les dev doivent aussi maîtriser la partie observability de leurs applications: Compréhension de la différence entre logs, métriques, et traces, de quand et comment les utiliser (types de métriques, labels, cardinalité, logs structurés, fonctionnement du tracing…). Capable de mettre en place des métriques techniques et business et de définir des SLO et alertes dessus. Capacité à aller explorer ces informations (dashboards, outils de recherche de logs…) et de savoir en autonomie investiguer des problèmes liés à leurs applications. Capable de comprendre l’impact d’une panne d’un composant système externe (base de données, autre service…) sur son application et de prévoir des mécanismes pour que l’application réagisse correctement (éviter des états inconsistants dans la base de données, réconciliation…). Cela ne veut pas dire qu’il n’y a pas des équipes ops/SRE en support, qui seront là pour mettre en place les outils nécessaires pour rendre cela possible et accompagner si besoin les équipes sur ces bonnes pratiques. Bref, on voit que de nombreuses choses liées à l’infrastructures doivent être compréhensibles pour les développeurs. J’aurai également pu parler de pipelines de CI où là aussi une certaine autonomie est attendue pour définir les étapes de tests, lint, build et pour investiguer des jobs en echec. Une histoire de niveau Bien sûr, un dev junior ne peut pas maitrîser tout ça. Mais au bout de plusieurs années de dev maîtriser ces sujets devient essentiel. Au délà de la partie métier (compréhension du besoin du client, organisation…) et technique pure dev (architecture logicielle), les profils senior et plus doivent élargir leurs horizons et comprendre comment les applications s’intègrent dans le SI au sens large. Travailler sur des architectures orientées service ou des systèmes distribués que l’on retrouve de plus en plus (message bus, base de données NoSQL, ou même sur les communications inter services) demandent d’avoir des compétences transverses. J’aurai également pu citer une maitrîse basique du réseau (HTTP/gRPC, TCP, TLS, UDP, DNS, base du routage, load balancing…) comme autre compétences importantes, voir des compétences systèmes ou hardware sur des applications demandant de très hautes performances (pattern d’accès au disque, epoll…). Je pense que ce sont les équipes qui ont intégrées ces éléments dans les compétences des développeurs qui travaillent le plus efficacement aujourd’hui. Mais je le répète, les dev ne remplacent pas les ops, on ne demande pas aux dev ici de déployer des clusters Kubernetes ou de configurer des machines virtuelles, mais d’avoir la maîtrise totale de leurs applications. Références J’ai déjà produit pas mal de contenu sur le sujet que je repartage ici: Mon talk "Rendez vos développeurs autonomes sur la production" Kubernetes et manifests YAML: trop bas niveau pour les dev ? L’important n’est pas la technologie mais la plateforme 2022: bash n’est toujours pas une bonne idée pour l’administration système Conclusion Avoir certaines compétences infra est non seulement utile mais indispensable. Pas besoin d’être un expert de Linux, du cloud ou de l’orchestration de conteneurs pour être développeur, mais des bases sont attendues et si des gens veulent aller plus loin c’est très bien aussi: l’industrie a besoin de ces profils hybrides ayants des expertises variées.

Les développeurs doivent-ils apprendre/connaître...
Je tenterai dans cet article de répondre à cette question.
Tout a commencé...
Source: mcorbin
Observability: tout ce que vous avez toujours voulu savoir sur les métriques
Je présenterai dans cet article "techno-agnostic" (aucune techno citée) les différents types de métriques que vous pouvez retrouver dans une application, et expliquerai comment les utiliser. Cet article est le premier d’une j’espère longue série sur l’observability qui traitera en profondeur du sujet. Attendez vous prochainement à d’autres articles sur les logs, les traces, les SLO/error budget/burn rate, l’alerting, le monitoring "blackbox" et toutes les bonnes pratiques associées. Retrouvez l’article sur Opentelemetry et le tracing ici. Qu’est ce qu’une métrique ? Une métrique est une mesure et des informations associés à une date précise (un timestamp). Prenons l’exemple d’une métrique représentant le nombre de requêtes HTTP reçu par un serveur web. Je l’appellerai http_requests_total. Cette métrique sera donc incrémentée à chaque fois que le serveur HTTP reçoit une nouvelle requête. Imaginons maintenant que nous puissons regarder et noter de manière régulière la valeur de cette métrique. Cela donnerait peut être (Pour plus de simplicité, je ferai toujours commencer le temps à la valeur 0 dans mes exemples) : Table 1. Valeurs de http_requests_total timestamp valeur 1 10 11 70 21 90 Au temps 1, la valeur de la métrique est 10. Au temps 11, elle est de 70 et au temps 21 elle est de 90. On en déduit donc que notre serveur HTTP a reçu 60 requêtes (70 - 10) entre les temps 1 et 11, et 20 requêtes (90 - 70) entre les temps 11 et 21. Prenons un autre exemple: une métrique pourrait par exemple remonter la consommation mémoire (RAM) d’un serveur. Elle s’appellera ici memory_used et sa valeur sera en mégabyte: Table 2. Valeurs de memory_used timestamp valeur 1 1500 11 3000 21 2900 Notre serveur utilisait donc 1500 MB de mémoire au temps 1, 30000 MB au temps 11, et 2900 au temps 21. Nos métriques peuvent donc représenter plusieurs choses mais l’idée est toujours la même: une mesure associée à un temps. Labels Tout cela est bien pratique, mais il est très souvent nécessaires d’avoir plus de détails sur les métriques. Reprenons notre métrique http_requests_total : comment faire si je souhaite compter le nombre de requêtes par url cible ou par méthode HTTP par exemple ? Si mon serveur web héberge un blog, j’aimerai bien avoir le nombre de de requêtes par article de blog dans le but de connaître mes articles les plus populaires. Cela est possible en rajoutant des labels à la métrique. Les labels sont des dimensions supplémentaires attachées à une métrique. Je vais ici en ajouter trois: url : l’adresse de la page demandée à mon serveur web method : la méthode HTTP de la requête Voici à quoi pourraient ressembler des observations de cette métrique pour par exemple un blog culinaire présentant des recettes de pâtisseries: Table 3. Valeurs de http_requests_total avec des labels timestamp url method valeur 1 /paris-brest GET 40 3 /eclair GET 10 11 /paris-brest GET 60 13 /eclair GET 15 Ces valeurs nous montrent qu’au temps 1, la page paris-brest avait eu 40 visites, puis 60 au temps 11. La page eclair avait elle 10 visites au temps 3, puis 15 au temps 13. La méthode HTTP ici est toujours GET. Nous verrons des exemples plus complexes dans la suite de cet article. On appelle généralement série une combinaison possible des valeurs des labels pour une métrique donnée. Nous avons dans cet exemple deux séries pour la métrique http_requests_total: url="/paris-brest", method="GET" url="/eclair", method="GET" Cela nous amène à la notion de cardinalité et du choix des labels. Cardinalité On appelle cardinalité le nombre de série pour une métrique. La cardinalité était donc de deux dans notre exemple précédent. Imaginons le même site web mais avec ce coup ci 50 recettes différentes, et que l’on autorise en plus les utilisateurs à voter pour une recette (dans le but de pouvoir classer les recettes par popularité par exemple) en exécutant une requête de type POST sur l’url de la page. GET /paris-brest permettrait par exemple aux utilisateurs de récupérer la recette du Paris Brest, et POST /paris-brest de voter pour cette recette. On a donc 50 pages (50 recettes), et 2 méthodes (GET et POST) par recette. Notre cardinalité est donc de 50 * 2 soit égale à 100. Rajoutons un label à notre métrique: le nom de la machine (host) hébergeant le serveur web. Il est en effet courant d’avoir une application hébergée sur plusieurs serveurs pour par exemple avoir de la tolérance aux pannes. Voici par exemple deux séries ayant les mêmes labels à part celui nommé host: Table 4. Exemples de séries timestamp url method host ̀ valeur 1 /paris-brest GET server_1 40 1 /paris-brest GET server_2 10 Quelle serait la cardinalité de la métrique si l’on hébergeait le blog culinaire sur 4 serveurs différents ? Elle serait de 50 * 2 * 4 soit 400 (50 pages, méthodes HTTP GET ou POST, et les 4 serveurs). choix des labels et explosion de la cardinalité Il est important de choisir correctement les labels d’une métrique: Les labels doivent être pertinents et avoir du sens pour la métrique. Comme nous le verrons plus loin dans cet article la majorité des bases de données pour stocker des séries temporelles permettent de requêter les métriques en fonction de leurs labels. Avoir un label url sur une métrique HTTP est donc logique car il sera très utile de pouvoir filter les métriques sur ce label. Il faut éviter d’avoir une cardinalité trop importante. Une erreur classique faite par de nombreux développeurs est de stocker l’ID aléatoire (uuid généralement) généralement associé à une requête HTTP dans un label: cela veut dire que chaque requêtes HTTP sur le serveur web créera une nouvelle série. Cette série n’aura qu’une mesure, car une nouvelle requête en créera une nouvelle. Il y a d’autres pièges à éviter sur les labels. Reprenons notre label url sur notre serveur HTTP. Certaines URL peuvent être variables, par exemple une API web pourrait contenir un ID d’utilisateur comme par exemple /user/:id, la partie :id étant variable et contenant un ID associé à chaque utilisateur. Il est important dans ce cas d’utiliser comme label pour url la valeur /user/:id sans remplacer l’ID à chaque requête, et non par exemple /user/1, /user/2… ce qui créerait une série pour chaque utilisateur de la plateforme. Utiliser l’url avec la variable non remplacée ne créera qu’une série quel que soit la valeur de la variable. Certains labels peuvent être identiques à de nombreuses séries. Les organisations ont très souvent plusieurs environnements: production, pré-production (staging), développement… Il est très intéressant d’ajouter ce label aux métriques pour pouvoir facilement les distinguer, comme pour par exemple avoir des politiques d’alertes différentes entre un environnement de production et de développement. Toutes les métriques de production pourraient par exemple avoir un label environment=production. D’autres labels génériques de ce type peuvent aider à classifier les séries. Il est également important d’avoir une cohérence sur le nommage des labels. Il serait dommage d’avoir la moitié des métriques avec env=production et l’autre moitié avec environment=production par exemple. Certains outils permettent de faire du relabeling (renommer les labels de certaines métriques) mais se poser ce genre de questions dès la mise en place du monitoring reste important. Types de métriques Il existe différents types de métriques. Il vous faudra choisir le bon type selon ce que vous voulez mesurer et calculer. Compteurs Un compteur (counter) est tout simplement une métrique comptant quelque chose. C’était par exemple le cas de la métrique http_requests_total présentée précédemment. Cela veut dire dans le cas de cette métrique que mon serveur web va incrémenter la série correspondante (en fonction des labels) à chaque requête HTTP reçue. Ces compteurs par série vont donc seulement s’incrémenter en permanence. Compter des choses est utile mais il est souvent plus intéressant de calculer un taux (rate) par seconde. Voici par exemples 3 valeurs pour une même série avec le calcul du rate pour chaque valeur. Table 5. Calcul du rate http_requests_total timestamp url method compteur rate (req/sec) 1 /paris-brest GET 40 11 /paris-brest GET 80 (80-40)/10 = 4 21 /paris-brest GET 180 (180-100)/10 = 8 31 /paris-brest GET 210 (210-180)/10 = 3 On voit que le rate est calculé en soustrayant la valeur actuelle du compteur par sa valeur précédente, le tout divisé par l’interval de temps. En effet, entre ma métrique au temps 1 et celle au temps 11, 10 secondes se sont écoulées. La valeur de la métrique au temps 1 était de 40, et celle au temps 11 était de 80. Il y a donc eu 40 requêtes (80 - 40) entre ces deux valeurs. On en déduit donc que l’application a reçue en moyenne 4 requêtes par seconde pendant cet interval de temps. Appliquer cette méthode à chaque nouvelle valeur reçue permet de calculer le rate au fil du temps. Réinitialisation du compteur Les applications gardent généralement leurs métriques en mémoire. Cela veut dire que les métriques sont réinitialisées en cas de redémarrage de l’application par exemple. Il est possible dans ce cas d’obtenir un rate négatif. Table 6. Calcul du rate http_requests_total timestamp url method compteur rate (req/sec) 1 /paris-brest GET 40 11 /paris-brest GET 80 (80-40)/10 = 4 21 /paris-brest GET 10 (10-80)/10 = -7 On voit dans cet exemple que la valeur de la métrique est de 80 au temps 11, et -7 au temps 21. En effet, la valeur de la métrique est passée de 80 à 10, ce qui peut arriver si l’application a redémarrée pendant cet interval de temps. Une solution peut être par exemple de filtrer les valeurs négatives, ces dernières étant de toute façon incorrectes. Jauge Un autre type de métrique est la Jauge (Gauge). Cette métrique représente tout simplement une valeur arbitraire. On peut s’en servir pour compter le nombre d’éléments dans une queue de message par exemple. Notre programme pourrait générer une métrique toutes les 10 secondes contenant le nombre d’éléments dans la liste à cet instant. Table 7. Valeur de ma jauge timestamp valeur 1 10 11 8 21 15 C’est sur ce genre de besoins (nombre d’éléments dans une liste, une queue, une table d’une base de données…) que l’on rencontrera le plus souvent ce type de métriques. Quantiles et histogrammes Les quantiles (souvent appelés également percentiles) sont très courants dans le monde du monitoring lorsqu’on souhaite monitorer les performances d’une application. Reprenons notre exemple de blog culinaire. Nous pourrions avoir envie de mesurer le temps de chargement des pages de notre site. Nous allons donc devoir mesurer le temps des requêtes côté serveur et utiliser ces mesures pour avoir une aperçu des performances de notre serveur HTTP. C’est ici que les quantiles entrent en jeux. Les quantiles vont s’appliquer sur nos mesures et servent à découper en deux partie cet ensemble de mesures, par exemple: q50 (quantile 50, souvent appelé 50 également): ceci est la médiane. Ici, 50 % des requêtes HTTP ont un temps d’exécution inférieur à la valeur associée à mon quantile, et 50 % ont un temps d’exécution supérieur. q75: 75 % des requêtes HTTP on un temps d’exécution inférieur à la valeur associée à mon quantile, et 25 % ont un temps d’exécution supérieur. q99: 99 % des requêtes HTTP on un temps d’exécution inférieur à la valeur associée à mon quantile, et 1 % ont un temps d’exécution supérieur. q1: ceci sera tout simplement la valeur maximale (la requête la plus lente) de mon serveur HTTP. Prenons par exemple ce jeu de données représentant les durées d’exécution des requêtes sur mon serveur HTTP en millisecondes: 550, 300, 1000, 2000, 450, 1300, 1400, 200, 300, 400, 900, 1200, 800, 350, 500 Nous n’avons dans cet exemple que 15 valeurs pour faciliter l’exemple mais en réalité vous pourriez réaliser ce calcul sur plusieurs milliers si nécessaire. Une des premières choses que l’on pourrait faire est de trier ces valeurs: 200, 300, 300, 350, 400, 450, 500, 550, 800, 900, 1000, 1200, 1300, 1400, 2000 Calculons maintenant le q50 sur ces valeurs (la médiane): nous voulons donc trouver la valeur au centre de notre distribution et donc avant le même nombre de valeurs avant et après cette valeur. Ceci est assez simple dans notre cas: nous avons 15 valeurs, donc la médiane sera la 7eme valeur de notre liste triée. Nous aurons en effet 6 valeurs inférieures, et 6 valeurs supérieures. La valeur du q50 est donc de 500. De la même façon, nous voulons pour le q75 trouver la valeur ayant 75 % de valeurs inférieures (soit 15 * 75/100 = 11,25 que l’on arrondira à 11), et 25 % supérieures (donc 4) La 11eme valeur de notre liste est 1000, et donc notre q75 sera égal à cette valeur. Notre jeu de données est petit donc la même procédure appliquée au q99 nous donnera la valeur 2000 qui est aussi la valeur maximale. Les quantiles sont donc bien utiles car ils permettant d’avoir rapidement une information pertinente sur par exemple des performances d’applications. Cela permet également d’énoncer des objectifs de performances clairs, comme par exemple 99 % de mes requêtes doivent s’exécuter dans un temps inférieur à une seconde. Dans des cas réels avec de grands jeux de données (des milliers de requêtes par exemple) on peut même aller jusqu’à calculer le q99.9 ou q99,99 si besoin. Histogrammes La méthode précédente pour calculer des quantiles est intéressante car elle permet de calculer exactement la valeur du quantile. Elle a également un défault: l’ensemble des valeurs doivent être disponibles pour réaliser le calcul. Cela peut être problématique lorsqu’on veut calculer des quantiles sur un grand nombre de valeurs qui devront donc être stockées de façon unitaire. Une autre solution pour calculer les quantiles est d’utiliser un histogramme dans le but de calculer une valeur approchée du quantile mais sans avoir à stocker l’ensemble des données. La première chose à faire est de choisir les intervalles (aussi appelés bucket) de notre histogramme. Nous reprendrons comme exemple ici le temps de traitement de requêtes par un serveur HTTP, avec ce temps en millisecondes. Une pratique courante dans le monde du monitoring serait d’utiliser des intervalles démarrant tous à 0 et de compter le nombre de requêtes ayant un temps d’exécution inférieur à une valeur donnée. La liste de nos mesures est dans cet exemple la même que précédemment: 200, 300, 300, 350, 400, 450, 500, 550, 800, 900, 1000, 1200, 1300, 1400, 2000 Comptons maintenant le nombre de valeurs dans différents intervalles, par exemple combien de requêtes ont un temps d’exécution dans l’intervalle 0-100, 0-200, 0-400… Table 8. Intervalles de l’histogramme Minimum (toujours 0) Maximum (inclus) Total (nombre) 0 100 0 0 200 1 0 400 5 0 600 8 0 1000 11 0 1400 14 0 2200 15 0 Infini 15 On a donc 0 requête ayant un temps d’exécution entre 0 et 100 millisecondes, 1 entre 0 et 200 millisecondes, 5 entre 0 et 400 millisecondes etc. On remarque que cette manière de faire permet de ne pas avoir à garder l’ensemble des mesures: il suffit lorsqu’une nouvelle mesure est réalisée d’incrémenter tous les intervalles nécessaires. On remarque également que le nombre de valeur dans chaque intervalle est en augmentation constante, ce qui est logique car les valeurs précédentes sont inclus dans chaque intervalle vu que l’on recompte à chaque fois le nombre de valeurs dans l’intervalle depuis 0. Le dernier intervalle est intéressant: il compte le nombre de valeurs de 0 à Infini, et contiendra donc toujours le total des valeurs. Ces informations permettent de calculer simplement une valeur approximative d’un quantile, comme par exemple le q50: Il y a 8 intervalles différents, et 15 mesures dans ces intervalles. Nous savons que nous recherchons la métrique au centre de notre distribution, et que nous voulons calculer la médiane: Nous commençons donc par réaliser le calcul suivant: 0.5 * 15 = 7.5. Nous recherchons donc où se trouve cette valeur (7.5) dans notre histogramme. On recherche l’intervalle juste après cette valeur: dans notre cas, c’est dans l’intervalle [0, 600] car sa valeur est de 8. La valeur de l’intervalle précédent étant de 5 nous pouvons en déduire que le quantile se trouve dans cet intervalle (car 5 < 7.5 < 8). Nous calculons maintenant le nombre de valeurs présentes entre cette intervalle ([0, 600]) et le précédent ([0, 400]. Nous souhaitons donc répondre à la question combien de mesures ayant une valeur entre 400 et 600 avons nous ? Le résultat est 8 - 5 et est donc égal à 3. Comme dit au début de cet article, nous allons calculer une valeur approximative pour notre quantile. Nous savons que notre quantile se trouve quelque part dans l’intervalle [400, 600] (qui couvre une durée d’exécution de 200 millisecondes), et que nous avons 3 valeurs dans cet intervalle. Rappelez vous que l’on recherche la durée théorique pour la valeur 7.5 calculée précédemment. Nous réalisons l’opération (7.5 - 5) / 3 = 0.833. Nous soustrayons ici la valeur recherchée à la valeur associée à la borne inférieure (400) de notre intervalle, que nous divisons ensuite par le nombre de valeurs dans l’intervalle (3). Nous multiplions le résultat précédent par la durée de l’intervalle: 200 * 0.833 = 166.6. Nous pouvons décrire ce calcul de la façon suivante: j’ai un intervalle de taille 200 le point recherché se trouve au pourcentage 0.833. Nous ajoutons la borne inférieure de notre intervalle à ce résultat: 400 + 166.6 = 566.6. Ceci est le résultat final et la valeur approximative de notre quantile (et la médiane dans cet exemple). Ce calcul peut aussi se résumer au fait de tracer une droite entre les coordonnées [400, 5] et [600, 8] et de rechercher la valeur associée à 7.5 sur cette droite. Notre résultat approximatif est différent du résultat réel (qui est de 500). Il faut garder en tête que ce type de calculs fonctionne mieux sur de plus gros jeux de données. Mais ce résultat nous donne dans tous les cas une idée de la performance de notre application, et c’est ce qui est le plus important. Connaître la performance de notre application à la milliseconde près n’est pas utile dans de nombreux contextes. Il vaut mieux pouvoir obtenir rapidement et simplement (en utilisant peu de capacités de stockage et de calcul) une valeur approximative mais proche de la réalité que de toujours vouloir une valeur exacte mais qui peut se révéler difficile à calculer. Pull vs Push et stockage Nos systèmes émettent donc des métriques. Pour les exploirer, il faut pouvoir les requêter et donc les stocker. Il existe deux mondes lorsqu’il s’agit pour une application de diffuser ses métriques dans le but de les stocker: le mode push et le mode pull. Le push Dans ce mode, l’application pousse les métriques vers une base de données temporelle (ou vers tout autre système de stream processing pouvant éventuellement servir à filtrer, modifier, enrichir la métrique, ou faire backpressure). Ce mode a un certain nombre d’avantages: Un endpoint unique à connaître pour les applications pour pousser les métriques: cela permet une énorme facilité en terme de configuration applicative et réseau (règles de firewalling en sortie seulement vers une destination unique). Possibilité d’ajouter facilement des composants intermédiaires comme dit précédemment pour faire du stream processing ou absorber des pics de charge en ayant un composant "buffer" entre l’application et la base de données temporelle. Haute disponibilité et scaling très facile (load balancing, déduplication du traffic entre plusieurs base de données/services cloud par exemple) Le push est plus facile à scale et beaucoup plus flexible en ajoutant un système intermédiaire de type "message broker" entre l’application et les systèmes externes. N’importe qui peut comme ça consommer les métriques sans impacter les autres. Bref, le push, c’est bon, mangez en, malheureusement c’est plus forcément "à la mode" pour la gestion de métriques. Le pull Une technologie apparue il y a quelques années a proposé une approche différente et été vite adoptée pour différentes raisons: le pull. Dans ce mode, c’est l’outil stockant les métriques qui va aller chercher (pull) les métriques en envoyant directement des requêtes à l’application. Cela veut dire que l’application doit les exposer, via HTTP dans notre cas. Une application pourrait par exemple exposer un endpoint HTTP /metrics retournant dnas cet exemple la valeur associée à l’instant T de la requête aux différentes métriques configurées (ici des compteurs): healthcheck_total{id="f7b4dba8-9626-436e-a0d2-670e862c650a", name="appclacks-website", status="failure", zone="fr-par-1"} 1 healthcheck_total{id="f7b4dba8-9626-436e-a0d2-670e862c650a", name="appclacks-website", status="success", zone="fr-par-1"} 2789 healthcheck_total{id="f7b4dba8-9626-436e-a0d2-670e862c650a", name="appclacks-website", status="failure", zone="pl-waw-1"} 1 healthcheck_total{id="f7b4dba8-9626-436e-a0d2-670e862c650a", name="appclacks-website", status="success", zone="pl-waw-1"} 2789 La base de données temporelle fera périodiquement (toutes les 30 secondes par exemple) une requête, associera à la valeur des métriques le timestamp de la requête, et stockera ça dans sa base. L’approche pull demande une énorme logique en service discovery et a une plus grosse complexité réseau que le push. Cela force également toutes vos applications à exposer un endpoint HTTP servant les métriques. Au final, les deux approches fonctionnent. Je vous recommande un autre de mes articles sur le sujet si vous voulez avoir plus de retours sur le pull vs push. Calculs côté client ou côté serveur Dernière partie de cette article, le calcul côté client ou côté serveur. J’ai expliqué précédemment comment calculer par exemple des rate, quantiles… à partir de métriques brutes. Il faut savoir que parfois, certaines librairies applicatives de gestion de métriques calculent ces valeurs pour vous. Cela veut dire que votre application ne va pas exposer à la base de données temporelle les données brutes (par exemple, la valeur d’un compteur ou les buckets d’un histogramme) mais directement un nombre de requête par seconde, es quantiles (p99, p75…). Cela peut sembler intéressant de prime abord car il n’y a aucun calcul à réaliser côté base de données temporelle, mais il y a beaucoup d’inconvénients à ne pas avoir accès aux métriques brutes: Si les données sont pré-calculées côté application, il n’est plus possible d’aggréger ensemble les métriques de plusieurs instances (replicas) d’une même application. Il est en effet commun de calculer des quantiles pour une instance d’une application, mais aussi pour toutes les instances d’une application ensemble. Cela permet de visualiser la latence par instance de l’application (utile pour voir si une instance a des caractéristiques de performances étranges par rapport aux autres) mais aussi de voir la latence globale pour toutes les instances de l’application. Ce dernier calcul ne peut se faire que en ayant accès aux métriques brutes et en faisant la somme de chaque bucket de chaque instance de l’application. Je n’ai présenté dans cet article que quelques exemples de calculs à réaliser sur les métriques. En réalité, il y en a de nombreux autres que vous retrouverez dans vos base de données temporelles et qui sont également indispensables pour monitorer vos applications. Si vous n’avez pas accès aux données brutes, ces calculs seront irréalisables. Conclusion Les métriques sont indispensables pour monitorer correctement des composants applicatifs ou d’infrastructure. Bien choisir ses métriques (type, labels…) est important et est la clé pour construire une solide plateforme d’observability.

Observability: tout ce que vous avez toujours voulu...
Je présenterai dans cet article "techno-agnostic" (aucune techno citée) les différents...
Source: mcorbin
L'architecture d'Appclacks de A à Z
Je travaille depuis un moment sur un service SaaS de monitoring: Appclacks. Je détaillerai dans cet article l’architecture complète de la plateforme, du dev à la prod. Le produit On m’a avec raison fait remarquer que les articles mi-sérieux mi-troll devenaient redondants, donc repartons sur un article un peu plus technique. Appclacks est une solution SaaS de monitoring de type "blackbox", permettant d’exécuter des health checks sur vos services web (requêtes HTTP(s), TLS, DNS, TCP, vérification d’expiration de certificats…). Les health checks sont réalisés depuis plusieurs endroits dans le monde (2 actuellement, 3 prochainement). J’essaye toujours de créer des produits dont moi même je serai utilisateur, souvent car je suis frustré par les solutions existantes. Construire un SaaS complet d’une qualité professionel en solo est aussi un bon challenge et me permet également d’expérimenter. Appclacks a plusieurs avantages par rapport à la concurrence: Outillage "state of the art": API, CLI, Provider Terraform, un opérateur Kubernetes (avec CRD maison) est en préparation (déjà fonctionnel mais le dépôt est encore privé). Pas de clickodrome. Compatible Prometheus pour récupérer les métriques des health checks: configurez votre Prometheus pour scrape l’API d’Appclacks ! Un accent fort sur l’open source. L’outil exécutant les health checks, Cabourotte, est open source. D’autres suivront. Fonctionne on premise ! Déployez Cabourotte chez vous et venez faire du service discovery via Appclacks pour monitorer vos endpoints internes, eux même gérés via CLI/Terraform/… ! Tout n’est pas encore finalisé, le produit n’est pas totalement "prod ready" mais on en est pas loin. N’hésitez pas à tester en vous inscrivant à l’alpha (lien dispo en fin de page ici). Mais comment faire tout ça de manière fiable quand on travaille sur le produit en mode "side project" ? L’architecture On voit qu’on a besoin ici de plusieurs choses: Une API comme point d’entrée à toutes les actions sur la plateforme et une base de données pour stocker les informations. Un composants pour stocker les métriques des health checks côté Appclacks et les rendre disponibles aux clients. Ce sera ici Prometheus. Pouvoir déployer des instances de Cabourotte, servant à exécuter des health checks, un peu partout dans le monde si besoin. Chaque instance doit également savoir quel sous ensemble de health check à exécuter pour pouvoir scale la plateforme horizontalement. J’utilise aujourd’hui exclusivement Scaleway (la partie Cloud) pour l’hébergement. Voici un schema d’architecture global: Je vais maintenant expliquer l’architecture en détail. L’infrastructure Provisioning Toute l’infrastructure Scaleway est déployée via Terraform. Le provider Scaleway est de qualité, je n’ai pas eu de problèmes avec. L’API, Prometheus, et Cabourotte sont déployés sur des machines virtuelles dédiées. Pour Cabourotte, le déploiement se fait en France et en Pologne pour, comme dit précédemment, avoir des health checks exécutés depuis différents endroits (chaque health check configuré sur Appclacks sera exécuté de ces deux localisations). Quoi ? mcorbin n’utilise pas Kubernetes malgré sa propagande importante sur le sujet ? On en reparlera en fin d’article. J’utilise ici beaucoup les offres managés de Scaleway. Pour PostgreSQL, pour envoyer des emails, et le nouveau service cockpit qui me sert de stockage long terme pour Prometheus et pour pouvoir consulter mes métriques (et les métriques Scaleway) via Grafana. Rien à redire sur ces services managés pour le moment, j’en suis satisfait. Cockpit avait quelques instabilités pendant la beta mais maintenant ça a l’air de marcher beaucoup mieux. C’est très agréable d’avoir accès à toutes les métriques internes de Scaleway via Grafana, ceux qui ont déjà utilisé AWS Cloudwatch comprennent la douleur qu’est la gestion des métriques chez certains cloud providers. Bravo Scaleway pour ce produit. Scaleway a par contre plusieurs limitations persistantes pénibles, comme le fait que les instances dans un réseau privé avec DHCP puissent changer d’IP (incroyable sur le cloud) ou sur la gestion des security groups (pas possible d’utiliser un autre security group en source/target par exemple). Mais je fais avec. Déploiement La configuration des différentes machines virtuelles et softwares (que ce soit ceux cités précédemment ou des outils comme node_exporter) se fait avec Ansible, lancé depuis mon poste^^. Aucun rôle, just des playbooks par composants pour simplifier la maintenance à l’extrême. Par contre Ansible c’est lent sans la fibre ;'( L’API L’API, le coeur du réacteur, est codée en Go (avec Echo comme framework HTTP) et reçoit toutes les requêtes vers Appclacks. C’est aussi elle qui gère l’authentification. Elle est stateless et peut donc scale horizontalement. D’ailleurs, concernant l’authentification, j’ai fait au plus simple: elle se fait par token (sauf quelques endpoints d’administrations comme changer son mot de passe, ou bien créer un token ^^) et il est possible d’attacher à un token la liste des appels à autoriser. Cela permet par exemple, si vous utilisez comme décrit précédemment Prometheus en interne pour scrape les métriques fournies par Appclacks, de ne permettre que l’appel GetHealthchecksMetrics sur le token fourni à Prometheus. Bref, l’API reçoit des requêtes et intéragit avec PostgreSQL pour la gestion des données. Lorsque vous créez un health check, récupérez les résultats des health checks, listez vos tokens… on passe par l’API et par PostgreSQL. J’ai choisi dès le début d’extraire tous les types (struct Golang) utilisés par l’API public dans un dépôt Github à part (go-types) ce qui me permet de les réutiliser directement dans le client Golang (utilisé notamment par la CLI, le provider Terraform, l’operator Kubernetes). Vous pouvez remarquer sur les types de l’API les différents tags json, query… utilisés ensuite par Echo pour désérialiser les requêtes, et les tags validate utilisés pour la vérification des données (via la lib Go validator). J’aime cette approche avec les types publics. Le prochain chantier est d’ailleurs de générer la spec OpenAPI depuis ces types, mais l’écosystème Golang sur le sujet laisse à désirer. Le code est organisé dans dans les grandes lignes en mode "DDD", ce qui facilite grandement son organisation et l’écriture de tests (me permettant de mock facilement mes repositories si nécessaire par exemple). Les migrations de base de données sont exécutées au démarrage de l’application via une lib Go. L’application log dans stdout en JSON (avec le logger zap) et expose un certain nombre de métriques (latency/rate/error des requêtes et réponses http, de certains clients…) au format Prometheus. Je n’ai pas encore de traces car aucun endroit pour les stockers, mais j’espère en avoir à terme. Emails Le service transactional email de Scaleway est top. J’ai configuré une clé d’API avec leur nouveau système d’IAM n’autorisant que l’envoi d’email. L’API utilise ensuite la lib net/smtp de Golang avec les creds de Scaleway et tout fonctionne comme prévu. Peut être que je ferai un article sur le sujet à l’occasion. Cabourotte Un défi était ensuite de configurer les différentes instances de Cabourotte exécutant les health checks des utilisateurs. J’avais un cahier des charges assez strict: Lorsqu’un utilisateur crée un health check, il devait commencer à s’exécuter très rapidement Cabourotte doit pouvoir scale horizontalement: je veux pouvoir faire exécuter les health checks des clients depuis plusieurs instances au sein d’une même région. Par exemple, mais en gardant la garantie qu’un health check n’est exécuté qu’une fois par région. Que la solution puisse fonctionner ensuite sur du multi région pour le support de multiples PoP, sur plusieurs cloud providers si besoin. Voici la solution retenue. Prober ID Chaque instance de Cabourotte dans une région donnée (France par exemple) se voit attribuer un ID (un peu comme un statefulset Kubernetes): 0, 1, 2… L’API connait ensuite le nombre total d’instances de Cabourotte par région: 3 par exemple. Détail important: bien qu’utilisant des UUID comme clés primaires, chaque health check créé sur Appclacks se voit attribuer un autre ID aléatoire au format integer. C’est important pour la suite. Si nous avons 1000 health checks à exécuter dans une région, nous voulons une répartition à peu près stable entre instances, proche de 333.. Cabourotte supporte déjà du service discovery, c’est ce qui permet de le faire tourner chez vous mais branché sur Appclacks. J’ai donc réutilisé ce système pour assigner les health checks aux instances de Cabourotte gérées par le SaaS. Chaque instance est configurée pour récupérer via un endpoint API interne (path/credentials spécifiques) les probes à exécuter. Le endpoint est de type /probers/discovery?prober-id=1, on voit que le prober ID décrit précédemment est passé en paramètre. Que fait donc l’API à partir de ça ? Une simple requête SQL similaire à "SELECT * FROM healthcheck WHERE random_id%<nombre_total_prober>=<prober_id> AND enabled=true": On sélectionne les health checks Mais seulement ceux où le résultat du modulo entre le nombre total d’instances Cabourotte dans la région et l’entier aléatoire assigné à chaque health check est égal à l’ID du prober (= de Cabourotte) demandant sa liste de health check à exécuter. On ne garde que les health checks qui sont été activés par l’utilisateur (Appclacks supporte la désactivation d’un health check sans le supprimer si nécessaire). Prenons un exemple avec 3 health checks ayant comme ID aléatoire 46, 190, 27. Si nous n’avons qu’une seule instance Cabourotte par région, voici ce que ça donne: Healthcheck random ID Prober ID Placement 46 0 46 mod 1 = 0 ? Vrai 190 0 190 mod 1 = 0 ? Vrai 27 0 27 mod 1 = 0 ? Vrai Si l’instance Cabourotte 0 demande à l’API donne moi mes health checks à exécuter, tous les health checks seront retournés. Normal, on a qu’une instance de Cabourotte qui tourne. Voyons maintenant le comportement si nous avons deux instances de Cabourotte. On s’attend à ce que les health checks soient répartis entre ces instances. Rappelez vous du calcul effectué (WHERE random_id%<nombre_total_prober>=<prober_id>). Ici le nombre de total de prober est 2. Healthcheck random ID Prober ID Placement 46 0 46 mod 2 = 0 ? Vrai 46 1 46 mod 2 = 1 ? Faux 190 0 190 mod 2 = 0 ? Vrai 190 1 190 mod 2 = 1 ? Faux 27 0 27 mod 2 = 1 ? Faux 27 1 27 mod 2 = 1 ? Vrai Ici, le prober 0 gèrera les health checks 46 et 190, le prober 1 le health check 27. On voit que grâce au modulo chaque instance récupère seulement un sous ensemble de health checks à exécuter. Si un nouveau health check est créé, celui ci sera automatiquement démarré au prochain "refresh" du service discovery de l’instance Cabourotte correspondante. Scale les probers est donc facile: il me suffit de rajouter des instances et de modifeir le nombre total d’instance dans l’API pour que les health checks se reconfigurent de manière équitable entre ces instances, et surtout la solution a le mérite d’être très simple. Elle est sûrement améliorable notamment pour éviter un déplacement important de health checks en cas d’ajout d’une instance Cabourotte (consistent hashing) mais c’est vraiment de l’optimisation prématurée surtout vu ma volumétrie actuelle. Je n’ai également aucune idée de la scalabilité de faire du modulo directement dans postgreSQL mais j’ai le temps de voir venir. Cabourotte pousse ensuite le résultat des health checks dans l’API, qui sont ensuites récupérables par les utilisateurs. Métriques Comme dit en début d’article, une instance Prometheus peut directement scrape l’API d’Appclacks pour récupérer les métriques des health checks exécutés par le SaaS. Il faut donc stocker ces métriques et les rendre récupérable. Prometheus récupère directement les métriques de Cabourotte sur le endpoint /metrics des prober. Lorsque les instances Prometheus des utilisateurs (ou quand appclacks healthcheck metrics get est exécuté) ciblent l’API d’Appclacks, cette dernière se contente de récupérer dans Prometheus les métriques de l’organisation les demandant. Oui, une simple query Prom du type (sum by (name, name, le, zone, id) (healthcheck_duration_seconds_bucket{owner="%s"})) or (sum by (name, name, status, zone, id) (healthcheck_total{owner="%s"})), où owner est un label indiquant l’organisation envoyant la requête (déduite depuis le token d’authentification). Les données sont ensuites renvoyées au format texte de Prometheus en réponse. Je n’ai aujourd’hui qu’une instance de Prometheus mais je prévois d’en mettre rapidement une deuxième, en actif/actif et avec un load balancing pas trop bête côté client, dans le but d’avoir une forte tolérance aux pannes sur ce composant. Comme dit précédemment, tout part également en remote write dans Scaleway Cockpit (métriques systèmes et applicatives incluses). Service discovery Une des propriétés intéressantes lorsqu’on branche Cabourotte installé on premise au service discovery d’Appclacks est la possibilité de passer des labels pour sélectionner les health checks à récupérer (et donc à exécuter) par Cabourotte. En effet, comme la majorité des ressources d’Appclacks, il est possible d’attacher à leurs créations des labels (de simples clés/valeurs). Elles sont stockées dans PostgreSQL au format jsonb (labels jsonb dans la table SQL). Ici pas de magie malheureusement, pour l’instant je me contente d’itérer de manière un peu degueulasse dans le code pour ne retourner que les "match" pour des labels donnés. Site vitrine J’utilise Dorik pour le site vitrine appclacks.com[appclacks.com]. Pour les pas doués du CSS comme moi c’est très bien et j’en suis content. Evolutions futures Résultats sur S3 Tous les résultats des health checks sont stockés pour l’instant dans PostgreSQL dans une table nommée healthcheck_result. Ca prend un max de place. Prenons des chiffres fictifs: 1000 healthchecks 3 régions Exécutés toutes les 30 secondes. 1000*3*2 = 6000 insertions par minute, donc 8640000 par jour. Alors bien sûr ça passe, Postgres n’a aucun soucis à gérer ça. Mais je prévois à moyen terme d’historiser les résultats dans S3 et de ne garder en base que quelques semaines maximum (voir moins) de rétention pour économiser du stockage et éviter que cette table devienne trop grosse. Du sharding serait également une possibilité Kubernetes Gérer des machines virtuelles est pénible. J’aimerai migrer toute l’infrastructure (sauf les instances de Cabourotte déployées de manière autonomes) sur Kubernetes. Je n’utilise aujourd’hui pas l’offre Kapsule de Scaleway car elle ne répond pas à mes exigences pour de la production (pas de réseau privé, pas de support des security groups ce qui la rend de facto inutilisable dans un contexte d’infra as code, kubelet exposé sur internet). Quand Scaleway aura amélioré son produit je basculerai dessus: cela me permettra de gérer beaucoup plus simplement qu’aujourd’hui l’infrastructure, et de manière plus fiable. Release de l’operator Kubernetes Il est prêt, plus qu’à faire la CI et la documentation :) Kube builder a été utilisé pour générer tout le boilerplate. Browser monitoring J’aimerai rajouter à moyen terme du monitoring via démarrage d’un vrai navigateur web (type Selenium) pour etoffer l’offre. C’est aussi pour ça que je veux basculer sur Kubernetes: cela me donnerait une plate-forme de base pour gérer des cronjobs Selenium de manière très simple par exemple. Interface web Je suis une quiche en HTML/CSS/Javascript et je prévois de le rester. Je préfère me focus sur du tooling "state of the art" comme dit précédemment. Conclusion Monter tout ça était (et est toujours) bien fun, on verra où ça va dans les années à venir :)

L'architecture d'Appclacks de A à Z
Je travaille depuis un moment sur un service SaaS de monitoring: Appclacks. Je...
Source: mcorbin
Le Tech radar de 2023
Quelles sont les technologies en vogue ou à éviter en 2023 ? Infrastructure management, cloud provider, langages de prog, Observability… Nos experts vous donnent leurs avis dans cet article écrit à la va vite ! Le radar Lien direct Infra management ADOPT Terraform est forcément indispensable en 2023. Bon, en réalité l’outil ne scale pas, il vaut mieux juste l’utiliser pour bootstrap vos clusters Kubernetes mais c’est le meilleur outil pour faire cela. Cloud init fait toujours le café pour faire 2-3 trucs dégueulasses (qu’on sait généralement pas trop où mettre) au boot d’une machine, un indispensable aujourd’hui. Ansible, à réserver pour les rares choses qui ne tournent pas sur Kubernetes (type certains VPN) et où une machine virtuelle est nécessaire. Faites des CLI internes, c’est cool TRIAL Nix: la hype du moment, c’est bien cool pour gérer des machines de manière déclarative, par contre ne pas utiliser si vous n’avez pas la fibre chez vous (ou alors faut pas avoir à installer un paquet en urgence à 3H du matin parce que l’astreinte a sonnée). HOLD Puppet: c’est fait en Ruby, tout est dit. Shell scripts: l’interdiction du shell pour l’administration système est en bonne voie, ne pariez pas là dessus. Procedures in PDFs: un classique de nos grands groupes, on évite également. Cloud providers ADOPT AWS: Nobody Ever Got Fired for Buying AWS: un classique. Pas forcément le cloud le plus user friendly (IAM, VPC peering…) mais ça marche bien, c’est très stable, et le catalogue de produit vous permettra de faire tourner les prods les plus modernes dessus. Exoscale: le meilleur cloud provider européen, what else ? Netlify: super choix pour du site statique, avec un peu de FaaS autour si besoin. Cloudflare: Le king du CDN/Anti DDoS et qui innove en permanence (cloudflare workers, R2…) TRIAL Scaleway: encore pas mal de produits moyens (par exemple l’offre Kubernetes) mais ça vaut dans le bon sens. Leurs dernières sorties (Observability, Transactional email…) sont top. On y croit. Scalingo: le PaaS qui monte. On attend maintenant quelques features avancées autour du réseau et la fameuse certification SecNumCloud, à suivre de près. Openstack: le boss du cloud privé. HOLD OVH: pour du bare metal avec le vrack, ça passe. Pour la partie cloud on oublie (offre disparate, outillage terrible…). Clever Cloud: beaucoup trop limité en fonctionnalité pour le moment pour pouvoir être utilisé pour de nombreux projets. No Cloud: aucune excuse pour ne plus faire du cloud en 2023 (j’inclus le cloud privé). CI & CD ADOPT ArgoCD: le meilleur outil de l’écosystème Kubernetes, un indispensable qui fait le café. Vos développeurs adoreront redémarrer leurs applications via l’interface quand ça mem leak ! Github actions: simple et efficace go race -race ./… && golangci-lint run && docker build && docker push: en réalité, est ce qu’on a vraiment besoin de plus dans une CI ? Arrêtons avec la complexité accidentelle. TRIAL Jenkins: les papys font de la résistance ! Avec du job DSL et du Jenkins pipeline partout ça fait le boulot. Flux: je le mets pour faire plaisir aux copains mais l’UI d’ArgoCD est trop bonne pour que Flux puisse tenir la comparaison, désolé ! HOLD Gitlab CI: qui s’est dit que c’était une bonne idée de faire un système de CI turing complet avec du YAML qui embarque du bash inline ? Pour lancer du one-liner, pourquoi pas, mais forcez vous à l’utilisez aucune autre fonctionnalités. Programming languages ADOPT Clojure: fonctionnel, persistent & immutable data structure, super écosystème (merci la JVM): coder en Clojure augmente votre productivité, c’est prouvé ! Golang: le langage des dégueulasse mais l’outillage, la stdlib, l’écosystème, les perfs et le fait que ça build en binaire statique fait de lui un très bon choix pour votre prochain projet. Java: je l’ai déjà dit, la JVM est probablement la meilleure plateforme pour écrire des apps en 2023 (et bientôt loom !). Les dernières évolutions de Java en font un super langage. Faites juste attention à ne pas utiliser les bloatwares comme Spring, Hibernate etc…. TRIAL Kotlin: Java avec quelques features en plus, indispensable pour le dev mobile. Python: franchement il était pas loin de passer dans la catégorie suivante rien qu’à cause des systèmes de build qui changent tous les deux jours. Mais si vous arrivez à build votre projet, ça fait le job, surtout en ajoutant des type hints. Zig: pas stable mais prometteur. Rust: difficile d’ignorer Rust en ce moment même si j’ai jamais vu un langage aussi usine à gaz/compliqué/si difficile à lire. A garder pour remplacer C mais c’est tout. NUKE IT FROM ORBIT (only way to be sure) Une catégorie spéciale ici ! Bash: la section Shell scripts a tout dit, à faire disparaître en urgence de votre production. Ruby: un langage où le moindre serveur web recevant 3 requêtes par secondes vous demandera une quantité astronomique de ressource, le tout propulsé par un framework (rails) inventé par le malin ! Perl: on a tous cru dans perl 6 (ou pas). C/C++: même les experts mondiaux qui en font depuis 40 ans arrivent à faire des use after free, heap overflow… dans des codes type sudo/openssl. Donc inaccessible pour les moldus comme nous. k8s infrastructure ADOPT KEDA: indispensable pour l’autoscaling depuis de nombreuses sources, comme par exemple des métriques Prometheus. Cluster autoscaler: la base pour autoscale les noeuds des clusters kube. Horizontal pod autoscaler: la base pour scale les pods, à coupler avec KEDA. Karpenter: le futur d’AWS EKS, qui simplifiera grandement la gestion de vos noeuds Kubernetes. TRIAL Cluster API: a regarder si vous devez vous diriger si vous devez gérer de nombreux clusters kubernetes chez vous. Kubevirt: gérer ses machines virtuelles de manière déclarative via Kubernetes, pourquoi pas ? HOLD Crossplane: l’idée est bonne mais au final ça fait un peu usine à gaz. Installer le provider AWS avec ses > 900 CRDs causera également quelques soucis à vos clusters. Service Mesh: une complexité démentielle pour un gain qu’on cherche encore. On oublie pour le moment sauf cas particuliers. MAPE K: peut être le futur de la gestion d’infrastructure mais on peine à trouver des implémentations. Load balancers ADOPT Traefik: la star pour faire de l’ingress sur Kubernetes. Stable même avec des milliers d’ingress, bonnes perfs, des métriques sympa par défaut… à utiliser. HAproxy: le choix par défaut pour du load balancing hors Kubernetes. TRIAL Envoy: le challenger, tout le monde en parle mais par contre personne ne l’a jamais vu en production. Caddy: parfait pour servir des fichiers statiques. HOLD nginx: qui comprend vraiment le format de configuration de l’outil ? F5: $$$ Observability ADOPT Opentelemetry: surtout pour les traces, c’est le futur ! Mettez en partout, remplacez vos logs par des events attachés aux spans ! Prometheus: J’ai jamais aimé le pull mais faut se rendre à l’évidence, ça marche et l’écosystème est là. Loki: Parfait pour vos logs surtout si vous voulez pas payer trop cher vu que tout part sur s3. Elasticsearch: le gestionnaire de logs historique, ça marche toujours bien surtout on premise. Tempo: Probablement l’outil le plus intéressant pour stocker vos traces. Grafana: L’outil de base (le seul ?) pour vos dashboards. Thanos: Simple et à installer et vous permettra de stocker vos métriques Prometheus sur le long terme. TRIAL Victoria metrics: Prometheus mais avec de meilleures performances, à tester ! Timescale: stocker vos métriques dans postgresql, pourquoi pas ? A voir selon la volumétrie que vous avez. Mimir: Comme Thanos, à tester notamment en cas de forte volumétrie. Appclacks: le futur du blackbox monitoring il parait. HOLD statsd: plus d’actualité cloudwatch: UX terrible à tous les niveaux Nagios & fork: le paradigme du monitoring a complètement changé depuis cette époque. Databases ADOPT Postgres: ça fait tout (SQL, json, full text search, timeseries comme montré précédemment…), le projet évolue à toute vitesse, un très bon choix ! MariaDB: marche très bien, rien à redire. MongoDB: une base au format document, ça peut servir. MongoDB Atlas est pour moi la meilleure offre DB cloud du marché actuellement en terme d’UX/fonctionnalités. Cassandra: la meilleure base orientée colonne, super notamment pour faire du multi data center facilement. Aurora: Propriétaire mais ça scale ! S3: Utiliser S3 comme base de données, de plus en plus fréquent pour les logs, métriques… peut être bientôt pour le reste également ? TRIAL MySQL: Toujours très présent donc doit être cité RocksDB: Ca a de grosses perfs et c’est fun ! HOLD Oracle: vous savez pourquoi Microservices ADOPT Pizza teams: On aime quand les équipes ont un ownership fort sur leurs services et domaines. Contract testing: obligatoire quand on fait du microservice ? Feature flags: Release early, release often: les features flags sont l’outil de base pour réaliser cela. TRIAL Keep the good old monolith: et pourquoi pas si le code est bien conçu ? DDD: permet d’éviter le couplage, facilite les tests… un bon outil. HOLD Distributed monolith: un classique on-demand environments: une mauvaise idée même si certains essayent de vous convaincre du contraire :D Bus and queues ADOPT Kafka: meilleur outil depuis des années pour faire des architectures orientées événements, du stream processing, faire buffer pour vos logs/métriques… Il s’opère également très bien et est dispo partout en mode SaaS. SQS: simple d’utilisation et fait le job quand on a juste besoin d’une queue de message. TRIAL NATS: Le streaming "cloud native", à voir où ça va. RabbitMQ: Bien mais pénible à opérer surtout si vous ne savez pas lire les stacktraces Erlang. HOLD Sidekiq: c’est du Ruby donc ça partage ses caractéristiques. Lean ADOPT Lean: on ne peut plus s’en passer. Des tonnes de pratiques utiles au quotidien. Self service/product approach: On fournit des services internes de qualité en écoutant nos utilisateurs et en les rendant autonomes au quotidien. J’en parle ici. Dev oncall on production: you build it, you run it. La qualité augmente bizarrement très vite quand on reçoit les alertes de ses propres services. Blameless incidents: Les incidents c’est de la faute de tout le monde et on ensemble bosse pour que ça se ne reproduire plus. No backlog: Un backlog, pourquoi faire ? De toute façon au bout de 3 mois votre backlog ne sera plus valide. Autant ne plus en avoir. TRIAL http://programming-motherfucker.com/: tout est sur le lien. HOLD Certifications: ça sert à quoi ? ITIL & SAFE: bien mais seulement pour les consultants spécialisés. Agile & Scrum: on veut du Lean. Security ADOPT IAM: la base de la base, notamment sur le cloud. Dommage que ce soit pas dispo sur ce nombreux acteurs Français. RBAC: si vous faites du Kubernetes, vous allez y passer. Kyverno: parfait pour valider vos manifests Kubernetes, faire de l’audit, et ajouter une couche supplémentaire de sécurité. TRIAL OPA: On préfère Kyverno ici mais ça fait aussi le job, notamment si vous maitrisez Rego. HOLD Databases exposed on internet: mauvaise idée il y a 15 ans, mauvaise idée aujourd’hui. Conclusion A vous de faire vos choix !

Le Tech radar de 2023
Quelles sont les technologies en vogue ou à éviter en 2023 ? Infrastructure management,...
Source: mcorbin
Kubernetes multi cluster et multi région en GitOps avec ArgoCD
Je présenterai dans cet article comment gérer les applications et ressources déployées sur plusieurs clusters Kubernetes, déployés dans plusieurs datacenters, avec ArgoCD. ArgoCD ArgoCD est un outil de déploiement continu pour Kubernetes, permettant de configurer en GitOps vos clusters. Cet outil est capable de gérer tout type de ressources Kubernetes (sans aucune limitations, CRD incluses). Il supporte également très bien l’écosystème Kubernetes standard pour gérer les manifests de déploiements (Helm, Kustomize… mais étendre l’outil avec ses propres outils est également possible), et dispose égalemement d’une très bonne interface utilisateur. ArgoCD est l’outil que je recommande le plus aujourd’hui pour les utilisateurs de Kubernetes. Nous commencerons dans cet article par expliquer rapidement le fonctionnement d’ArgoCD, puis nous déploierons 3 clusters Kubernetes sur Exoscale. SKS, l’offre Kubernetes as a service d’Exoscale, est actuellement la meilleure offre "managed Kubernetes" européenne, meilleure en tout point de vue par rapport à celles de Scaleway et OVH. C’est aussi celle la plus facile et rapide à déployer et à administrer. Mais il serait tout à fait possible de réaliser l’architecture de ce tutoriel sur un autre cloud provider, on premise, ou même en mode "multi cloud" pour une meilleure tolérance aux pannes ! C’est une des forces de Kubernetes: c’est disponible partout. Un cluster sera utilisé pour déployer ArgoCD. Les deux autres seront eux gérés par l’instance ArgoCD installée sur le premier cluster. Chaque cluster sera déployé dans un datacenter séparé, dans des pays différents. Je trouve cette approche intéressante pour plusieurs raisons: ArgoCD pilote l’ensemble des clusters et permet donc d’avoir une gestion de configuration de ces clusters centralisée, notamment grâce aux ApplicationSet que je présenterai plus loin. Toutes les ressources déployées dans tous les clusters seront par exemple visibles dans l’interface d’ArgoCD. Le cluster où ArgoCD tourne peut être très simple (avec seulement ArgoCD et pour un cas réel de production quelques outils de monitoring). Cela donne un cluster facile à administrer, facile à reconstruire, et peu coûteux. Cela peut avoir l’air d’un Single Point of Failure mais perdre ArgoCD n’est pas très grave: toutes les applications continuent de tourner, seulement les mises à jour seront impossibles. Si le cluster est complètement reconstructible en quelques minutes (ce qui est largement faisable sur le cloud), ArgoCD n’est plus vraiment un SPOF. Vous pouvez même si vous le souhaitez avoir un cluster Kubernetes passif avec ArgoCD préinstallé mais désactivé (0 replica), pour pouvoir à tout moment le redémarrer en cas de perte du cluster principal. Votre source de vérité sera de toute façon Git, le cluster ArgoCD sera complètement stateless. On voit sur cette image que seul le cluster Kubernetes à Genève aura ArgoCD d’installé. Il pilotera les deux autres clusters installés dans deux autres datacenters (Zurich et Vienne), et pourra également être utilisé pour se configure soit même. Avec cette architecture, des load balancers devant vos clusters Kubernetes et du DNS, vous pouvez facilement avoir des applications fortement tolérantes aux pannes, en actif/passif entre régions et clouds ou même en actif/actif si votre architecture le permet. Je vais maintenant présenter très brièvement les différentes CRD d’ArgoCD. Pour plus d’informations, lisez la documentation de l’outil. Project La première ressource importante d’ArgoCD est AppProject. Un AppProject est une abstraction permettant de regrouper des Applications (présentées juste après) ensemble. Les AppProject permettent d’exprimer des choses comme "dans le namespace monitoring de Kubernetes, je n’ai le droit que de déployer que les applications venant du dépot gît dont l’URL est github.com/mon-org/monitoring.git, et j’autorise seulement ces types de ressources Kubernetes (deployment, service, ingress…) à être déployés". Un exemple: apiVersion: argoproj.io/v1alpha1 kind: AppProject metadata: name: example-project namespace: argocd spec: sourceRepos: - '*' destinations: - namespace: '*' server: '*' clusterResourceWhitelist: - group: '*' kind: '*' Ce projet appelé example-project autorise tout. SourceRepos, la liste des dépôts Git autorisés à être déployés pour ce projet, contient *. On pourrait la remplacer comme dit précédemment par les addresses des dépôts autorisés à être déployés. destinations permet de configurer dans quelles namespaces et servers (clusters kubernetes) les applications peuvent être déployées. Enfin, clusterResourceWhitelist permet de spécifier quelles types de ressources Kubernetes sont autorisées. Application Une Application représente un groupe de ressources Kubernetes gérées par ArgoCD: apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: guestbook namespace: argocd spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook destination: server: https://kubernetes.default.svc namespace: guestbook Cette application s’appelle guestbook. Le paramètre source dans la spec permet de référencer un dépôt Git contenant les fichiers Kubernetes à déployer. Le paramètre destination indique sur quel cluster les déployer, et dans quel namespace. Ici c’est le cluster où ArgoCD est déployé qui est ciblé. Ici, ArgoCD récupérera les fichiers présents dans https://github.com/argoproj/argocd-example-apps, dans le dossier guestbook (le paramètre path de la source), et les déploiera sur le cluster Kubernetes local (où ArgoCD tourne, accessible sur https://kubernetes.default.svc) et dans le namespace guestbook Dans cet exemples de simples fichiers sont déployés. La ressource Application supporte aussi des outils comme Helm, ou Kustomize. ApplicationSet On voit que la partie destination permet déjà de déployer sur plusieurs clusters Kubernetes depuis une même instance d’ArgoCD. Cela est pratique mais a un désavantage: si vous souhaitez déployer la même ressource sur 3 clusters, vous allez devoir définir 3 applications, chacunes avec une destination différente. C’est là que les ApplicationSet interviennent. Voici un exemple d’ApplicationSet: apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: guestbook namespace: argocd spec: generators: - clusters: {} template: metadata: name: '{{name}}-guestbook' spec: project: "default" source: repoURL: https://github.com/argoproj/argocd-example-apps/ targetRevision: HEAD path: guestbook destination: server: '{{server}}' namespace: guestbook On voit qu’une ApplicationSet est assez similaire à une Application: la partie template contient d’ailleurs la définition d’une Application. L’ApplicationSet va en fait être utilisée pour générer des ressources de type Application en fonction de générateurs (generators dans la configuration). Dans cet exemple, on utilise le generateur clusters qui génère par défaut une Application ArgoCD pour chaque cluster configuré dans ArgoCD (la partie suivante montrera comment configurer des clusters). La ressource ApplicationSet permet également de templatiser en fonction du générateur utilisé la configuration des Applications générées. Par exemple ici: La variable {{name}} contiendra le nom du cluster cible de l’application générée. La variable {{server}} contiendra l’URL du cluster cible. Si j’ai par exemple deux clusters configurés dans ArgoCD, l’un appelé cluster1 ayant pour URL cluster1.com, et l’autre appelé cluster2 ayant pour URL cluster2.com, cette ApplicationSet me génèrera 2 applications dont les noms seront cluster1-guestbook et cluster2-guestbook et dont les destinations seront les URL correspondantes. Il existe de très nombreux générateurs, permettant d’automatiser la génération d’applications ArgoCD en se basant sur de nombreux critères (clusters, Pull Requests ouvertes sur un projet Git, en fonction de fichiers, en fonction d’une simple liste de variables…). Les ApplicationSet sont un très bon outil pour éviter de passer son temps à réécrire plusieurs fois des Applications quasiment identiques. Les ApplicationSet autorisent aussi depuis peu des rollout progressifs, pour mettre à jour des clusters un par un dans un ordre pré-déterminé. Cas pratique Création des clusters Comme dit précédemment je vais utiliser Exoscale pour ce tutoriel. Vous pouvez reproduire l’exercice sur un autre cloud également avec quelques variations mais je vous recommande vraiment d’essayer l’offre d’Exoscale qui est de bonne qualité, et qui permet d’avoir dans son mode starter un contrôle plane Kubernetes gratuit. La documentation d’Exoscale vous explique comment créer un cluster grâce à sa CLI. Vous aurez besoin aussi de kubectl pour interagir avec Kubernetes. Le script suivant vous permettra de créer les 3 clusters comme montré dans la première image (dans 3 régions différentes). A noter qu’en production il vaudrait mieux créer un security group par cluster pour plus de sécurité, mais dans ce contexte de POC ce n’est pas trop grave de partager le même: #!/bin/bash set -e ## Création des règles réseaux exo compute security-group create sks-argo-tuto exo compute security-group rule add sks-argo-tuto --description "NodePort services" --protocol tcp --network 0.0.0.0/0 --port 30000-32767 exo compute security-group rule add sks-argo-tuto --description "SKS kubelet" --protocol tcp --port 10250 --security-group sks-argo-tuto exo compute security-group rule add sks-argo-tuto --description "Calico traffic" --protocol udp --port 4789 --security-group sks-argo-tuto ## Création des 3 clusters ### Premier cluster dans la région ch-gva-2 qui hébergera ArgoCD exo compute sks create argocd --zone ch-gva-2 --service-level starter --nodepool-name argocd --nodepool-instance-prefix argocd --nodepool-size 3 --nodepool-security-group sks-argo-tuto ### Second cluster dans la région ch-zrh-1 (anciennement appelée ch-dk-2) exo compute sks create zrh1 --zone ch-dk-2 --service-level starter --nodepool-name zrh1 --nodepool-instance-prefix zrh1 --nodepool-size 3 --nodepool-security-group sks-argo-tuto ### Troisième cluster dans la région at-vie-1 exo compute sks create vie1 --zone at-vie-1 --service-level starter --nodepool-name vie1 --nodepool-instance-prefix vie1 --nodepool-size 3 --nodepool-security-group sks-argo-tuto Récupération des kubeconfig Nous allons maintenant récupérer des fichiers Kubeconfig root pour chaque cluster dans le but de pouvoir intéragir avec eux via kubectl: exo compute sks kubeconfig argocd kube-admin --zone ch-gva-2 --group system:masters > argocd.kubeconfig exo compute sks kubeconfig zrh1 kube-admin --zone ch-dk-2 --group system:masters > zrh1.kubeconfig exo compute sks kubeconfig vie1 kube-admin --zone at-vie-1 --group system:masters > vie1.kubeconfig Vous devriez maintenant pouvoir par exemple lister les noeuds de vos clusters, avec kubectl --kubeconfig <kubeconfig-file> get nodes, par exemple kubectl --kubeconfig argocd.kubeconfig get nodes. Installation d’ArgoCD Nous allons maintenant installer ArgoCD sur le cluster Kubernetes de la zone ch-gva-2: kubectl --kubeconfig argocd.kubeconfig create namespace argocd kubectl --kubeconfig argocd.kubeconfig apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml Pour ce tutoriel nous désactivons pour plus de facilité l’authentification d’ArgoCD. ArgoCD supporte une gestion fine des utilisateurs et permissions (et supporte également des protocoles comme OIDC), donc n’hésitez pas à jeter un oeil à la documentation officielle. Exécutez tout d’abord kubectl --kubeconfig argocd.kubeconfig -n argocd edit configmap argocd-cm pour désactiver l’authentification. Rajoutez le block suivant dans le YAML affiché, sauvegardez et fermez votre éditeur: data: users.anonymous.enabled: "true" Faisons la même chose pour une seconde configmap via kubectl --kubeconfig argocd.kubeconfig -n argocd edit configmap argocd-rbac-cm, pour rendre admin l’utilisateur par défaut. data: policy.default: role:admin Rappel: ne faites pas ça sur un "vrai" cluster, ou même un cluster de test où ArgoCD est exposé sur internet ! Je ne vais pas configurer d’ingress controller dans ce tutoriel l’accès à ArgoCD se fera via kubectl port-forward. Lancez kubectl --kubeconfig argocd.kubeconfig port-forward svc/argocd-server -n argocd 8080:443 et ouvrez votre navigateur sur localhost:8080: une fois le certificat auto signé accepté vous devriez pouvoir accéder à l’interface d’ArgoCD. Configuration des clusters ArgoCD permet par défaut de déployer des applications sur le cluster où il est installé mais pas ailleurs. Nous allons donc reconfigurer les clusters dans ArgoCD. Cela se fait en créant un secret Kubernetes par cluster. Nous utiliserons ici directement les certificats contenus dans les kubeconfig générés précédemment pour s’authentifier aux clusters distants (pour le cluster local, l’authentification se fera par défaut via le service account d’ArgoCD). Voici la configuration à appliquer, en remplaçant les valeurs server et les certificats attendus par chaque cluster par ce que vous avez dans vos kubeconfig: --- apiVersion: v1 kind: Secret metadata: name: cluster-gva2 namespace: argocd labels: argocd.argoproj.io/secret-type: cluster type: Opaque stringData: name: gva2 server: https://kubernetes.default.svc --- apiVersion: v1 kind: Secret metadata: name: cluster-zrh1 namespace: argocd labels: argocd.argoproj.io/secret-type: cluster type: Opaque stringData: name: zrh1 server: "<contenu de server dans zrh1.kubeconfig>" config: | { "tlsClientConfig": { "caData": "<contenu de certificate-authority-data dans dans zrh1.kubeconfig>", "certData": "<contenu de client-certificate-data dans dans zrh1.kubeconfig>", "keyData": "<contenu de client-key-data dans dans zrh1.kubeconfig>" } } --- apiVersion: v1 kind: Secret metadata: name: cluster-vie1 namespace: argocd labels: argocd.argoproj.io/secret-type: cluster type: Opaque stringData: name: vie1 server: "<contenu de server dans vie1.kubeconfig>" config: | { "tlsClientConfig": { "caData": "<contenu de certificate-authority-data dans dans vie1.kubeconfig>", "certData": "<contenu de client-certificate-data dans dans vie1.kubeconfig>", "keyData": "<contenu de client-key-data dans dans vie1.kubeconfig>" } } Note Dans le cas d’Exoscale, la bonne pratique serait de créer un ClusterRole dédié pour ArgoCD pour éviter l’utilisations de system:masters pour l’authentification, et d’utiliser par exemple des certificats avec un TTL maîtrisé. La commande exo compute sks kubeconfig permet en effet de spécifier un TTL. Voir mon article sur le TLS et l’authentification dans Kubernetes pour plus de détails. Une fois les valeurs remplacées, et le fichier appliqué via kubectl --kubeconfig argocd.kubeconfig apply -f <fichier>, les clusters devraient être visibles dans l’interface d’ArgoCD à l’adresse https://localhost:8080/settings/clusters: Déploiement d’un ApplicationSet Déployez maintenant l’ApplicationSet suivant pour tester le setup, toujours via kubectl --kubeconfig argocd.kubeconfig apply -f <fichier>: --- apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: guestbook namespace: argocd spec: generators: - clusters: {} template: metadata: name: '{{name}}-guestbook' spec: project: "default" source: repoURL: https://github.com/argoproj/argocd-example-apps/ targetRevision: HEAD path: guestbook destination: server: '{{server}}' namespace: guestbook syncPolicy: syncOptions: - CreateNamespace=true automated: prune: true selfHeal: true ArgoCD devrait vous générer automatiquement 3 applications, chacunes sur un cluster différent, le tout en fonction de ce qui est stocké dans le dossier guestbook sur le répertoire github https://github.com/argoproj/argocd-example-apps/. Elles seront également visibles dans l’interface. On remarque que le nom de chaque application est bien <cluster>-guestbook, par exemple vie1-guestbook. Rappelez vous que les générateurs des ApplicationSet vous permettent de filtrer si besoin sur quelles clusters les applications doivent être déployées, via des labels par exemple. Dans le setup actuel, ArgoCD créera automatiquement l’application pour chaque nouveau cluster ajouté, ou la supprimera si un cluster est supprimé. Chaque changement dans Git sera répercuté sur l’ensemble des clusters automatiquement également (grâce à l’option syncPolicy.automated, vous pouvez également choisir de "sync" manuellement les applications). Vous pouvez également vérifier sur vos clusters avec kubectl que l’application a bien été déployée, par exemple avec kubectl --kubeconfig vie1.kubeconfig get po -n guestbook. Un dernier point avant la conclusion: rien ne vous empêche d’utiliser les Application ET les ApplicationSet ArgoCD en parallèle selon les besoins. Conclusion ArgoCD est un outil puissant, qui peut grandement simplifier le multi region ou le multi cloud Kubernetes. On voit encore une fois ici l’avantage de Kubernetes: l’outillage est là pour répondre à des problématiques assez complexes, et ça fonctionne très bien.

Kubernetes multi cluster et multi région en GitOps...
Je présenterai dans cet article comment gérer les applications et ressources...
Source: mcorbin
Kubernetes et manifests YAML: trop bas niveau pour les dev ?
Un débat fait rage depuis longtemps dans la communauté Kubernetes: devons-nous construire des abstractions au dessus des primitives de Kubernetes, notamment pour générer les manifests ? Je pense que oui et j’expliquerai dans cet article pourquoi. La boîte à outil Kubernetes Kubernetes est une formidable boîte à outil pouvant s’adapter à n’importe quelle application à déployer. C’est ce qui fait sa force: contrairement à de nombreuses autres plateformes, toutes les options imaginables sont présentes pour définir comment votre application doit se comporter: ressources (cpu et mémoires), probes et cycle de vie, gestion des rolling upgrades, autoscaling, gestion du réseau, du load balancing et du stockage… Tout est possible grâce à l’API de Kubernetes. Cette puissante flexibilité est une des raisons du succès de Kubernetes: ça tourne partout, pour tout. Une question se pose pourtant immédiatement en entreprise lorsque Kubernetes commence à être utilisé: qui doit écrire les manifests Kubernetes (ces fameux fichiers YAML servant à décrire les ressources à déployer), et sous quel format ? Pour la première question, je pense que les utilisateurs (donc les développeurs) doivent pouvoir déployer de nouvelles applications sur un cluster Kubernetes en totale autonomie. Répondons maintenant à la seconde question. Le bon niveau d’abstraction Prenons le manifest potentiel d’une application fictive: --- kind: ConfigMap apiVersion: v1 metadata: name: cabourotte data: cabourotte.yaml: | http: host: "0.0.0.0" port: 7000 dns-checks: - name: "dns-check" description: "example" domain: "appclacks.com" timeout: 5s interval: 20s --- apiVersion: apps/v1 kind: Deployment metadata: name: cabourotte namespace: default labels: app.kubernetes.io/name: cabourotte spec: replicas: 3 selector: matchLabels: app.kubernetes.io/name: cabourotte template: metadata: labels: app.kubernetes.io/name: cabourotte spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app.kubernetes.io/name operator: In values: - cabourotte topologyKey: kubernetes.io/hostname containers: - name: cabourotte image: appclacks/cabourotte:v1.13.0 imagePullPolicy: IfNotPresent args: - daemon - --config - /config/cabourotte.yaml resources: limits: memory: 150Mi requests: cpu: 100m memory: 50Mi ports: - containerPort: 7000 name: http securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1664 livenessProbe: httpGet: path: /healthz port: http timeoutSeconds: 5 successThreshold: 1 failureThreshold: 3 periodSeconds: 10 startupProbe: httpGet: path: /healthz port: http timeoutSeconds: 5 successThreshold: 1 failureThreshold: 30 periodSeconds: 10 volumeMounts: - name: cabourotte mountPath: /config readOnly: true volumes: - name: cabourotte configMap: name: cabourotte --- apiVersion: v1 kind: Service metadata: namespace: default name: cabourotte spec: selector: app.kubernetes.io/name: cabourotte ports: - protocol: TCP name: content targetPort: 7000 port: 7000 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: "nginx" namespace: default name: cabourotte spec: rules: - host: www.cabourotte.test-domain.com http: paths: - path: / pathType: "Prefix" backend: service: name: cabourotte port: number: 7000 Il y a beaucoup de choses dans ce manifest: on crée une configmap, un deployment avec 3 replicas, un service, un ingress. Le but de cet article n’est pas de présenter toutes les options utilisées dans ce manifest (on aurait pu en ajouter d’autres, comme par exemple la configuration des capabilities ou du rolling update), mais on voit avec cet exemple simple que la configuration de Kubernetes est verbeuse. Pourtant, les besoins sont 99 % du temps les mêmes: déployer une application, l’exposer sur un port, créer un service puis éventuellement un ingress pour l’exposer sur un domaine spécifique. Il est donc intéressant de se demander quelle est la part d’informations "utiles" dans l’exemple présenté précédemment. Personnellement, ce que j’aimerai pouvoir définir dans ces situations classiques serait quelque chose comme ça: name: cabourotte replicas: 3 image: appclacks/cabourotte:v1.13.0 args: - daemon - --config - /config/cabourotte.yaml resources: limits: memory: 150Mi requests: cpu: 100m memory: 50Mi port: 7000 host: www.cabourotte.test-domain.com config: - path: /config/cabourotte.yaml content: | http: host: "0.0.0.0" port: 7000 dns-checks: - name: "dns-check" description: "example" domain: "appclacks.com" timeout: 5s interval: 20s C’est tout de suite beaucoup plus lisible. les différentes ressources n’apparaissent plus ici, l’application apparaissant comme un tout unifié. Pourquoi devrais-je avoir à définir explicitement une configmap, puis utiliser volumeMounts et volumes alors que je souhaite juste pouvoir exprimer le besoin "je veux ce contenu dans le fichier à cet emplacement" ? Pourquoi avoir plusieurs ressources à lier ensemble pour l’ingress alors que j’ai une relation directe entre mon application et le domaine sur laquelle je veux l’exposer ? Comme vous pouvez le deviner, je suis dans le camp de ceux pensant que l’abstraction de Kubernetes est trop bas niveau pour les développeurs. Je travaille avec Kubernetes depuis 2017 et ait eu l’occasion de travailler de très nombreux développeurs sur ce sujet au cours de ces années. Je n’ai jamais (vraiment !) rencontré un développeur content de devoir écrire des centaines de lignes de YAML, c’est même complètement l’opposé: Les gens (cela inclut les meilleurs développeurs avec qui j’ai travaillé) demandent une abstraction permettant d’aller à l’essentiel. Je suis sûr que c’est comme ça aussi chez vous. Pourquoi ? Réduire la charge cognitive Personne n’aime écrire et maintenir des centaines, voir milliers de lignes de YAML, alors que l’information vraiment utile peut être exprimée beaucoup plus simplement. C’est comme en programmation, où il existe parfois un facteur 10 en nombre de ligne de code entre deux langages plus ou moins expressifs (Clojure et Java par exemple): un programme court est très souvent plus maintenable car il "tient dans la ram" de votre cerveau. Réduire la définition d’une application Kubernetes à seulement ses parties essentielles nous fait gagner du temps de cerveau que l’on peut consacrer à autre chose. Prenons d’autres exemples. Il est courant dans Kubernetes d’ajouter des comportements à des ressources via des annotations: configuration des ingress (tls, entrypoint…), du monitoring (blackbox exporter), ou autre choses de ce type. Est ce que les développeurs ont besoin de savoir que pour exposer son application via un ingress sur un entrypoint Traefik public, il faut rajouter l’annotation spécifique traefik.ingress.kubernetes.io/router.entrypoints: "public" ? Ou bien, pour activer le TLS, traefik.ingress.kubernetes.io/router.tls: "true" ? Je veux permettre d’exprimer cela simplement, sans que l’infrastructure interne "leak" côté dev. Je ne veux pas qu’ils aient à connaitre des dizaines d’annotations à rallonge pour exprimer des besoins simples, je préfère des public: true, tls: true, monitoring: enabled… Je dois l’avouer: je n’arrive jamais à définir des Network Policies qui fonctionnent du premier coup. Pourtant, la majorité des besoins sont identiques: autoriser du traffic entre pods. Je n’ai pas envie que les gens aient à écrire des fichiers YAML complexes, se trompant 9 fois sur 10, en mode "trial and error", pour exprimer un besoin qui se résume en une ligne: "je veux que mon pod A puisse communiquer avec mon pod B sur le port 9876". La gestion des secrets, se résumant souvent (si vous utilisez par exemple external secrets) à "j’ai cette valeur dans le parameter store AWS et je souhaite la retrouver dans cette variable d’environnement ou dans ce fichier". Pourquoi devoir créer ou modifier de multiples ressources alors que le besoin s’exprime en une ligne ? Je pourrai continuer comme ça encore longtemps. On se rend compte rapidement qu’une application "prod ready" sur Kubernetes nécessite énormément de configuration, beaucoup plus que mon exemple précédent. Je le répète, c’est ce qui fait la force de Kubernetes. Mais les utilisateurs finaux ne sont pas intéressés par le "bruit", seulement par l’information utile à leurs niveaux. Fournir de bons défaults securityContext, anti affinity, imagePullPolicy… de nombreuses options dans Kubernetes sont souvent identiques dans la majorité des applications. Pourquoi les répéter ad nauseam alors qu’on peut les ajouter par défaut une bonne fois pour toute ? On peut même imaginer des valeurs par défaut dans les ingress pour les domaines (<app>.<main_company_domain>) par exemple. Il est certe possible (et très fortement conseillé) de faire de l’audit avec des outils comme Kyverno sur les configurations des manifests. Mais pourquoi s’embêter à définir ces options manuellement si elles sont de toute façon obligatoires ? Eviter les erreurs et gagner en vélocité Ce point est un peu similaire à celui de la charge cognitive. Pouvoir définir une application classique prête à être déployée sur Kubernetes ne devrait pas prendre plus que quelques minutes pour n’importe qui. Utiliser une abstraction permet cela, tout en évitant les heures de debugging à chaque nouveau microservice à base de "je me suis trompé dans mon volume", "j’ai une erreur dans mon label selector mon service marche pas", "je comprends pas mon secret n’est pas injecté"… Et c’est ce qui arrivera si les développeurs (et pas que, la même chez les SRE) doivent écrire des manifests complets, 100 % garantie. Pire, ça les saoulera (à raison) et les gens commenceront à copier/coller des manifests d’autres projets (bugs inclus) jusqu’à ce que ça "marche" (à première vue). Les critiques de cette approche Commençons par sûrement la plus évidente, que je partage également en partie: comment doit être définie l’abstraction ? J’ai dans mon exemple précédent volontairement omis de définir les probes dans mon manifest "simplifié". Que doit faire l’abstraction: définir des probes par défaut au risque à ce qu’elles ne soient pas adaptées à l’application (par exemple, pas de configuration d’une startup probe, ou mauvais type de probe) ? L’abstraction doit-elle également définir par défaut de l’anti-affinity au niveau des pods ? On voit qu’une connaissance des mécanismes de Kubernetes est quand même nécessaire, même avec une abstraction, comme par exemple sur le fonctionnement du cycle de vie des pods. Est ce qu’il n’est pas risqué de ne pas exposer cela ? Si l’option est essentielle (comme par exemple le nombre de réplicas ou la définition des ressources), il faut la rendre obligatoire. Cela doit sûrement être également le cas pour les probes, même si il peut quand même être intéressant d’avoir des probes "prédéfinies" en fonction du type de service. Egalement, rien de plus frustrant que de ne pas pouvoir utiliser une option spécifique de Kubernetes car l’abstraction ne l’expose pas. Comme dit en début d’article, la force de Kubernetes est de s’adapter à tout, et chaque option a son utilité, même si elle n’est utilisée que sur une application sur 100. Si les gens commencent à lutter contre l’abstraction, c’est perdu. Au pire, rien n’empêche de rebasculer sur du pure YAML pour des cas très spécifiques. Mais mon expérience me montre qu’il sont rares. Ouin Ouin c’est pas DevOps on cache Kubernetes aux dev Ici, Kubernetes est en partie masqué aux équipes de développement. Ce n’est pas un problème. Comme dit précédemment, il faut quand même comprendre les concepts de Kubernetes même dans ce cas. De plus, le YAML final est toujours récupérable, donc rien n’est caché: on simplifie juste sa génération. Fournir une abstraction ET former les gens à Kubernetes est possible (et souhaitable), mais il ne sert à rien de perdre en productivité pour aucune raison valable autre que "j’aime bien écrire 1500 lignes de YAML et 10 ressources quand je bootstrap une application". Je me considère pas trop mauvais avec Kubernetes et je suis le premier heureux lorsque j’utilise ce type d’abstractions. Est ce que j’ai l’impression de perdre en compétence ? Non. Par contre, je pourrai définir mes manifests beaucoup plus rapidement et avec plus de confiance. Et c’est aussi grâce à ça que l’on arrive à démocratiser Kubernetes au sein d’une entreprise: en fournissant l’outillage permettant d’exprimer clairement, simplement et précisément son besoin. A vous de choisir ou concevoir les bons outils (Helm ? Kustomize ? Typescript ? Une CRD ?) pour construire votre abstraction.

Kubernetes et manifests YAML: trop bas niveau pour...
Un débat fait rage depuis longtemps dans la communauté Kubernetes: devons-nous...
Source: mcorbin
Test de Clever Cloud en condition réelle
Clever Cloud, vous connaissez ? Ca vend un produit PaaS. Ca peut même remplacer Kubernetes il paraît, trop bien ! Testons et analysons donc la plateforme en situation réelle, comme d’habitude sur ce blog, sans bullshit. Le projet Je bosse depuis quelques mois sur un SaaS de monitoring. Projet bien fun à faire, sur lequel j’avance à mon rythme et qui permet de s’occuper pendant les longues nuits d’hiver. Le projet tourne chez Scaleway, sur des machines virtuelles classiques. Il utilise également une base de données PostgreSQL (gérée par Scaleway). Dans cet article, je vais tenter le déploiement de l’API de mon projet chez Clever Cloud. Cette API est écrite en Go (et est donc un simple binaire statique) et a juste besoin en dépendance dans le cadre de cette article d’une base de données PostgreSQL pour démarrer. On est donc sur un besoin très simple qui semble donc parfait pour du PaaS. J’en profiterai pour faire une analyse de la plateforme sur l’à côté. Allons y ! Base de données Pour plus de facilité, je ferai tout en clic dans l’interface dans cet article. Une CLI est aussi disponible si vous le souhaitez (écrite en javascript mais passons). Je commence donc par créer ma base de données dans l’interface: en quelques clics c’est fait, aucun problème. J’obtiens les informations de connexion de ma base de données: host, user, password. Premier soucis (ouais j’ai mon Linux en Français): psql: erreur : n’a pas pu traduire le nom d’hôte « <id>-postgresql.services.clever-cloud.com » en adresse : Nom ou service inconnu Impossible de me connecter à la base de données. Après un peu d’attente, ça passe. Bon c’est du cloud mais faut pas trop être pressé sur la propagation DNS. Seconde surprise: ma base de données est exposée sur internet. Ca pose bien sûr un énorme soucis de sécurité. Idéalement je voudrais que seule mon application puisse se connecter sur la base, et pouvoir éventuellement ouvrir mon IP temporairement pour un accès administrateur (dans la vraie vie faudrait du VPN ou autre mais on va pas pousser mémé dans les orties, on va rester dans le mindset "utilisateur de base qui fait le strict minimum pour que ce soit plus ou moins sécurité"). Rien dans l’interface pour le firewalling. Je cherche dans la doc, et je trouve finalement: c’est pas possible. Bon là j’ai quand même regardé mon calendrier pour vérifier: oui on est bien en 2023 et on doit encore faire des mails au support pour des features basiques de sécurité. J’espère d’ailleurs que vous êtes pas pressé si vous devez accéder à la DB en urgence, et que vous n’êtes pas en IP dynamique chez vous. Ce premier critère exclut déjà l’utilisation de la plateforme pour tous les projets un peu sérieux mais bon, continuons. Déploiement de l’application Création de l’application Je commence à créer l’application depuis l’interface. Déjà ça commence mal: je dois probablement choisir un truc sur cette page mais rien ne s’affiche (ce problème existe depuis plusieurs mois, je l’ai déjà rencontré avant): Bon, je vais cliquer sur Next et on verra plus tard. je choisis mon datacenter (je garde celui par défaut), et mon application est créée. Je dois maintenant pousser mon code pour déclencher un déploiement. Lisons un peu la documentation: Your application listens to the wild network 0.0.0.0,: aucun soucis. Your application listens on port 8080: wut ? Je peux pas choisir mon port ? Pourquoi ? You put your main code in a file named main.go aucun soucis. Je passe à la section Build the application: j’utilise bien sûr les go modules (avec un go.mod etc, bref le standard Go depuis des années). Bon allez, on teste. n’oubliez pas la variable CC_GO_PKG et CC_GO_BUILD_TOOL=gomod (qui est le standard depuis 150 ans donc pourquoi devoir le préciser) car sinon ça explose direct avec des erreurs (du genre no required module provides package). Problème: ça continue de péter avec des erreurs bizarres (http/root.go:11:2: cannot query module due to -mod=vendor). Je décide de ne plus faire de vendoring pour tester un build encore plus "classique". Surprise: je reviens dans la situation initiale d’avant la définition des variables CC_GO_PKG et CC_GO_BUILD_TOOL: des erreurs du type no required module provides package. Je rappelle que je n’utilise aucune lib privée en dehors de mon projet. Le temps passé à tenter de déployer un simple binaire Go se compte maintenant en heure. Je décide d’abandonner et de juste écrire un "main.go" sans aucun autre fichier et aucune dépendance, juste pour voir si j’arrive au moins à déployer une application. Je recrée donc une application from scratch; go mod init clever et un main.go contenant: package main import ( "fmt" "io" "net/http" ) func lol(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Srsly ?n") } func main() { fmt.Println("I'm starting") http.HandleFunc("/", lol) http.ListenAndServe(":8080", nil) } Ca fonctionne (et ça semble d’ailleurs utiliser go install ⊙﹏⊙) ! Aller plus loin On peut déjà voir que c’est la galère lorsqu’il s’agit de déployer un vrai projet. Mais continuons de creuser un peu la plateforme. Gestion de configuration Je n’ai pas trouvé comment injecter des fichiers de configuration dans les applications. C’est pourtant un besoin commun. Seulement des configurations via variables d’environnements semblent disponibles. J’avais fait il y a longtemps un email au support Clever Cloud quand je testais pour un autre besoin la plateforme et il n’y avait pas de solutions. C’est toujours le cas aujourd’hui. Réseau Clever Cloud n’offre quasiment aucune fonctionnalité réseau, par exemple des réseaux privés ni de règles de firewalling (type security groups, qui seraient une solution éventuelle même en exposant les applications sur des IP publiques). Si vous avez plus de deux applications à déployer et à faire communiquer de manière sécurisée (donc sans passer par internet), vous ne pourrez pas utiliser la plateforme. On rappelle que les VPC AWS sont sortis en… 2009. Secrets Je ne vois aucune façon sécurisée de stocker et injecter aux applications des secrets autres que d’utiliser des variables d’environnements. Mélanger configuration et mots de passe est une très mauvaise idée qui semble ici encouragée. Sur de L’IaaS classique il est toujours possible de gérer soit même les secrets (Hashicorp Vault, Ansible Vault). Sur des cloud comme AWS il y a des outils comme secret manager. Ici, vos secrets ne seront pas plus secret que l’hostname de votre base postgreSQL. Ici aussi on m’a dit que c’était "dans la roadmap" (probablement dans la même roadmap que le FaaS en alpha présenté à Devoxx il y a 4 ans). IAM Inexistant ? Couplage build and run Un sujet dont je parlais déjà dans cet article. Il y a dans clever cloud un fort couplage entre le build de votre application et son déploiement. Cela est probablement une tentative de créer une expérience utilisateur unifiée, mais le résultat est un lock-in fort qui force à tout faire en passant par Clever Cloud, là où de nombreux utilisateurs (dont moi) ont déjà toutes leurs CI (et stockage des artefacts de build) dans Github, Gitlab… et nos images Docker sur Harbor, Docker hub etc. Une approche sans avenir pour moi. Le fait d’être forcé de lier une application à un dépôt Git (confirmé par le support) est également dommage. Dans la vrai vie on veut juste fournir l’adresse d’une image Docker et c’est tout. Stockage Il y a du S3, mais aucun stockage local (les limitations de FS bucket le rendent pas vraiment utilisables). Donc vous ne pourrez déployer aucune application ayant besoin d’un stockage local non partagé. Health checks Il est commun aujourd’hui d’avoir plusieurs types de health checks pour monitorer la santé d’une application. Vous avez accès par exemple sur Kubernetes aux startup probes, readiness probes, et liveness probes, chacune répondant à un besoin précis et permettant de contrôler comment, au niveau réseau, et quand l’application recevra ou non du traffic. Il est aussi souvent possible de configurer plusieurs types de checks: HTTP, TCP, gRPC, commandes, avec choix des ports, path, timeouts, intervalles, delais avant de commencer le check… Clever Cloud ne semble avoir qu’un type de healthcheck (HTTP) et avec peu de paramètres. Vous serez donc rapidement limités pour gérer des cas un peu pénibles (slow start, services TCP et gRPC…). On va s’arrêter là. TL:DR Clever Cloud vous conviendra très bien si vous voulez héberger le site de votre PME ou le CFP de votre conférence (une application HTTP et une base de données, un VPS avec de l’autoscaling pour résumer), et si n’êtes pas regardant sur la sécurité. Les applications avec des architectures un peu plus complexes (plusieurs services, protocoles non HTTP voir non TCP, briques sortant du cadre web ou demandant du stockage local…) ne pourront pas être déployées. Mais au final, comme on l’a vu avec mon exemple en Golang ici que je n’ai réussi à faire marcher, vous allez devoir quand même devoir vous battre avec le PaaS. Un Netlify ou un fly.io (ou un Heroku, qui même en étant en mode maintenance depuis 10 ans vous fournira plus de features) vous posera moins de problèmes, ou même 2 VMs chez Scaleway avec un load balancer devant. Les autres besoins devront aller voir ailleurs: IaaS classique (qui ont souvent aussi des services de base de données, métriques, messages bus… en mode PaaS, c’est ce que je fais aujourd’hui pour certains projets), offres Kubernetes as a service… Vous aurez également beaucoup moins de lock-in sur votre infrastructure et votre vous du futur vous remerciera. Peut être qu’il serait temps pour certains de passer moins de temps en conf ou à taper sur Docker et Kubernetes (et leurs utilisateurs), non ? On a compris que c’était une stratégie marketing (certains se rappelleront de l’épisode Traefik dans la même veine) mais j’ai personnellement du mal à cautionner la diffusion volontaire d’informations fausses pour tenter de gagner quelques clients. Se concentrer un peu plus sur les produits et délivrer une plateforme pouvant répondre aux besoins du monde tech d’aujourd’hui est sûrement une bonne idée.

Test de Clever Cloud en condition réelle
Clever Cloud, vous connaissez ? Ca vend un produit PaaS. Ca peut même remplacer...
Source: mcorbin
La clé de la productivité des dev: un environnement de dev le plus simple possible
Marre des usines à gaz quand on bosse en local ! Il est 00:16, cela fait longtemps que je n’ai pas publié sur le blog. Plus le temps, plus trop la motivation aussi pour être honnête. Nouveau concept: je vais tenter d’écrire cet article en quelques dizaines de minutes pour ensuite prendre un repos bien mérité. Les dev, chez vous, comment ils travaillent en local ? Combien d’outils doivent-ils installer et démarrer, combien de scripts doivent-ils lancer avant de pouvoir travailler sur leurs applications ? Sûrement, comme partout, beaucoup trop. Vous arrivez dans votre nouvelle entreprise. Onboarding. Présentation de l’équipe. Votre première tâche arrive. Rajouter une petite fonctionnalité dans un projet. Git clone. Ah, il y a une doc dans le README sur comment installer les outils nécessaires pour travailler sur le projet. Vous commencez à lire. Un frisson vous traverse. Goutte de sueur qui coule sur le front. Un lointain souvenir du passé. Vous avez déjà vécu cette situation, avant, durant l’onboarding de votre précédente entreprise. Et durant l’onboarding de celle d’encore avant. Vous savez ce qui vous attend. Configurer l’environnement local. Packages à installer. Scripts shell à récupérer et à lancer. Variables d’environnements à configurer. Boîtes à outils construites en interne à télécharger. Vous copiez-coller les commandes qu’on vous dit d’exécuter. Ca a l’air de faire des trucs. Tiens, une erreur. Vous coincez. Une heure. Deux. Vous demandez de l’aide. Cette erreur est "normale", elle est là depuis deux ans, vous pouvez passer à la suite. Ici, la doc est fausse, faut lancer une autre commande pour pouvoir build le projet. Personne sait comment ça marche, mais ça marche. La doc s’arrête là, la suite du setup est de la tradition orale qu’un collègue vous expliquera. Vous voulez lancer les tests. Docker pour les base de données. Vous êtes sur Mac M1, merde. Pour tester votre application, il vous en faut aussi 5 autres pour lancer des tests end-to-end, microservices ftw. De nouveaux scripts plus ou moins maintenus pour cloner tout ça, tout démarrer en parallèle. C’est cassé, un des services ne démarre plus pour une raison obscure. Vous demandez, suppliez sur Slack vos collègues de vous aider. Personne n’a jamais eu cette erreur, pas de chance. C’est bon ça marche. Vous lancez les tests. Ca passe pas. Ah, on vous dit que les tests demandent d’initialiser des données dans la base de données dans docker compose. Tiens, le dump sql est dispo ici, charge le avec ce script. On vous souffle dans l’oreillette que des gens POC Minikube pour lancer toute la stack en local. Ca devrait simplifier les choses dans le futur. Il paraît. On est jour 3. Vous n’avez encore rien fait. Votre seule envie ? Ca tombe bien, c’est ce qu’il faut faire. Productivité = simplicité L’équation est simple: chaque nouveau outil à installer, chaque variable d’environnement à définir, chaque action à réaliser pour démarrer ou tester un service en local nuit à la productivité des développeurs. Quel dev n’a pas rencontré les problèmes décrits précédemment ? Pourtant, tout cela est de la pure complexité accidentelle. Il n’y a aucne raison valable pour avoir une telle complexité sur un environnement de développement. Vous ne devriez avoir besoin que des outils du langage que vous utilisez, et éventuellement docker (et docker-compose) pour démarrer quelques dépendances types base de données SQL pour des tests d’intégration. C’est tout Vous faites du Go ? Il vous faut la CLI go. Il vous faut quelques linters standards comme gofmt et golangci-lint. Vous lancez les tests avec go test. Vous buildez avec go build. FIN Vous faites du Java ? Vous installez le JDK, Maven. Vous lancez les tests avec mvn test, vous buildez avec mvn package ou équivalent (tbh j’ai pas fait de Maven depuis quelques années). FIN Du Clojure ? Il vous faut le JDK, leiningen. lein test, lein uberjar. FIN Du Rust ? cargo test, cargo build. FIN J’veux pas voir de dossiers scripts ou build, ou env dans vos projets. J’veux pas devoir lancer 36 commandes, 10 services, et définir 14 variables pour bosser. J’veux pas que tout pète à chaque upgrade de mon OS ou parce que mon collègue Roger a modifié une variable d’environnement obscure au fin fond d’un script stocké dans un random repo git, ou parce que des scripts shell codés sur un coin de table et "enrichis" à chaque problème local rencontré par quelqu’un explosent une fois sur deux. J’veux me concentrer sur mon application. Mon code. Sur la valeur que j’apporte à l’entreprise en bossant vite et bien, et en délivrant de la feature. Tout le reste, c’est du bruit. Et quand je bosse, je veux pas de bruit, enfin sauf Benighted dans mon casque bien sûr ! Gruik Gruik ! Il est 00:55, on verra demain pour les typos !

La clé de la productivité des dev: un environnement...
Marre des usines à gaz quand on bosse en local !
Il est 00:16, cela fait longtemps...
Source: mcorbin
2023: le shell n'est toujours pas une bonne idée pour l'administration système.
Le shell (bash par exemple) est à éviter au maximum lorsqu’on fait de l’administration système. Découvrez pourquoi dans cet article. Pour les mêmes raisons que 2022 et 2020. Bonne année.

2023: le shell n'est toujours pas une bonne idée...
Le shell (bash par exemple) est à éviter au maximum lorsqu’on fait de l’administration...
Source: mcorbin
Le PaaS est mort, vive le PaaS !
Le Platform as as Service. Promesse de productivité, de pouvoir se focus sur son code, de se passer des équipes ops… Pourtant, 12 ans après le rachat de Heroku par Salesforce, les limites du PaaS traditionnel sont de plus en plus visibles. Je donnerai dans cet article ma vision sur le PaaS, parlerai du multi cloud, et expliquerai pour quels usages le PaaS et pertinent ou non. Les limites du PaaS traditionnel j’ai écris il y a quelques temps un article appelé l’important n’est pas la technologie mais la plateforme qui est en lien avec le sujet de cet article. J’ai également eu l’occasion de donner un talk à AlpesCraft nommé rendez vos développeurs autonomes sur la production (slides ici), où l’explique pourquoi il est important aujourd’hui de laisser les équipes de développement gérer eux même, dans un certain périmètre, leurs services en production. Je pense en effet que, comme le PaaS nous le promet, il est important que les développeurs puissent se concentrent sur le développement (et donc sur créer de la valeur), et qu’il faut donc leur fournir de l’outillage leur permettant de déployer (très souvent) et gérer (au sens large: rollbacks, logs et métriques, service discovery…) leurs applications en production. A première vue, les PaaS répondent à ce besoin. J’ai d’ailleurs passer de longues journées récemment à tester les deux principaux PaaS Français pour un besoin de ce type, et il existe également bien sûr des PaaS d’autres pays fournissant en théorie ces fonctionnalités. En pratique, la réalité est tout autre. La partie infrastructure n’est pas à ignorer Les PaaS nous fournissent une abstraction de l’infrastructure. C’est, comme dit précédemment, exactement ce que l’on veut. Sauf que les fonctionnalités offertes par cette abstraction laisse souvent à désirer. Les PaaS s’adressent principalement à un public de développeur, qui ont au final peu de connaissances en infrastructure ou en gestion de production. Beaucoup d’utilisateurs de PaaS ne sont donc pas vraiment en capacité de juger l’abstraction proposée par le PaaS. Voici quelques exemples sur ce que je rencontre régulièrement sur ce type de plateformes. Nous sommes en 2022 et il est souvent difficile, voir impossible, de faire communiquer plusieurs applications de manière sécurisée. Les outils type base de données sont souvent exposées sur internet, il n’est pas possible de mettre en place des règles de firewalling fines. Les applications sont très souvent contraintes sur ce qu’elles peuvent exposer comme type de services (par exemple seulement de l’HTTP, sur un seul port). La partie précédente sur le réseau montre bien que n’importe quelle entreprise ayant un minimum d’exigence de sécurité ne peut pas utiliser ce type de fournisseur. L’absence de gestionnaire de secrets, qui sont souvent stockés et passés comme simple variables d’environnements (pratique qui est souvent la cause de leak de secrets d’ailleurs) est également problématique. Les utilisateurs exigeants voulant par exemple faire du mTLS entre services seront également déçus. Les PaaS se chargent de déployer chaque nouvelle version du logiciel déployé. De manière assez surprenante, il est souvent impossible de configurer nous même le health check à exécuter sur l’application. C’est pourtant un aspect essentiel sur la gestion d’un déploiement, notamment pour pouvoir faire des rolling updates de type "graceful", sans aucune pertes de requêtes et en drainant proprement les instances de l’application à éteindre. Ce n’est d’ailleurs pas pour rien que Kubernetes possède différents types de health checks (startupProbe, livenessProbe et readinessProbe) ou des options de type terminationGracePeriodSeconds, pour contrôler finement comment l’application va réagir en cas d’arrêt ou au démarrage. Rien de pire que de perdre du trafic ou perdre des tâches en cours d’exécution à chaque déploiement, ce qui sera le cas sans la gestion de ces paramètres. Cette petite liste n’est bien sûr pas exhaustive. J’aurai pû également parler de l’outillage qui laisse souvent à désirer. Un novice ne verra pas ce type de problèmes. Ils sautent pourtant aux yeux de n’importe qui ayant une certaine expérience dans la gestion d’infrastructures. L’app s’adapte au PaaS, non l’inverse J’ai écris il y a quelques temps un article nommé développement d’applications, conteneurs, et plateforme d’exécution. Presque 2 ans après, je pense toujours la même chose: une application bien conçue doit pouvoir tourner sur différents types de plateformes (bare metal, machines virtuelles, environnement conteneurisés…). Si votre application expose ses métriques au format Prometheus sur /metrics, qu’elle génère des logs propres en JSON, qu’elle se configure simplement (via un simple fichier de configuration par exemple), expose un endpoint type /healthz Il n’y a aucune raison que vous ayez à réécrire votre application pour tourner sur un runtime particulier. Cela n’est pas vrai sur le PaaS. Le fait dans certains PaaS de ne supporter que HTTP, sur un seul port, exclut un grande nombre d’applications. D’autres ne supportent pas le passage de fichiers de configuration, mais ne supportent que les variables d’environnement, ce qui exclut le déploiement d’une majorité des logiciels. On va vous dire "ah non, nous on ne supporte pas les métriques au format Prometheus, il faut utiliser un autre protocole". Bref, vous allez lier votre application à la façon dont fonctionne votre provider, ce qui est pour moi un problème. Couplage intégration et déploiement continu Les PaaS essayent de fournir une expérience tout en un aux développeurs: ils font un commit, le code part en production. C’est donc le PaaS qui se charger de récupérer les dernières modifications (via Git), de construire l’artefact à déployer, et de le lancer. Je n’ai jamais compris cette approche. J’ai toujours personnellement travaillé en décorrélant la partie CI (lancement des tests, construction et stockage de l’artefact à déployer) de la partie déploiement. Je ne veux pas avoir à passer par une phase de build obligatoire avant de pouvoir déployer, je veux pouvoir le faire de mon côté, avec mes propres outils, fournisseurs d’outils de CI ou de gestion de dépendances et artefacts (Artifactory, Docker Hub…). Avoir autant de couplage est problématique et je ne considère pas cela une bonne pratique Lock in Le PaaS, s’occupant de tout pour vous, est probablement le type de cloud créant le plus de vendor lock-in. Vous avez même souvent dû, comme dit précédemment, adapter votre application pour tourner dessus ! Cela n’est pas forcément un problème (le lock-in étant un choix qui peut se justifier si il permet de travailler efficacement), mais c’est à garder en tête. L’IaaS en parallèle du PaaS Un autre problème du PaaS est l’offre restreinte de produits disponibles. Quelques bases de données et bus de messages, et c’est tout. Il est très courant d’avoir besoin de plus. C’est là que le bon vieux IaaS (Infrastructure as a Service) rentre en action. Il y a d’ailleurs quelque chose d’intéressant à énoncer sur le sujet: il est possible de construire sur un IaaS (ou meme dans son propre datacenter) n’importe quoi, PaaS inclus. L’inverse n’est bien sûr pas vrai. Votre machine virtuelle avec ipsec dessus pour discuter avec un fournisseur externe, cette petite base de données non disponibles as a service mais que vous voulez utiliser, ce client qui veut absolument pouvoir faire du sftp… Tout cela est possible si votre provider cloud fournit également de l’IaaS. Et croyez moi, ces cas non supportés par un PaaS arrivent très vite. C’est en partie d’ailleurs pour cela que beaucoup d’entreprises quittent rapidement le PaaS pour aller chez des acteurs plus gros type AWS (qui a l’avantage de fournir aussi des produits types PaaS), par ne plus être limité par ce que le PaaS propose. Le futur du PaaS Mais donc, le PaaS actuel est-il voué à disparaître ? Non. Comme aujourd’hui, certaines personnes avec de petits besoins vont y trouver leur compte. Ce que je trouve beaucoup plus intéressants, c’est l’apparition de PaaS spécialisés qui viennent en complément des gros cloud providers. Je pense notamment à Netlify (où tourne ce blog et un certain nombre de sites web que je maintiens), qui pour moi est un des meilleur produit dans sa catégorie. Héberger des sites statiques chez Netlify peut être complètement décorrélé de l’hébergement de l’infrastructure principale de votre entreprise chez AWS par exemple. Cloudflare, et sa partie worker, est également un bon exemple. C’est un produit qui viendra en addition du reste de votre infrastructure, avec peu de couplage. Les fournisseurs IaaS traditinnels fournissent également de plus en plus de services haut niveau. Comme dit précédemment, cela permet à terme de faire cohabiter les deux mondes (IaaS et PaaS) sur le même cloud provider, et avec une très bonne intégration entre les produits. Ce n’est pas un hasard si la majorité des entreprises tech aujourd’hui construisent leurs propres PaaS internes en utilisant Kubernetes, en s’appuyant bien sûr sur des offres Kubernetes as a service fournies par des cloud provider. Kubernetes peut sembler complexe, mais c’est aussi parce qu’il vous expose toutes les options pour faire tourner vos applications correctement (notamment les points que je citais en début d’article, mais aussi beaucoup d’autres), et sans a priori sur comment votre application va se comporter. Il est beaucoup plus simple de porter une application "legacy" sur Kubernetes que sur un PaaS traditionnel. Je pense que l’on verra de plus en plus des offres PaaS basées sur l’API Kubernetes, mais avec les parties réseau, observability, load balancing et ingress… gérées par le cloud provider et avec une facturation au pod (et donc, sans notion de noeuds worker pour le client). C’est déjà des choses qui commencent à apparaître et qui je pense vont peu à peu se démocratiser Il est possible que certains fournisseurs PaaS arrivent à rebondir en améliorant leurs offres et en proposant en complément des produits orientés IaaS pour donner plus de flexibilité aux clients. Mais il semblerait qu’aujourd’hui cela soit plus simple pour un IaaS de sortir peu à peu des produits PaaS que l’inverse. Conclusion Tout est dans le titre de l’article. Je pense que le PaaS traditionnel ne sera qu’une niche sur le marché du cloud, et qu’à l’avenir on se tournera de plus en plus vers des PaaS spécialisés, des acteurs IaaS du cloud qui proposent des outils PaaS/FaaS totalement intégrés à leurs écosystèmes (ce que font déjà les gros acteurs), et des PaaS utilisant Kubernetes avec plus ou moins de fonctionnalités gérées par le cloud provider (et donc avec plus ou moins de choses à gérer en interne). On verra dans quelques années si cet article était exact ou non. C’est tout l’intérêt d’un blog d’ailleurs: se relire quelques années après publication, et là deux réactions sont généralement possibles: un sourire satisfait ou un gros /facepalm. Qui vivra verra.

Le PaaS est mort, vive le PaaS !
Le Platform as as Service. Promesse de productivité, de pouvoir se focus sur son...
Source: mcorbin
DevOps
Définition
Le DevOps est une approche de développement logiciel qui vise à améliorer la collaboration entre les équipes de développement (Dev) et d'exploitation (Ops) au sein d'une organisation. Le terme "DevOps" est une contraction de "Development" (développement) et "Operations" (exploitation). L'objectif principal du DevOps est d'accélérer le cycle de développement, de déploiement et de mise en production des logiciels tout en assurant une plus grande fiabilité et une meilleure qualité.
Les principaux aspects du DevOps sont les suivants :
- Collaboration : Le DevOps encourage une communication et une collaboration étroites entre les équipes de développement et d'exploitation. Cela aide à éliminer les silos organisationnels et à favoriser une compréhension mutuelle des objectifs et des contraintes de chaque équipe.
- Automatisation : L'automatisation est au cœur du DevOps. Les tâches répétitives et manuelles sont automatisées autant que possible, ce qui permet de réduire les erreurs humaines, d'accélérer les processus et de garantir une cohérence dans les déploiements.
- Intégration continue (CI) : Dans le cadre du DevOps, les développeurs intègrent fréquemment leur code dans une base commune. Chaque intégration est automatiquement testée, ce qui permet de détecter rapidement les erreurs et de les corriger.
- Livraison continue (CD) : La livraison continue consiste à automatiser le processus de déploiement des applications. Les modifications apportées au code sont automatiquement déployées dans un environnement de test, puis dans l'environnement de production lorsque les tests sont concluants.
- Surveillance et rétroaction : Le DevOps implique une surveillance continue des performances de l'application en production. Les données de surveillance aident à détecter les problèmes rapidement et à prendre des mesures correctives. De plus, les commentaires des utilisateurs sont pris en compte pour améliorer constamment l'application.
- Sécurité : La sécurité est un aspect essentiel du DevOps. Les pratiques de sécurité sont intégrées dès le début du processus de développement, et des contrôles de sécurité sont automatisés dans le pipeline de livraison continue pour détecter les vulnérabilités rapidement.
En adoptant le DevOps, les organisations visent à accélérer leur capacité à fournir des logiciels de haute qualité tout en réduisant les risques et les coûts associés aux déploiements. Cette approche favorise également une culture de collaboration, d'amélioration continue et d'agilité au sein de l'entreprise.