Le compte à rebours est lancé pour Keyfactor Tech Days | Réservez votre place dès aujourd'hui !

Songbird : Le voyage vers la conteneurisation d'un monolithe de .NET Framework

Communauté des développeurs

La première fois que j'ai entendu parler des conteneurs, c'était en 2018, lorsqu'un collègue nous a montré une présentation sur ce qu'ils étaient et comment ils fonctionnaient. Mes seules pensées ont été "cool, c'est juste des machines virtuelles (VM) surcompliquées. Quoi qu'il en soit..." C'est embarrassant de voir à quel point j'étais naïf et dans l'erreur.

J'étais encore novice dans le développement de software et ce sujet était en dehors de ce qui m'intéressait, à savoir les corrections de bogues et quelques petites fonctionnalités du produit sur lesquelles je travaillais à l'époque. Qui aurait pu imaginer que 4 ans plus tard, nous entendrions des murmures au sujet des conteneurs et que je serais à la tête de la charge pour que cela se produise.

Mais Joe, pourquoi consacrer tout ce temps et tous ces efforts à conteneuriser un monolithe qui est déjà stable et fiable ?

Cher lecteur, nos raisons ne sont peut-être pas les mêmes que les vôtres, mais nous avons choisi de le faire pour des raisons de modernisation, d'évolutivité et de réduction des coûts d'infrastructure. Notre application fonctionnant sur .NET Framework et reposant sur Active Directory (AD) ne répondait pas aux besoins des clients, qui souhaitaient OAuth, une prise en charge multiplateforme et une plus grande évolutivité. Les applications web conteneurisées peuvent évoluer en fonction des besoins, et les coûts d'infrastructure liés à l'ajout de conteneurs ne représentent qu'une fraction de ce qu'il en coûterait pour mettre en place davantage de machines virtuelles Windows et pour conserver un environnement AD complet.

Nous avions un défi à relever : Notre solution phare d'automatisation du cycle de vie des certificats, Keyfactor Command , était à l'époque un monolithe .NET Framework qui s'appuyait fortement sur AD et Internet Information Services (IIS). Elle utilisait également un installateur Microsoft Software (MSI) et une interface graphique Windows uniquement pour l'installation/la configuration. Prendre tout cela, l'envelopper dans un conteneur Windows et s'arrêter là n'était pas une option réaliste.

Bien que les conteneurs Windows existent, ils n'ont jamais été un standard de l'industrie et nous ne voulions pas nous intégrer encore plus dans l'écosystème Windows. Les conteneurs Windows auraient constitué une étape temporaire, mais comme l'a si bien dit un jour un collègue de travail, "il n'y a rien de plus permanent qu'une solution temporaire" : "Il n'y a rien de plus permanent qu'une solution temporaire". Il était temps de prendre le multiplateforme au sérieux.

Nous nous sommes fixé quelques objectifs ambitieux :

  1. La plateforme Keyfactor Command peut être démarrée et exécutée dans un environnement de conteneurs Kubernetes.
  2. Les composants connexes (les "abeilles ouvrières"), notamment l'orchestrateur et les passerelles CA, seront également conteneurisés, de sorte que l'ensemble de la suite de produits puisse être exécuté dans le même environnement.
  3. Maintenir la compatibilité ascendante de toutes les caractéristiques du produit.
    1. L'authentification AD fonctionne toujours pour les clients qui l'utilisent actuellement
    2. Les installations basées sur Windows continuent de fonctionner sans interruption.

C'est formidable ! Mais comment y parvenir ? Alors que nous nous engagions sur la voie de la conteneurisation, notre liste de choses à faire n'a cessé de s'allonger....

Tout d'abord, l'application doit pouvoir fonctionner sur plusieurs plates-formes, et nous devons donc passer de .NET Framework à .NET 8. Une partie de cette migration impliquait de ne plus s'appuyer sur IIS et AD pour l'authentification, et donc d'ajouter la prise en charge d'OAuth et d'OIDC à la liste. Nous avons également dû mettre en œuvre la conversion du runtime .NET et OAuth sur tous les composants du produit, et pas seulement sur la plateforme elle-même. Ai-je mentionné que vous deviez remplacer tout ce qui se trouve sous le capot et qui dépend de Windows par quelque chose d'entièrement différent ? En fin de compte, il s'agit d'une réécriture aussi proche que possible d'une réécriture tout court. Néanmoins, nous avons donné à cet effort le nom de code "Songbird" et nous nous sommes mis au travail.

Les conditions préalables

Au moment où nous avons lancé Songbird, nous étions déjà bien engagés dans un autre effort que nous appelions en interne "AD Independence". Il s'agissait de rompre notre lien étroit avec AD et d'ajouter éventuellement le support d'OAuth au produit comme objectif final. Bien, donc c'est en cours, et par hasard la conversion .NET a fini par être une exigence pour compléter l'indépendance AD. Donc, pour commencer, il est temps de s'attaquer à la conversion .NET.

Pour ceux qui ne sont pas familiers avec .NET, la conversion de Framework à .NET est une montagne russe de banalités et de réécritures complètes. Alors que 95 % de votre base de code survivra à la transition 1 pour 1, le code de démarrage de l'application en particulier a besoin d'une réécriture complète. Vous devez ensuite le faire pour chaque application. La plateforme Command est une combinaison de 7 applications qui fonctionnent toutes ensemble : Cinq sont des sites web ASP.NET, une est un service Windows et une autre est l'application de configuration de la console.

La conversion des sites web ASP.NET a certainement été la partie la plus difficile, car l'ensemble du pipeline ASP.NET, depuis la réception d'une requête jusqu'au point où notre couche contrôleur reçoit, est entièrement différent. Non seulement le code de démarrage de l'application a dû être refait, mais des pans entiers d'applications web de base, comme l'autorisation, la journalisation des requêtes et la gestion des erreurs, ont dû être refaits pour mieux s'adapter au nouveau moule ASP.NET.

L'assistant de configuration est un autre élément à prendre en compte dans la conversion .NET. Aujourd'hui, il s'agit d'une application basée sur WPF, qui n'est pas multiplateforme même avec un runtime .NET. Nous devrions nous convertir à un autre cadre d'interface utilisateur, comme MAUI ou Avalonia, mais qu'est-ce que cela nous apporterait ? La possibilité d'installer Command sur une machine Linux et d'utiliser quelque chose comme NGINX pour remplacer IIS ne figurait pas sur la liste des objectifs finaux, c'est pourquoi nous avons fini par abandonner cette idée.

Au lieu de cela, nous avons transféré la plus grande partie de notre configuration de base dans une bibliothèque commune multicible et créé une nouvelle application de console qui imite les capacités de configuration d'un assistant. Cette nouvelle application, l'outil de mise à jour de la base de données, serait le bootstrapper de la base de données dans le cas d'un déploiement en conteneur. Vous ne pouvez pas utiliser une interface graphique à partir d'un conteneur de toute façon :).

Maintenant que tout est converti en .NET, il faut s'attaquer au problème de l'authentification (AuthN) et de l'autorisation (AuthZ). Auparavant, nous comptions sur IIS pour effectuer l'authentification via Basic ou Kerberos auth, puis ASP.NET fournissait une identité d'utilisateur à utiliser. Ce sujet mérite un article à part entière, mais nous avons dû utiliser le middleware ASP.NET pour permettre à l'authentification basée sur IIS AD de continuer à fonctionner et pour ajouter le code d'authentification OAuth et les flux d'informations d'identification du client. De plus, au lieu de faire correspondre les rôles et permissions de l'application aux utilisateurs et groupes AD, le modèle de sécurité a dû être retravaillé pour être basé sur les réclamations.

Il est temps de fabriquer des conteneurs

Cela nous a pris beaucoup de temps, mais nous sommes enfin arrivés et nous sommes prêts à commencer à assembler les pièces du puzzle. Pour ne pas trop compliquer nos constructions, nous avons choisi de rendre nos dockerfiles aussi minces que possible, et de copier des binaires déjà construits dans les images, au lieu de reconstruire l'intégralité du code source dans les images des conteneurs.

Une fois que nous avions les images, elles devaient être exécutées quelque part. Au début du développement, il s'agissait d'instances locales de Docker Compose avec une tonne de configuration personnalisée, ainsi qu'un proxy inverse. Nous avons rapidement réalisé que cela n'allait pas s'adapter ou être très supportable en production car il y avait tout simplement trop de paramètres de configuration et de conteneurs qui devaient s'aligner pour que tout fonctionne comme prévu. Nous avions également fixé l'objectif d'exécuter l'application dans Kubernetes (K8s).

Nous avons décidé d'adopter une autre approche et de monter d'un cran dans l'écosystème des conteneurs pour orchestrer avec Kubernetes (K8s) et Helm. En utilisant Helm, nous pourrions définir un diagramme avec un ensemble commun de valeurs de configuration qui ferait tout le travail de connexion pour vous. Nous avons brièvement envisagé d'écrire un opérateur K8s, qui fait effectivement la même chose, mais nous avons décidé de ne pas le faire car cela nécessitait plus de code qui devait être maintenu, alors que Helm est un modèle de configuration plat qui reflète étroitement ce qui est déployé dans K8s.

Nous avons passé beaucoup de temps à rendre cette charte aussi facile à utiliser que possible. L'une de nos exigences étant de maintenir la compatibilité ascendante, Keyfactor Command doit toujours pouvoir être installé via notre MSI sur Windows, et nous voulions que la courbe d'apprentissage soit faible pour l'utilisation de cette méthode d'installation alternative. Même si le diagramme Helm sera probablement utilisé en production par des pipelines automatisés, il était important pour nous que les utilisateurs internes qui ne sont pas très au fait des conteneurs puissent être dirigés vers un cluster, recevoir un fichier de valeurs qu'ils doivent remplir avec les éléments importants, puis lancer une installation de Helm, et 2 minutes plus tard, ils peuvent se connecter à leur instance Command et commencer à l'utiliser. En même temps, bien que le cas par défaut sur le graphique soit aussi simple que possible, nous avons fourni suffisamment de configuration pour permettre à l'application d'être personnalisée en fonction de n'importe quel besoin.

Notre dernier défi concernait une limitation selon laquelle notre base de données devait être créée et configurée avant que les applications elles-mêmes ne puissent démarrer avec succès. Dans les anciens environnements Windows, la configuration de la base de données est effectuée dans le cadre du processus d'installation, avec les services web associés et la configuration d'IIS. K8s est très déclaratif, vous lui dites de lancer des applications et il le fait. Voyez-vous le problème ? Si nous lançons les services web et une tâche de configuration de la base de données en même temps, quelque chose va forcément échouer. Puisque nous essayons de rendre ce système aussi facile à utiliser que possible, notre seule option était de l'intégrer dans le diagramme, ce qui est plus difficile qu'il n'y paraît.

Helm, comme K8s, essaie également de fonctionner de la manière la plus déclarative possible. Mais nous disposons de quelques options pour réaliser ce flux de démarrage :

  1. Déployez tout ensemble, utilisez les conteneurs Init pour interroger la base de données et attendre la fin d'un travail de configuration, puis démarrez les services web.
  2. Utilisez les crochets Helm pour pondérer les déploiements de manière à ce que le travail de configuration s'exécute avant que les webservices n'existent, et une fois terminé, déployez les webservices.

Nous avons passé beaucoup de temps à étudier ces options, car chacune avait ses avantages et ses inconvénients et il n'y avait pas de meilleure option. L'option 2 a fini par l'emporter en raison des préoccupations liées aux conditions de course qui pourraient être possibles avec l'option 1. Les crochets eux-mêmes ont quelques inconvénients, notamment en cas d'échec de l'installation, Helm ne nettoie pas les ressources du crochet et vous devez le faire à la main. Ce n'est pas vraiment un problème en production, mais pendant les tests de développement, c'était un point de douleur constant qui nous a presque fait revenir à l'option des conteneurs init à plusieurs reprises.

Le produit final

Et c'est tout. Maintenant que nous disposons du manuel de jeu, il ne nous reste plus qu'à appliquer un concept similaire aux autres composants. Dites bonjour au nuage moderne et adieu aux vieux serveurs Windows. Ceci fait, nous avons maintenant Keyfactor Command version 24.4 avec la conteneurisation disponible pour les clients et nos objectifs élevés fixés plus tôt ont finalement été atteints.

Au final, nous avons pu atteindre notre objectif de créer un déploiement conteneurisé robuste de Command à l'aide de Kubernetes et Helm, tout en conservant une installation basée sur Windows MSI. Bien que cela ait représenté beaucoup de travail impliquant de nombreuses étapes complexes, le résultat répond avec succès à toutes nos exigences. Nous avons beaucoup appris en cours de route et voyons maintenant un océan de possibilités pour rendre notre application plus cloud native et encore meilleure pour les clients.

Il s'agit de notre premier blog sur le développement de Keyfactor , et si vous l'avez apprécié, nous serions ravis de recevoir vos commentaires afin de pouvoir vous emmener dans les coulisses d'autres projets à l'avenir. Connectez-vous avec moi sur LinkedIn et envoyez-moi un message si vous avez apprécié ce que vous avez lu, et restez à l'écoute pour d'autres blogs de développement à l'avenir.