Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Beim RIT 2019 hat unser Kollege Alexander Korotkov gemacht Bericht über die Automatisierung der Entwicklung bei CIAN: Um Leben und Arbeiten zu vereinfachen, nutzen wir unsere eigene Integro-Plattform. Es verfolgt den Lebenszyklus von Aufgaben, entlastet Entwickler von Routinearbeiten und reduziert die Anzahl von Fehlern in der Produktion erheblich. In diesem Beitrag ergänzen wir Alexanders Bericht und erzählen Ihnen, wie wir von einfachen Skripten zur Kombination von Open-Source-Produkten über unsere eigene Plattform gekommen sind und was unser separates Automatisierungsteam tut.
 

Null-Niveau

„So etwas wie ein Nullniveau gibt es nicht, so etwas kenne ich nicht“
Meister Shifu aus dem Film „Kung Fu Panda“

Die Automatisierung bei CIAN begann 14 Jahre nach der Firmengründung. Zu diesem Zeitpunkt bestand das Entwicklungsteam aus 35 Personen. Kaum zu glauben, oder? Natürlich gab es Automatisierung in irgendeiner Form, aber 2015 begann sich eine eigene Richtung für kontinuierliche Integration und Codebereitstellung herauszubilden. 

Zu dieser Zeit verfügten wir über einen riesigen Monolithen aus Python, C# und PHP, der auf Linux/Windows-Servern bereitgestellt wurde. Um dieses Monster einzusetzen, hatten wir eine Reihe von Skripten, die wir manuell ausführten. Hinzu kam der Zusammenbau des Monolithen, der aufgrund von Konflikten beim Zusammenführen von Zweigen, beim Beheben von Mängeln und beim Wiederaufbau „mit einer anderen Reihe von Aufgaben im Bau“ Schmerzen und Leid mit sich brachte. Ein vereinfachter Prozess sah so aus:

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Wir waren damit nicht zufrieden und wollten einen wiederholbaren, automatisierten und verwaltbaren Build- und Bereitstellungsprozess aufbauen. Dafür brauchten wir ein CI/CD-System und haben uns zwischen der kostenlosen Version von Teamcity und der kostenlosen Version von Jenkins entschieden, da wir mit ihnen gearbeitet haben und beide vom Funktionsumfang her zu uns passten. Als neueres Produkt haben wir uns für Teamcity entschieden. Zu diesem Zeitpunkt hatten wir noch keine Microservice-Architektur im Einsatz und erwarteten keine große Anzahl an Aufgaben und Projekten.

Wir kommen auf die Idee eines eigenen Systems

Durch die Implementierung von Teamcity wurde nur ein Teil der manuellen Arbeit entfernt: Was bleibt, ist die Erstellung von Pull Requests, die Heraufstufung von Issues nach Status in Jira und die Auswahl von Issues zur Freigabe. Dem war das Teamcity-System nicht mehr gewachsen. Es galt, den Weg der weiteren Automatisierung zu wählen. Wir haben Optionen für die Arbeit mit Skripten in Teamcity oder den Wechsel zu Automatisierungssystemen von Drittanbietern in Betracht gezogen. Aber am Ende haben wir entschieden, dass wir maximale Flexibilität brauchen, die nur unsere eigene Lösung bieten kann. So entstand die erste Version des internen Automatisierungssystems namens Integro.

Teamcity befasst sich mit der Automatisierung auf der Ebene des Starts der Build- und Bereitstellungsprozesse, während Integro sich auf die Automatisierung der Entwicklungsprozesse auf höchster Ebene konzentriert. Es war notwendig, die Arbeit an Vorgängen in Jira mit der Verarbeitung des zugehörigen Quellcodes in Bitbucket zu kombinieren. Zu diesem Zeitpunkt begann Integro mit der Einführung eigener Arbeitsabläufe für die Arbeit mit Aufgaben unterschiedlicher Art. 

Durch die zunehmende Automatisierung von Geschäftsprozessen ist die Anzahl der Projekte und Läufe in Teamcity gestiegen. Es kam also ein neues Problem: Eine kostenlose Teamcity-Instanz reichte nicht aus (3 Agenten und 100 Projekte), wir fügten eine weitere Instanz hinzu (3 weitere Agenten und 100 Projekte) und dann noch eine. Dadurch entstand ein System aus mehreren Clustern, das schwer zu verwalten war:

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Als die Frage nach einer 4. Instanz aufkam, wurde uns klar, dass wir so nicht weiterleben können, da sich die Gesamtkosten für die Betreuung von 4 Instanzen nicht mehr in Grenzen hielten. Es stellte sich die Frage, ob man Teamcity kostenpflichtig kaufen oder sich für kostenloses Jenkins entscheiden soll. Wir haben Berechnungen zu Instanzen und Automatisierungsplänen durchgeführt und beschlossen, dass wir von Jenkins leben würden. Nach ein paar Wochen wechselten wir zu Jenkins und beseitigten einige der Probleme, die mit der Verwaltung mehrerer Teamcity-Instanzen verbunden waren. Daher konnten wir uns auf die Entwicklung von Integro und die individuelle Anpassung von Jenkins konzentrieren.

Mit der Zunahme der grundlegenden Automatisierung (in Form der automatischen Erstellung von Pull-Anfragen, der Erfassung und Veröffentlichung der Codeabdeckung und anderer Prüfungen) besteht ein starker Wunsch, manuelle Releases so weit wie möglich aufzugeben und diese Arbeit Robotern zu überlassen. Darüber hinaus begann das Unternehmen mit der Umstellung auf Microservices innerhalb des Unternehmens, die häufige und voneinander getrennte Releases erforderten. So kamen wir nach und nach zu automatischen Releases unserer Microservices (aktuell veröffentlichen wir den Monolithen aufgrund der Komplexität des Prozesses manuell). Doch wie so oft entstand eine neue Komplexität. 

Wir automatisieren Tests

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Durch die Automatisierung von Releases haben sich die Entwicklungsprozesse beschleunigt, was teilweise auf das Überspringen einiger Testphasen zurückzuführen ist. Und dies führte zu einem vorübergehenden Qualitätsverlust. Es klingt trivial, aber zusammen mit der Beschleunigung der Veröffentlichungen war es notwendig, die Produktentwicklungsmethodik zu ändern. Es war notwendig, über die Automatisierung des Testens nachzudenken, die persönliche Verantwortung (hier geht es darum, die Idee im Kopf anzunehmen, nicht um Geldstrafen) des Entwicklers für den veröffentlichten Code und die darin enthaltenen Fehler sowie die Entscheidung, dies zu tun Eine Aufgabe durch automatische Bereitstellung freigeben/nicht freigeben. 

Um Qualitätsprobleme zu beseitigen, trafen wir zwei wichtige Entscheidungen: Wir begannen mit der Durchführung von Canary-Tests und führten eine automatische Überwachung des Fehlerhintergrunds mit einer automatischen Reaktion auf deren Überschreitung ein. Die erste Lösung ermöglichte es, offensichtliche Fehler zu finden, bevor der Code vollständig in die Produktion freigegeben wurde, die zweite verkürzte die Reaktionszeit auf Probleme in der Produktion. Natürlich passieren Fehler, aber wir verwenden den größten Teil unserer Zeit und Mühe nicht darauf, sie zu korrigieren, sondern sie zu minimieren. 

Automatisierungsteam

Derzeit beschäftigen wir 130 Entwickler und wir machen weiter aufwachsen. Das Team für kontinuierliche Integration und Codebereitstellung (im Folgenden als Deploy and Integration oder DI-Team bezeichnet) besteht aus 7 Personen und arbeitet in zwei Richtungen: Entwicklung der Integro-Automatisierungsplattform und DevOps. 

DevOps ist für die Dev/Beta-Umgebung der CIAN-Site, die Integro-Umgebung, verantwortlich, hilft Entwicklern bei der Lösung von Problemen und entwickelt neue Ansätze zur Skalierung von Umgebungen. Die Integro-Entwicklungsrichtung befasst sich sowohl mit Integro selbst als auch mit zugehörigen Diensten, beispielsweise Plugins für Jenkins, Jira, Confluence, und entwickelt auch Hilfsdienstprogramme und Anwendungen für Entwicklungsteams. 

Das DI-Team arbeitet mit dem Plattformteam zusammen, das die Architektur, Bibliotheken und Entwicklungsansätze intern entwickelt. Gleichzeitig kann jeder Entwickler innerhalb von CIAN zur Automatisierung beitragen, beispielsweise Mikroautomatisierung an die Bedürfnisse des Teams anpassen oder eine coole Idee teilen, wie die Automatisierung noch besser gemacht werden kann.

Torte der Automatisierung bei CIAN

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Alle an der Automatisierung beteiligten Systeme lassen sich in mehrere Schichten unterteilen:

  1. Externe Systeme (Jira, Bitbucket usw.). Entwicklungsteams arbeiten mit ihnen zusammen.
  2. Integro-Plattform. Meistens arbeiten Entwickler nicht direkt damit, aber es ist das, was die gesamte Automatisierung am Laufen hält.
  3. Bereitstellungs-, Orchestrierungs- und Discovery-Dienste (z. B. Jeknins, Consul, Nomad). Mit ihrer Hilfe stellen wir Code auf Servern bereit und stellen sicher, dass Dienste miteinander funktionieren.
  4. Physikalische Schicht (Server, Betriebssystem, zugehörige Software). Unser Code funktioniert auf dieser Ebene. Dies kann entweder ein physischer oder ein virtueller Server (LXC, KVM, Docker) sein.

Basierend auf diesem Konzept teilen wir die Verantwortungsbereiche innerhalb des DI-Teams auf. Die ersten beiden Ebenen liegen im Verantwortungsbereich der Integro-Entwicklungsrichtung, die letzten beiden Ebenen liegen bereits im Verantwortungsbereich von DevOps. Diese Trennung ermöglicht es uns, uns auf die Aufgaben zu konzentrieren und beeinträchtigt die Interaktion nicht, da wir nah beieinander sind und ständig Wissen und Erfahrungen austauschen.

Integration

Konzentrieren wir uns auf Integro und beginnen mit dem Technologie-Stack:

  • CentOS 7
  • Docker + Nomad + Konsul + Vault
  • Java 11 (der alte Integro-Monolith bleibt auf Java 8)
  • Spring Boot 2.X + Spring Cloud-Konfiguration
  • PostgreSql 11
  • RabbitMQ 
  • Apache entzünden
  • Camunda (eingebettet)
  • Grafana + Graphit + Prometheus + Jaeger + ELK
  • Web-Benutzeroberfläche: React (CSR) + MobX
  • SSO: Schlüsselmantel

Wir halten an dem Prinzip der Microservice-Entwicklung fest, obwohl wir über ein Erbe in Form eines Monolithen einer frühen Version von Integro verfügen. Jeder Microservice läuft in seinem eigenen Docker-Container und die Dienste kommunizieren untereinander über HTTP-Anfragen und RabbitMQ-Nachrichten. Microservices finden einander über Consul und stellen eine Anfrage an ihn, indem sie die Autorisierung über SSO (Keycloak, OAuth 2/OpenID Connect) weiterleiten.

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Betrachten Sie als Beispiel aus der Praxis die Interaktion mit Jenkins, die aus den folgenden Schritten besteht:

  1. Der Workflow-Management-Microservice (im Folgenden als Flow-Microservice bezeichnet) möchte einen Build in Jenkins ausführen. Dazu ermittelt er mithilfe von Consul den IP:PORT des Microservices zur Integration mit Jenkins (im Folgenden als Jenkins-Microservice bezeichnet) und sendet eine asynchrone Anfrage an diesen, um den Build in Jenkins zu starten.
  2. Nach Erhalt einer Anfrage generiert der Jenkins-Microservice eine Job-ID und antwortet mit dieser, anhand derer dann das Ergebnis der Arbeit identifiziert werden kann. Gleichzeitig löst es über einen REST-API-Aufruf den Build in Jenkins aus.
  3. Jenkins führt den Build durch und sendet nach Abschluss einen Webhook mit den Ausführungsergebnissen an den Jenkins-Microservice.
  4. Der Jenkins-Mikroservice generiert nach Erhalt des Webhooks eine Nachricht über den Abschluss der Anforderungsverarbeitung und hängt die Ausführungsergebnisse daran an. Die generierte Nachricht wird an die RabbitMQ-Warteschlange gesendet.
  5. Über RabbitMQ erreicht die veröffentlichte Nachricht den Flow-Mikrodienst, der das Ergebnis der Verarbeitung seiner Aufgabe erfährt, indem er die Job-ID aus der Anfrage und der empfangenen Nachricht abgleicht.

Mittlerweile haben wir etwa 30 Microservices, die sich in mehrere Gruppen einteilen lassen:

  1. Konfigurationsmanagement.
  2. Informationen und Interaktion mit Benutzern (Messenger, Mail).
  3. Arbeiten mit Quellcode.
  4. Integration mit Bereitstellungstools (Jenkins, Nomad, Consul usw.).
  5. Überwachung (Releases, Fehler etc.).
  6. Web-Dienstprogramme (Benutzeroberfläche zum Verwalten von Testumgebungen, Sammeln von Statistiken usw.).
  7. Integration mit Task-Trackern und ähnlichen Systemen.
  8. Workflow-Management für verschiedene Aufgaben.

Workflow-Aufgaben

Integro automatisiert Aktivitäten im Zusammenhang mit dem Aufgabenlebenszyklus. Unter dem Lebenszyklus einer Aufgabe wird vereinfacht gesagt der Workflow einer Aufgabe in Jira verstanden. Unsere Entwicklungsprozesse weisen je nach Projekt, Aufgabentyp und den für eine bestimmte Aufgabe ausgewählten Optionen verschiedene Workflow-Varianten auf. 

Schauen wir uns den Workflow an, den wir am häufigsten verwenden:

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Im Diagramm zeigt das Zahnrad an, dass der Übergang automatisch von Integro aufgerufen wird, während die menschliche Figur anzeigt, dass der Übergang manuell von einer Person aufgerufen wird. Sehen wir uns verschiedene Pfade an, die eine Aufgabe in diesem Workflow einschlagen kann.

Vollständig manuelles Testen auf DEV+BETA ohne Canary-Tests (normalerweise veröffentlichen wir so einen Monolithen):

Von Skripten bis zu unserer eigenen Plattform: Wie wir die Entwicklung bei CIAN automatisiert haben

Möglicherweise gibt es noch andere Übergangskombinationen. Manchmal kann der Weg, den ein Problem nehmen soll, über Optionen in Jira ausgewählt werden.

Aufgabenbewegung

Schauen wir uns die Hauptschritte an, die ausgeführt werden, wenn eine Aufgabe den Workflow „DEV-Tests + Canary-Tests“ durchläuft:

1. Der Entwickler oder PM erstellt die Aufgabe.

2. Der Entwickler nimmt die Aufgabe in Angriff. Nach Abschluss wechselt es in den Status IN REVIEW.

3. Jira sendet einen Webhook an den Jira-Microservice (verantwortlich für die Integration mit Jira).

4. Der Jira-Mikrodienst sendet eine Anfrage an den Flow-Dienst (verantwortlich für interne Arbeitsabläufe, in denen Arbeit ausgeführt wird), um den Arbeitsablauf zu starten.

5. Innerhalb des Flow-Dienstes:

  • Der Aufgabe werden Prüfer zugewiesen (Benutzer-Microservice, der alles über Benutzer weiß + Jira-Microservice).
  • Über den Source-Microservice (er kennt sich mit Repositories und Branches aus, funktioniert aber nicht mit dem Code selbst) wird nach Repositories gesucht, die einen Branch unseres Issues enthalten (um die Suche zu vereinfachen, stimmt der Name des Branchs mit dem Issue überein). Nummer in Jira). Meistens verfügt eine Aufgabe nur über einen Zweig in einem Repository. Dies vereinfacht die Verwaltung der Bereitstellungswarteschlange und verringert die Konnektivität zwischen Repositorys.
  • Für jeden gefundenen Zweig wird die folgende Abfolge von Aktionen ausgeführt:

    i) Aktualisieren des Master-Zweigs (Git-Microservice für die Arbeit mit Code).
    ii) Der Zweig ist für Änderungen durch den Entwickler gesperrt (Bitbucket-Mikroservice).
    iii) Für diesen Zweig wird ein Pull Request erstellt (Bitbucket-Mikroservice).
    iv) Eine Nachricht über eine neue Pull-Anfrage wird an Entwickler-Chats gesendet (Notify-Microservice für die Arbeit mit Benachrichtigungen).
    v) Build-, Test- und Bereitstellungsaufgaben werden auf DEV (Jenkins-Microservice für die Arbeit mit Jenkins) gestartet.
    vi) Wenn alle vorherigen Schritte erfolgreich abgeschlossen wurden, fügt Integro seine Genehmigung in den Pull Request (Bitbucket-Mikroservice) ein.

  • Integro wartet auf die Genehmigung im Pull-Request von bestimmten Prüfern.
  • Sobald alle erforderlichen Genehmigungen vorliegen (einschließlich automatisierter Tests, die positiv bestanden wurden), überträgt Integro die Aufgabe in den Status „Test on Dev“ (Jira-Microservice).

6. Tester testen die Aufgabe. Wenn es keine Probleme gibt, wird die Aufgabe in den Status „Ready For Build“ versetzt.

7. Integro „sieht“, dass die Aufgabe zur Veröffentlichung bereit ist, und startet ihre Bereitstellung im Canary-Modus (Jenkins-Microservice). Die Freigabebereitschaft wird durch eine Reihe von Regeln bestimmt. Beispielsweise befindet sich die Aufgabe im erforderlichen Status, es gibt keine Sperren für andere Aufgaben, es gibt derzeit keine aktiven Uploads dieses Microservices usw.

8. Die Aufgabe wird in den Canary-Status (Jira-Microservice) übertragen.

9. Jenkins startet eine Bereitstellungsaufgabe über Nomad im Canary-Modus (normalerweise 1–3 Instanzen) und benachrichtigt den Release-Überwachungsdienst (DeployWatch-Mikroservice) über die Bereitstellung.

10. Der DeployWatch-Microservice sammelt den Fehlerhintergrund und reagiert bei Bedarf darauf. Wenn der Fehlerhintergrund überschritten wird (die Hintergrundnorm wird automatisch berechnet), werden Entwickler über den Notify-Microservice benachrichtigt. Wenn der Entwickler nach 5 Minuten nicht geantwortet hat (auf Zurücksetzen oder Bleiben geklickt hat), wird ein automatisches Rollback der Canary-Instanzen gestartet. Wenn der Hintergrund nicht überschritten wird, muss der Entwickler die Aufgabenbereitstellung in der Produktion manuell starten (durch Klicken auf eine Schaltfläche in der Benutzeroberfläche). Wenn der Entwickler die Bereitstellung in der Produktion nicht innerhalb von 60 Minuten gestartet hat, werden aus Sicherheitsgründen auch die Canary-Instanzen zurückgesetzt.

11. Nach dem Start der Bereitstellung in der Produktion:

  • Die Aufgabe wird in den Produktionsstatus (Jira-Microservice) übertragen.
  • Der Jenkins-Mikrodienst startet den Bereitstellungsprozess und benachrichtigt den DeployWatch-Mikrodienst über die Bereitstellung.
  • Der DeployWatch-Mikrodienst prüft, ob alle Container in der Produktion aktualisiert wurden (es gab Fälle, in denen nicht alle aktualisiert wurden).
  • Über den Notify-Microservice wird eine Benachrichtigung über die Ergebnisse der Bereitstellung an die Produktion gesendet.

12. Entwickler haben 30 Minuten Zeit, um mit dem Rollback einer Aufgabe aus der Produktion zu beginnen, wenn falsches Microservice-Verhalten festgestellt wird. Nach dieser Zeit wird die Aufgabe automatisch in den Master (Git-Microservice) eingebunden.

13. Nach einer erfolgreichen Zusammenführung in den Master wird der Aufgabenstatus in „Geschlossen“ (Jira-Microservice) geändert.

Das Diagramm erhebt keinen Anspruch auf Vollständigkeit (in Wirklichkeit sind es sogar noch mehr Schritte), aber es ermöglicht Ihnen, den Grad der Integration in Prozesse einzuschätzen. Wir halten dieses Schema nicht für ideal und verbessern die Prozesse der automatischen Release- und Deployment-Unterstützung.

Was weiter

Wir haben große Pläne für die Entwicklung der Automatisierung, zum Beispiel die Eliminierung manueller Vorgänge bei Monolith-Releases, die Verbesserung der Überwachung während der automatischen Bereitstellung und die Verbesserung der Interaktion mit Entwicklern.

Aber lassen Sie uns hier zunächst einmal aufhören. Viele Themen haben wir im Automatisierungs-Review nur oberflächlich behandelt, einige wurden gar nicht angesprochen, daher stehen wir gerne für Fragen zur Verfügung. Wir warten auf Vorschläge, was im Detail behandelt werden soll, schreiben Sie in die Kommentare.

Source: habr.com

Kommentar hinzufügen