Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Au RIT 2019, notre collègue Alexander Korotkov a réalisé rapport à propos de l'automatisation du développement au CIAN : pour simplifier la vie et le travail, nous utilisons notre propre plateforme Integro. Il suit le cycle de vie des tâches, soulage les développeurs des opérations de routine et réduit considérablement le nombre de bugs en production. Dans cet article, nous compléterons le rapport d'Alexander et vous expliquerons comment nous sommes passés de simples scripts à la combinaison de produits open source via notre propre plate-forme et ce que fait notre équipe d'automatisation distincte.
 

Niveau zéro

"Il n'y a pas de niveau zéro, je n'en connais pas"
Maître Shifu du film "Kung Fu Panda"

L'automatisation chez CIAN a commencé 14 ans après la création de l'entreprise. A cette époque, l'équipe de développement comptait 35 personnes. Difficile à croire, non ? Bien sûr, l’automatisation existait sous une forme ou une autre, mais une direction distincte pour l’intégration continue et la livraison de code a commencé à prendre forme en 2015. 

A cette époque, nous avions un énorme monolithe de Python, C# et PHP, déployé sur des serveurs Linux/Windows. Pour déployer ce monstre, nous disposions d'un ensemble de scripts que nous exécutions manuellement. Il y a aussi eu l’assemblage du monolithe, qui a apporté douleur et souffrance en raison de conflits lors de la fusion des branches, de la correction des défauts et de la reconstruction « avec un ensemble de tâches différentes dans la construction ». Un processus simplifié ressemblait à ceci :

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Cela ne nous satisfaisait pas et nous souhaitions créer un processus de création et de déploiement reproductible, automatisé et gérable. Pour cela, nous avions besoin d'un système CI/CD, et nous avons choisi entre la version gratuite de Teamcity et la version gratuite de Jenkins, car nous avons travaillé avec elles et les deux nous convenaient en termes d'ensemble de fonctions. Nous avons choisi Teamcity comme produit plus récent. À cette époque, nous n’avions pas encore utilisé l’architecture des microservices et ne nous attendions pas à un grand nombre de tâches et de projets.

Nous arrivons à l'idée de notre propre système

L'implémentation de Teamcity n'a supprimé qu'une partie du travail manuel : il ne reste plus que la création des Pull Requests, la promotion des tickets par statut dans Jira et la sélection des tickets à publier. Le système Teamcity ne pouvait plus faire face à cela. Il fallait choisir la voie d’une automatisation plus poussée. Nous avons envisagé des options permettant de travailler avec des scripts dans Teamcity ou de passer à des systèmes d'automatisation tiers. Mais nous avons finalement décidé qu’il nous fallait une flexibilité maximale, que seule notre propre solution peut offrir. C'est ainsi qu'est apparue la première version du système d'automatisation interne appelée Integro.

Teamcity s'occupe de l'automatisation au niveau du lancement des processus de construction et de déploiement, tandis qu'Integro se concentre sur l'automatisation de haut niveau des processus de développement. Il était nécessaire de combiner le travail sur les tickets dans Jira avec le traitement du code source associé dans Bitbucket. À ce stade, Integro a commencé à disposer de ses propres flux de travail pour travailler sur des tâches de différents types. 

En raison de l'automatisation croissante des processus métier, le nombre de projets et d'exécutions dans Teamcity a augmenté. Un nouveau problème est donc apparu : une instance Teamcity gratuite ne suffisait pas (3 agents et 100 projets), nous avons ajouté une autre instance (3 agents supplémentaires et 100 projets), puis une autre. Du coup, on se retrouvait avec un système de plusieurs clusters, difficile à gérer :

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Lorsque la question d’une 4ème instance s’est posée, nous avons réalisé que nous ne pouvions pas continuer à vivre ainsi, car le coût total du support de 4 instances n’était plus dans aucune limite. La question s'est posée de savoir comment acheter Teamcity payant ou choisir Jenkins gratuit. Nous avons fait des calculs sur les instances et les plans d'automatisation et avons décidé que nous vivrions sur Jenkins. Après quelques semaines, nous sommes passés à Jenkins et avons éliminé certains des maux de tête associés à la maintenance de plusieurs instances Teamcity. Nous avons donc pu nous concentrer sur le développement d'Integro et la personnalisation de Jenkins pour nous-mêmes.

Avec la croissance de l'automatisation de base (sous forme de création automatique de Pull Requests, de collecte et de publication de la couverture de Code et d'autres contrôles), il existe une forte volonté d'abandonner autant que possible les versions manuelles et de confier ce travail aux robots. En outre, l'entreprise a commencé à migrer vers des microservices au sein de l'entreprise, qui nécessitaient des versions fréquentes et séparées les unes des autres. C'est ainsi que nous sommes progressivement arrivés à des versions automatiques de nos microservices (nous publions actuellement le monolithe manuellement en raison de la complexité du processus). Mais, comme c’est souvent le cas, une nouvelle complexité est apparue. 

Nous automatisons les tests

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

En raison de l'automatisation des versions, les processus de développement se sont accélérés, en partie à cause du saut de certaines étapes de test. Et cela a entraîné une perte temporaire de qualité. Cela semble anodin, mais parallèlement à l'accélération des sorties, il a fallu changer la méthodologie de développement des produits. Il a fallu penser à l'automatisation des tests, en inculquant la responsabilité personnelle (on parle ici d'"accepter l'idée dans la tête", pas d'amendes monétaires) du développeur pour le code publié et les bugs qu'il contient, ainsi que la décision de libérer/ne pas libérer une tâche via le déploiement automatique. 

En éliminant les problèmes de qualité, nous avons pris deux décisions importantes : nous avons commencé à effectuer des tests Canary et introduit une surveillance automatique des erreurs de fond avec une réponse automatique en cas d'excès. La première solution a permis de détecter des erreurs évidentes avant que le code ne soit entièrement mis en production, la seconde a réduit le temps de réponse aux problèmes de production. Des erreurs, bien sûr, se produisent, mais nous consacrons la plupart de notre temps et de nos efforts non pas à les corriger, mais à les minimiser. 

Équipe d'automatisation

Nous avons actuellement une équipe de 130 développeurs et nous continuons grandir. L'équipe d'intégration continue et de livraison de code (ci-après dénommée équipe Déploiement et Intégration ou DI) est composée de 7 personnes et travaille dans 2 directions : développement de la plateforme d'automatisation Integro et DevOps. 

DevOps est responsable de l'environnement Dev/Beta du site CIAN, l'environnement Integro, aide les développeurs à résoudre les problèmes et développe de nouvelles approches pour faire évoluer les environnements. La direction de développement d'Integro s'occupe à la fois d'Integro lui-même et des services associés, par exemple des plugins pour Jenkins, Jira, Confluence, et développe également des utilitaires et des applications auxiliaires pour les équipes de développement. 

L'équipe DI travaille en collaboration avec l'équipe Platform, qui développe l'architecture, les bibliothèques et les approches de développement en interne. Dans le même temps, n'importe quel développeur au sein du CIAN peut contribuer à l'automatisation, par exemple en créant une micro-automatisation adaptée aux besoins de l'équipe ou en partageant une idée intéressante sur la façon de rendre l'automatisation encore meilleure.

Gâteau étagé d’automatisation au CIAN

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Tous les systèmes impliqués dans l'automatisation peuvent être divisés en plusieurs couches :

  1. Systèmes externes (Jira, Bitbucket, etc.). Les équipes de développement travaillent avec eux.
  2. Plateforme Intégro. Le plus souvent, les développeurs ne travaillent pas directement avec lui, mais c'est ce qui permet à toute l'automatisation de fonctionner.
  3. Services de livraison, d'orchestration et de découverte (par exemple, Jeknins, Consul, Nomad). Avec leur aide, nous déployons du code sur les serveurs et veillons à ce que les services fonctionnent entre eux.
  4. Couche physique (serveurs, OS, logiciels associés). Notre code opère à ce niveau. Il peut s'agir d'un serveur physique ou virtuel (LXC, KVM, Docker).

Sur la base de ce concept, nous répartissons les domaines de responsabilité au sein de l'équipe DI. Les deux premiers niveaux relèvent du domaine de responsabilité de la direction de développement Integro, et les deux derniers niveaux relèvent déjà du domaine de responsabilité de DevOps. Cette séparation nous permet de nous concentrer sur les tâches et n'interfère pas avec l'interaction, puisque nous sommes proches les uns des autres et échangeons constamment des connaissances et des expériences.

integro

Concentrons-nous sur Integro et commençons par la pile technologique :

  • CentOs 7
  • Docker + Nomade + Consul + Vault
  • Java 11 (l'ancien monolithe Integro restera sur Java 8)
  • Spring Boot 2.X + configuration Spring Cloud
  • PostgreSql 11
  • RabbitMQ 
  • Apache Ignite
  • Camunda (intégré)
  • Grafana + Graphite + Prométhée + Jaeger + ELK
  • Interface utilisateur Web : React (CSR) + MobX
  • SSO : keycloak

Nous adhérons au principe du développement de microservices, même si nous disposons d'un héritage sous la forme d'un monolithe d'une première version d'Integro. Chaque microservice s'exécute dans son propre conteneur Docker et les services communiquent entre eux via des requêtes HTTP et des messages RabbitMQ. Les microservices se trouvent via Consul et lui font une demande, en passant l'autorisation via SSO (Keycloak, OAuth 2/OpenID Connect).

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

À titre d'exemple concret, envisagez d'interagir avec Jenkins, qui comprend les étapes suivantes :

  1. Le microservice de gestion de workflow (ci-après dénommé microservice Flow) souhaite exécuter une build dans Jenkins. Pour ce faire, il utilise Consul pour trouver l'IP:PORT du microservice à intégrer avec Jenkins (ci-après dénommé microservice Jenkins) et lui envoie une requête asynchrone pour démarrer la build dans Jenkins.
  2. Après avoir reçu une demande, le microservice Jenkins génère et répond avec un ID de tâche, qui peut ensuite être utilisé pour identifier le résultat du travail. En même temps, il déclenche la construction dans Jenkins via un appel API REST.
  3. Jenkins effectue la construction et, une fois terminée, envoie un webhook avec les résultats de l'exécution au microservice Jenkins.
  4. Le microservice Jenkins, après avoir reçu le webhook, génère un message concernant l'achèvement du traitement de la demande et y joint les résultats de l'exécution. Le message généré est envoyé à la file d'attente RabbitMQ.
  5. Grâce à RabbitMQ, le message publié atteint le microservice Flow, qui apprend le résultat du traitement de sa tâche en faisant correspondre l'ID de tâche de la demande et le message reçu.

Nous disposons désormais d'une trentaine de microservices, qui peuvent être divisés en plusieurs groupes :

  1. Gestion de la configuration.
  2. Information et interaction avec les utilisateurs (messagers, mail).
  3. Travailler avec le code source.
  4. Intégration avec les outils de déploiement (jenkins, nomad, consul, etc.).
  5. Surveillance (releases, erreurs, etc.).
  6. Utilitaires Web (UI pour la gestion des environnements de tests, la collecte de statistiques, etc.).
  7. Intégration avec des trackers de tâches et des systèmes similaires.
  8. Gestion du workflow pour différentes tâches.

Tâches de flux de travail

Integro automatise les activités liées au cycle de vie des tâches. En termes simplifiés, le cycle de vie d'une tâche sera compris comme le workflow d'une tâche dans Jira. Nos processus de développement comportent plusieurs variantes de flux de travail en fonction du projet, du type de tâche et des options sélectionnées dans une tâche particulière. 

Regardons le workflow que nous utilisons le plus souvent :

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Dans le diagramme, l'engrenage indique que la transition est appelée automatiquement par Integro, tandis que la figure humaine indique que la transition est appelée manuellement par une personne. Examinons plusieurs chemins qu'une tâche peut emprunter dans ce flux de travail.

Tests entièrement manuels sur DEV+BETA sans tests Canary (c'est généralement ainsi que nous publions un monolithe) :

Des scripts à notre propre plateforme : comment nous avons automatisé le développement au CIAN

Il peut y avoir d'autres combinaisons de transitions. Parfois, le chemin qu'empruntera un problème peut être sélectionné via les options de Jira.

Mouvement de tâche

Examinons les principales étapes effectuées lorsqu'une tâche passe par le workflow « DEV Testing + Canary Tests » :

1. Le développeur ou PM crée la tâche.

2. Le développeur se charge de la tâche. Une fois terminé, il passe au statut EN REVUE.

3. Jira envoie un Webhook au microservice Jira (responsable de l'intégration avec Jira).

4. Le microservice Jira envoie une demande au service Flow (responsable des workflows internes dans lesquels le travail est effectué) pour démarrer le workflow.

5. Dans le service Flow :

  • Des réviseurs sont affectés à la tâche (microservice Utilisateurs qui sait tout sur les utilisateurs + microservice Jira).
  • Grâce au microservice Source (il connaît les référentiels et les branches, mais ne fonctionne pas avec le code lui-même), une recherche est effectuée sur les référentiels contenant une branche de notre numéro (pour simplifier la recherche, le nom de la branche coïncide avec le numéro numéro dans Jira). Le plus souvent, une tâche ne possède qu'une seule branche dans un référentiel ; cela simplifie la gestion de la file d'attente de déploiement et réduit la connectivité entre les référentiels.
  • Pour chaque branche trouvée, la séquence d'actions suivante est effectuée :

    i) Mise à jour de la branche master (microservice Git pour travailler avec du code).
    ii) La branche est bloquée contre les modifications par le développeur (microservice Bitbucket).
    iii) Une Pull Request est créée pour cette branche (microservice Bitbucket).
    iv) Un message concernant une nouvelle Pull Request est envoyé aux discussions des développeurs (microservice Notify pour travailler avec les notifications).
    v) Les tâches de construction, de test et de déploiement sont démarrées sur DEV (microservice Jenkins pour travailler avec Jenkins).
    vi) Si toutes les étapes précédentes sont terminées avec succès, Integro place son approbation dans la demande d'extraction (microservice Bitbucket).

  • Integro attend l'approbation dans la demande d'extraction des réviseurs désignés.
  • Dès que toutes les approbations nécessaires ont été reçues (y compris les tests automatisés réussis), Integro transfère la tâche au statut Test on Dev (microservice Jira).

6. Les testeurs testent la tâche. S'il n'y a aucun problème, la tâche est transférée au statut Prêt pour la construction.

7. Integro « voit » que la tâche est prête à être publiée et démarre son déploiement en mode Canary (microservice Jenkins). La préparation à la publication est déterminée par un ensemble de règles. Par exemple, la tâche est dans l'état requis, il n'y a aucun verrou sur d'autres tâches, il n'y a actuellement aucun téléchargement actif de ce microservice, etc.

8. La tâche est transférée au statut Canary (microservice Jira).

9. Jenkins lance une tâche de déploiement via Nomad en mode Canary (généralement 1 à 3 instances) et informe le service de surveillance des versions (microservice DeployWatch) du déploiement.

10. Le microservice DeployWatch collecte l'arrière-plan de l'erreur et y réagit, si nécessaire. Si le fond d'erreur est dépassé (la norme de fond est calculée automatiquement), les développeurs sont avertis via le microservice Notify. Si au bout de 5 minutes le développeur n'a pas répondu (cliqué sur Revert ou Stay), alors un rollback automatique des instances Canary est lancé. Si l'arrière-plan n'est pas dépassé, alors le développeur doit lancer manuellement le déploiement de la tâche en Production (en cliquant sur un bouton dans l'UI). Si dans les 60 minutes le développeur n'a pas lancé le déploiement en production, alors les instances Canary seront également restaurées pour des raisons de sécurité.

11. Après avoir lancé le déploiement en Production :

  • La tâche est transférée en statut Production (microservice Jira).
  • Le microservice Jenkins démarre le processus de déploiement et informe le microservice DeployWatch du déploiement.
  • Le microservice DeployWatch vérifie que tous les conteneurs en production ont été mis à jour (il y a eu des cas où tous n'ont pas été mis à jour).
  • Grâce au microservice Notify, une notification sur les résultats du déploiement est envoyée à Production.

12. Les développeurs disposeront de 30 minutes pour commencer à restaurer une tâche de production si un comportement incorrect du microservice est détecté. Passé ce délai, la tâche sera automatiquement fusionnée dans master (microservice Git).

13. Après une fusion réussie avec le maître, le statut de la tâche passera à Fermé (microservice Jira).

Le schéma n'a pas la prétention d'être complètement détaillé (en réalité il y a encore plus d'étapes), mais il permet d'évaluer le degré d'intégration dans les processus. Nous ne considérons pas ce schéma idéal et améliorons les processus de prise en charge automatique de la publication et du déploiement.

Quelle est la prochaine

Nous avons de grands projets pour le développement de l'automatisation, par exemple en éliminant les opérations manuelles lors des versions monolithiques, en améliorant la surveillance lors du déploiement automatique et en améliorant l'interaction avec les développeurs.

Mais arrêtons-nous ici pour l'instant. Nous avons abordé de nombreux sujets dans l'examen de l'automatisation de manière superficielle, certains n'ont pas été abordés du tout, nous serons donc heureux de répondre aux questions. Nous attendons des suggestions sur ce qu'il faut couvrir en détail, écrivez dans les commentaires.

Source: habr.com

Ajouter un commentaire