Le monde a changé. Je le sens dans l'eau, je le vois dans le sol, je le sens dans l'air. Tout ce qui existait autrefois a disparu, et il n'y a plus ceux qui s'en souviennent.Extrait du film «Le Seigneur des anneaux: la communauté de l'anneau»Il y a 100 500 articles et rapports sur Internet sur le sujet «comment nous avons vu un monolithe», et je n'ai aucune envie d'en écrire un autre. J'ai essayé d'aller un peu plus loin et de dire comment les changements technologiques ont conduit à l'apparition d'un tout nouveau produit (spoiler: nous avons écrit la boîte et écrit la plate-forme). L'article est en grande partie une revue, sans détails techniques. Les détails viendront plus tard.
Il s'agira du site du panneau de contrôle et du serveur Vepp. Il s'agit d'un produit ISPsystem où je dirige le développement. Découvrez les capacités du nouveau panneau
dans un autre article , ici - uniquement sur la technologie. Mais d'abord, comme d'habitude, un peu d'histoire.
Partie 1. Il est nécessaire de changer quelque chose
Notre entreprise écrit des logiciels pour l'automatisation des services d'hébergement depuis plus de 15 ans. Pendant ce temps, plusieurs générations de nos produits ont changé. Lorsque nous sommes passés du deuxième au quatrième, nous avons changé Perl en C ++ et avons commencé la vente gratuite de nos logiciels ... cadre).
Mais, non seulement nous changions, nos clients et nos concurrents changeaient. Si il y a 10 à 15 ans, le propriétaire du site était techniquement bien informé (d'autres étaient peu intéressés par Internet), il peut maintenant s'agir d'une personne qui n'est en aucun cas connectée à l'informatique. Il existe de nombreuses solutions concurrentes. Dans de telles conditions, il ne suffit pas de travailler un produit, il faut qu'il soit facile et agréable à utiliser.
Refonte héroïque
Ce changement est devenu dans tous les sens le plus notable. Pendant tout ce temps, l'interface de nos produits est restée pratiquement inchangée et unifiée. Nous avons déjà écrit à ce sujet séparément - une vue de l'UX. Dans la cinquième génération de produits, l'API a défini l'apparence des formulaires et des listes. Ce qui, d'une part, a permis de mettre en œuvre beaucoup de choses sans impliquer les développeurs frontaux, d'autre part, il a donné lieu à des appels complexes très complexes, affectant parfois la majeure partie du système, et limitant considérablement la possibilité de changer d'interface. Après tout, tout changement est inévitablement un changement dans l'API. Et c'est tout, bonjour aux intégrations!
Par exemple: la création d'un utilisateur dans ISPmanager peut également conduire à la création d'un utilisateur FTP, domaine de messagerie, boîte aux lettres, enregistrement DNS, site. Et tout cela se fait de manière atomique, et bloque donc les modifications dans les composants répertoriés jusqu'à ce que l'opération soit terminée.Dans la nouvelle API, nous sommes passés à des opérations atomiques petites et simples, et les développeurs Frontend se sont retrouvés avec des actions complexes complexes. Cela vous permet d'implémenter une interface complexe et de la modifier sans affecter l'API de base et, par conséquent, sans interrompre l'intégration.
Pour les développeurs frontend, nous avons implémenté un mécanisme d'exécution de requêtes par lots avec la possibilité d'effectuer des actions inverses en cas d'erreur. Comme le montre la pratique, dans la plupart des cas, les requêtes complexes sont des demandes de création de quelque chose, et la création est assez facile à annuler en effectuant une suppression.Réduisez le temps de réponse et les notifications instantanées
Nous avons refusé de longues demandes de modification. Les versions précédentes de nos produits pouvaient se bloquer pendant longtemps, essayant de répondre à une demande de l'utilisateur. Nous avons décidé que pour chaque action, il est plus difficile de modifier trivialement les données de la base de données, nous allons créer une tâche dans le système et répondre au client le plus rapidement possible, lui permettant de continuer à travailler et de ne pas regarder le processus de chargement sans fin.
Mais si vous avez besoin du résultat, comment appelle-t-on ici et maintenant? Je pense que beaucoup de gens connaissent la situation lorsque vous rechargez la page encore et encore dans l'espoir que l'opération est sur le point de se terminer.
Dans la nouvelle génération de produits, nous utilisons websocket pour la livraison instantanée d'événements.
La première implémentation utilisait longpoll, mais les développeurs front-end étaient mal à l'aise avec cette approche.Communication interne HTTP
HTTP en tant que transport a gagné. Maintenant, dans n'importe quelle langue, un serveur HTTP est implémenté en quelques secondes (si vous google, puis en quelques minutes). Même localement, il est plus facile de former une requête HTTP que de bloquer votre protocole.
Dans la génération précédente, les extensions (plugins) étaient des applications qui, si nécessaire, étaient lancées en tant que CGI. Mais pour écrire une extension longue durée, j'ai dû m'efforcer: écrire un plug-in en C ++ et le reconstruire à chaque mise à jour du produit.
Par conséquent, à la sixième génération, nous sommes passés à l'interaction interne via HTTP, et les extensions, en fait, sont devenues de petits serveurs WEB.
Nouvelle API (pas tout à fait REST)
Dans les générations précédentes de produits, nous avons transmis tous les paramètres via GET ou POST. S'il n'y a pas de problèmes particuliers avec GET - sa taille est petite, alors dans le cas de POST, il n'y avait aucun moyen de vérifier l'accès ou de rediriger la demande jusqu'à ce qu'elle soit entièrement lue.
Imaginez à quel point c'est triste: accepter quelques centaines de mégaoctets ou gigaoctets, puis découvrir qu'ils ont été versés par un utilisateur non autorisé ou qu'ils doivent maintenant être transférés sur ce serveur!Maintenant, le nom de la fonction est passé dans l'URI, et l'autorisation est exclusivement dans les en-têtes. Et cela vous permet d'effectuer une partie des contrôles avant de lire le corps de la requête.
De plus, les fonctions API sont devenues plus simples. Oui, maintenant, nous ne garantissons pas l'atomicité de la création d'un utilisateur, d'un courrier, d'un site Web, etc. Mais nous donnons la possibilité de combiner ces opérations selon les besoins.
Nous avons mis en œuvre la possibilité d'exécuter des requêtes par lots. En fait, il s'agit d'un service distinct qui accepte une liste de demandes et les exécute séquentiellement. Il peut également annuler des opérations déjà terminées en cas d'erreur.
Vive SSH!
Une autre décision que nous avons prise sur la base de notre expérience précédente est de travailler avec le serveur uniquement via SSH (même s'il s'agit d'un serveur local). Dans un premier temps, nous avons travaillé avec le serveur local dans VMmanager et ISPmanager, et ce n'est qu'alors que nous avons permis d'ajouter des serveurs distants supplémentaires. Cela a conduit à la nécessité de prendre en charge deux implémentations.
Et lorsque nous avons refusé de travailler avec le serveur local, les dernières raisons d'utiliser les bibliothèques natives du système d'exploitation utilisateur ont disparu. Avec eux, nous sommes tourmentés depuis la fondation de l'entreprise. Non, les bibliothèques natives ont leurs avantages, mais il y a plus d'inconvénients.
Un avantage absolu est une consommation moindre de disque et de mémoire, et pour travailler à l'intérieur de VDS, cela peut être assez important. Parmi les inconvénients - l'utilisation d'une bibliothèque dont vous ne contrôlez pas la version peut entraîner des résultats inattendus, ce qui augmente considérablement la charge de développement et de test. Un autre inconvénient est l'impossibilité d'utiliser les dernières versions des bibliothèques et du C ++ moderne (par exemple, sur CentOS 6, même C ++ 11 n'est pas entièrement pris en charge).Vieilles habitudes
En changeant d'approches et en passant aux nouvelles technologies, nous avons continué à agir à l'ancienne. Cela a conduit à des difficultés.
Le sujet de battage médiatique avec les microservices ne nous a pas échappé. Nous avons également décidé de diviser l'application en composants de service distincts. Cela a permis de resserrer le contrôle de l'interaction et d'effectuer des tests sur les différentes parties de l'application. Et afin de contrôler l'interaction encore plus étroitement, nous les avons disposés dans des conteneurs, qui, cependant, pouvaient toujours être rassemblés en une seule pile.
Dans une application monolithique, vous pouvez facilement accéder à presque toutes les données. Mais même si vous divisez le produit en plusieurs applications et que vous les laissez à proximité, celles-ci, en tant qu'actives, peuvent former des liens. Par exemple, sous forme de fichiers partagés ou de demandes directes les uns aux autres.
Le passage aux microservices n'a pas été facile. L'ancien schéma consistant à «écrire une bibliothèque et à la connecter là où elle était nécessaire» nous hante depuis un certain temps. Par exemple, nous avons un service chargé d'effectuer des opérations «longues». Initialement, il a été implémenté comme une bibliothèque connectée à l'application qui en avait besoin.
Une autre habitude peut être décrite comme suit: pourquoi écrire un autre service, si vous pouvez enseigner celui-ci existant. La première chose que nous avons sciée de notre monolithe est le mécanisme d'autorisation. Mais alors la tentation a semblé pousser tous les composants communs dans ce service, comme dans COREmanager (le cadre de base pour les produits de cinquième génération).
Partie 2. Combiner l'incompatible
Au début, les demandes de lecture et d'écriture étaient effectuées par un seul processus. En règle générale, les demandes d'écriture bloquent les demandes, mais elles sont très rapides: j'ai écrit dans la base de données, créé une tâche et répondu. Avec une demande de lecture, l'histoire est différente. Créer une tâche dessus est difficile. Il peut générer une réponse assez volumineuse, et que faire de cette réponse si le client ne revient pas après? Combien pour le stocker? Mais en même temps, le traitement des demandes de lecture est parfaitement parallélisé. Ces différences ont entraîné des problèmes de redémarrage de ces processus. Leurs cycles de vie sont tout simplement incompatibles!
Nous avons divisé l'application en deux parties: lecture et écriture. Certes, il est vite devenu clair que ce n'est pas très pratique du point de vue du développement. Lire la liste que vous faites à un endroit, la modifier à un autre. Et voici l'essentiel - n'oubliez pas de réparer le second, si vous changez le premier. Oui, et passer d'un fichier à l'autre est au moins ennuyeux. Par conséquent, nous sommes arrivés à une application qui s'exécute en deux modes: lecture et écriture.
La génération précédente de nos produits utilisait largement les filetages. Mais, comme le montre la pratique, ils ne nous ont pas beaucoup épargnés. En raison des nombreux verrous, la charge sur le CPU dépasse rarement 100%. L'émergence d'un grand nombre de services séparés assez rapides a permis d'abandonner le multithreading au profit du travail asynchrone et mono-thread.
Pendant le processus de développement, nous avons essayé d'utiliser des flux avec asynchronie (boost :: Asio le permet). Mais cette approche apporte très probablement à votre projet toutes les lacunes des deux approches qui ne donnent aucun avantage visible: vous devrez combiner le besoin de contrôle lors de l'accès aux objets partagés et la difficulté d'écrire du code asynchrone.Partie 3. Comme nous avons écrit la boîte et écrit la plate-forme
Tous les services sont organisés dans des conteneurs et fonctionnent à distance avec le serveur client. Et pourquoi alors mettre l'application sur le serveur client? C'est la question que j'ai posée à la direction au moment de conditionner le produit résultant pour l'installation sur le serveur.
Qu'est-ce qu'une plateforme? Tout d'abord, nous avons déployé SaaS, un service qui s'exécute sur nos serveurs et vous permet de configurer votre serveur. Si vous avez utilisé un panneau de contrôle de serveur et l'avez acheté vous-même, c'est la solution pour vous. Mais cela ne convient pas aux prestataires: ils ne sont pas prêts à donner accès aux serveurs de leurs clients à une société tierce, et je les comprends très bien. Cela soulève des questions de sécurité et de tolérance aux pannes. Nous avons donc décidé de leur donner l'intégralité de notre SaaS afin qu'ils puissent le déployer chez eux. C'est comme Amazon, que vous pouvez exécuter dans votre propre centre de données et vous connecter à votre système de facturation. Nous avons appelé cette solution une plate-forme.
Le premier déploiement ne s'est pas déroulé sans heurts. Pour chaque utilisateur actif, nous avons créé un conteneur distinct. Les conteneurs Docker montent rapidement, mais la découverte de service ne fonctionne pas instantanément: elle n'est pas destinée à lever / arrêter dynamiquement des conteneurs en une seconde. Et du moment de la montée au moment où le service peut être utilisé, parfois des minutes passent !!!J'ai déjà écrit que l'hébergeur avait beaucoup changé au cours de la dernière décennie. Imaginez maintenant: il vous achète un hébergement - et accède au shell. WTF?!?! Pour l'utiliser, il devra d'abord trouver un client SSH (dans Windows, cela peut devenir un problème - par défaut, il n'y a pas de client, je suis généralement silencieux sur les clients mobiles).
Après avoir pu entrer dans la console, il doit installer le panneau. Et cette opération n'est pas non plus rapide. Et si quelque chose ne va pas? Par exemple, RosKomNadzor peut bloquer les serveurs à partir desquels les packages de votre système d'exploitation sont téléchargés. Avec de telles erreurs, l'utilisateur sera laissé seul.

Vous pouvez faire valoir que dans la plupart des cas, l'utilisateur reçoit le panneau déjà installé par l'hébergeur, et ce qui précède ne s'applique pas à lui. C'est possible. Mais le panneau exécuté sur le serveur consomme les ressources que vous avez payées (prend de l'espace disque, mange votre processeur et votre mémoire). Ses performances dépendent directement des performances du serveur (le serveur s'est écrasé - le panneau s'est écrasé).
Il y a peut-être encore ceux qui n'utilisent aucun panneau et pensent: cela ne me concerne pas. Mais peut-être que sur votre serveur, si vous en avez un, une sorte de panneau tient toujours et mange des ressources, mais vous ne l’utilisez pas?
"Donc, c'est même merveilleux, vous nous aidez à vendre des ressources", déclarent les hôtes russes. D'autres ajoutent: «Pourquoi dépensons-nous nos ressources à déployer une plate-forme qui consomme plus qu'un panneau autonome?»
Il y a plusieurs réponses à cette question.
- La qualité de service est améliorée: vous pouvez contrôler la version du panneau - la mettre à jour rapidement lorsque de nouvelles fonctionnalités apparaissent ou des erreurs sont détectées; Vous pouvez annoncer des actions directement dans le panneau.
- À l'échelle de l'infrastructure, vous économisez du disque, de la mémoire et un processeur, car certains processus ne sont lancés que pour desservir des utilisateurs actifs et d'autres pour de nombreux clients en même temps.
- Le support n'a pas besoin d'analyser si le comportement est une caractéristique d'une version particulière du panneau ou une erreur. Cela fait gagner du temps.
Beaucoup plus de nos clients ont acheté des licences en packs, puis les ont jonglés, les revendant à leurs clients. Il n'est plus nécessaire de faire cela, en principe, un travail sans signification. Après tout, c'est maintenant un produit.
De plus, nous avons eu l'occasion d'utiliser des solutions lourdes, offrant des fonctionnalités auparavant inaccessibles. Par exemple, un service pour obtenir des captures d'écran d'un site nécessite le lancement d'un chrome sans tête. Je ne pense pas que l'utilisateur sera très heureux si, à cause d'une telle opération, il manque de mémoire et se tire, disons, MySQL.
En conclusion, je tiens à souligner que nous apprenons de l'expérience des autres et que nous développons activement la nôtre. Journalisation, docker, découverte de service, toutes sortes de retards, de nouvelles tentatives et de files d'attente ... Vous ne vous souvenez plus de tout ce que vous aviez à maîtriser ou à réinventer. Tout cela ne facilite pas le développement, mais ouvre de nouvelles opportunités et rend notre travail plus passionnant.
L'histoire n'est pas encore terminée, mais ce qui s'est passé peut être consulté
sur le site Web de Vepp .