Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Auf der RIT 2019 präsentierte unser Kollege Alexander Kortkow hält über die Automatisierung der Entwicklung bei CIAN: Um das Leben und die Arbeit zu erleichtern, nutzen wir unsere eigene Plattform Integro. Sie verfolgt den Lebenszyklus von Aufgaben, entlastet die Entwickler von Routinearbeiten und verringert signifikant die Anzahl der Bugs in der Produktion. In diesem Beitrag ergänzen wir Alexanders Vortrag und erzählen, wie wir von einfachen Skripten zur Integration von Open-Source-Produkten über unsere eigene Plattform gelangt sind und was unser spezielles Automatisierungsteam tut.
 

Null-Level

„Null-Level gibt es nicht, davon weiß ich nichts“
Meister Shifu aus dem Zeichentrickfilm „Kung Fu Panda“

Die Automatisierung bei CIAN begann 14 Jahre nach der Gründung des Unternehmens. Damals bestand das Entwicklungsteam aus 35 Personen. Kaum zu glauben, oder? Natürlich gab es in irgendeiner Form bereits Automatisierung, aber die separate Abteilung für kontinuierliche Integration und Codeauslieferung begann sich tatsächlich erst im Jahr 2015 zu bilden. 

Zu diesem Zeitpunkt hatten wir ein großes Monolith-System aus Python, C# und PHP, das auf Linux- und Windows-Servern bereitgestellt wurde. Um dieses Monster zu deployen, hatten wir eine Reihe von Skripten, die wir manuell ausführten. Außerdem gab es den Build des Monolithen, der uns Schmerzen und Leiden bereitete, aufgrund von Konflikten beim Zusammenführen von Branches, Fehlerkorrekturen und dem Neubau mit „einem anderen Satz von Aufgaben im Build“. Vereinfacht dargestellt, sah der Prozess so aus:

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Das entsprach nicht unseren Vorstellungen, und wir wollten einen wiederholbaren, automatisierten und verwalteten Build- und Deployment-Prozess aufbauen. Dazu benötigten wir ein CI/CD-System und wählten zwischen der kostenlosen Version von TeamCity und Jenkins, mit denen wir bereits gearbeitet hatten, und die uns beide hinsichtlich der Funktionalitäten überzeugten. Letztendlich entschieden wir uns für TeamCity als das neuere Produkt. Zu diesem Zeitpunkt nutzten wir noch keine Microservices-Architektur und erwarteten nicht die Vielzahl an Aufgaben und Projekten.

Wir kommen zur Idee eines eigenen Systems

Die Einführung von Teamcity reduzierte nur einen Teil der manuellen Arbeiten: Es blieben die Erstellung von Pull-Requests, das Vorantreiben von Aufgaben in Jira und die Auswahl von Aufgaben für das Release. Mit diesen Anforderungen konnte Teamcity nicht mehr zurechtkommen. Daher war ein weiterer Automatisierungsweg erforderlich. Wir prüften die Optionen für die Arbeit mit Skripten in Teamcity oder den Wechsel zu externen Automatisierungssystemen. Letztendlich entschieden wir uns jedoch für die maximale Flexibilität, die nur eine eigene Lösung bieten kann. So entstand die erste Version unseres internen Automatisierungssystems namens Integro.

Teamcity automatisiert den Prozess der Ausführung von Build- und Deployment-Prozessen, während Integro sich auf die Hochgradautomatisierung der Entwicklungsprozesse konzentriert. Es war notwendig, die Arbeit mit Aufgaben in Jira mit der Verarbeitung des dazugehörigen Codes in Bitbucket zu verbinden. In dieser Phase begannen in Integro eigene Workflows für die Bearbeitung verschiedener Aufgabentypen zu entstehen. 

Mit der zunehmenden Automatisierung in den Geschäftsprozessen ist die Anzahl der Projekte und Runs in TeamCity gestiegen. Dabei trat ein neues Problem auf: Eine kostenlose TeamCity-Instanz war nicht mehr ausreichend (3 Agents und 100 Projekte). Wir haben eine weitere Instanz hinzugefügt (noch 3 Agents und 100 Projekte), und dann noch eine. Letztendlich hatten wir ein System aus mehreren Clustern, das schwer zu verwalten war.

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Als die Frage nach einer vierten Instanz aufkam, wurde uns klar, dass wir so nicht weitermachen konnten, da die Gesamtkosten für die Unterstützung von 4 Instanzen aus dem Rahmen fielen. Es stellte sich die Frage, ob wir TeamCity kostenpflichtig erwerben oder auf das kostenlose Jenkins umsteigen sollten. Wir führten Berechnungen zu den Instanzen und Automatisierungsplänen durch und entschieden uns, mit Jenkins zu arbeiten. Nach ein paar Wochen wechselten wir zu Jenkins und reduzierten einen Teil des Kopfzerbrechens, der mit der Wartung mehrerer TeamCity-Instanzen verbunden war. So konnten wir uns auf die Entwicklung von Integro konzentrieren und Jenkins an unsere Bedürfnisse anpassen.

Mit dem Anstieg der grundlegenden Automatisierung, wie der automatischen Erstellung von Pull Requests, dem Sammeln und Veröffentlichen von Code Coverage sowie anderen Prüfungen, wurde der Wunsch laut, manuelle Releases möglichst abzulehnen und diese Aufgabe Robotern zu überlassen. Darüber hinaus hat im Unternehmen der Umstieg auf Mikrodienste begonnen, die häufige Releases erforderten, und zwar unabhängig voneinander. So sind wir allmählich zu automatisierten Releases unserer Mikrodienste übergegangen (das Monolith-Projekt wird aufgrund des komplexen Prozesses weiterhin manuell herausgegeben). Doch wie es oft der Fall ist, trat eine neue Herausforderung auf. 

Wir automatisieren das Testing

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Durch die Automatisierung der Releases wurden die Entwicklungsprozesse beschleunigt, wobei teilweise auf bestimmte Testphasen verzichtet wurde. Dies führte zu einem temporären Qualitätsverlust. Es mag banal klingen, aber mit der Beschleunigung der Releases musste auch die Produktentwicklungsmethodik überdacht werden. Wir mussten über die Automatisierung von Tests nachdenken, die persönliche Verantwortung der Entwickler für den produzierten Code und die darin enthaltenen Bugs fördern (hierbei geht es um das „Verinnerlichen der Idee“ und nicht um finanzielle Strafen) und über Entscheidungen zum Release/Durchführung von Aufgaben durch automatisiertes Deployment nachdenken. 

Um die Qualitätsprobleme zu beheben, kamen wir zu zwei wichtigen Entscheidungen: Wir führten Kanarientests ein und implementierten ein automatisiertes Fehlermonitoring mit automatisierter Reaktion bei Überschreitung. Die erste Lösung ermöglicht es, offensichtliche Fehler zu erkennen, bevor der Code in die Produktion gelangt, während die zweite die Reaktionszeit auf Probleme in der Produktion verkürzt hat. Fehler treten natürlich auf, aber wir investieren den Großteil unserer Zeit und Energie nicht in deren Behebung, sondern in deren Minimierung. 

Automatisierungsteam

Derzeit haben wir ein Team von 130 Entwicklern und wachsen weiter. wachsen. Das Team für kontinuierliche Integration und Codebereitstellung (im Folgenden als Deploy and Integration Team oder DI bezeichnet) besteht aus 7 Personen und arbeitet in 2 Bereichen: der Entwicklung der Automatisierungsplattform Integro und DevOps. 

DevOps ist verantwortlich für die Dev/Beta-Umgebungen der CIAN-Website sowie die Integro-Umgebungen. Es unterstützt die Entwickler bei der Lösung von Problemen und entwickelt neue Ansätze zur Skalierung dieser Umgebungen. Der Bereich der Integro-Entwicklung befasst sich sowohl mit Integro selbst als auch mit verwandten Diensten, zum Beispiel Plugins für Jenkins, Jira, Confluence, und entwickelt auch unterstützende Tools und Anwendungen für Entwicklerteams. 

Das DI-Team arbeitet eng mit dem Plattformteam zusammen, das für die Entwicklung von Architekturen, Bibliotheken und internen Entwicklungsansätzen verantwortlich ist. Darüber hinaus kann jeder Entwickler bei CIAN zur Automatisierung beitragen, indem er beispielsweise Mikroautomationen für die Bedürfnisse seines Teams erstellt oder coole Ideen zur Verbesserung der Automatisierung teilt.

Der mehrschichtige Ansatz zur Automatisierung bei CIAN

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Alle Systeme, die in der Automatisierung eingesetzt werden, können in mehrere Schichten unterteilt werden:

  1. Externe Systeme (Jira, Bitbucket usw.). Diese werden von den Entwicklungsteams genutzt.
  2. Die Integro-Plattform. In der Regel arbeiten Entwickler nicht direkt damit, aber sie ist die Grundlage aller Automatisierungsprozesse.
  3. Liefer-, Orchestrierungs- und Entdeckungsdienste (zum Beispiel Jenkins, Consul, Nomad). Mit ihrer Hilfe setzen wir Code auf Servern um und gewährleisten die Interaktion der Dienste miteinander.
  4. Physische Ebene (Server, OS, zugehörige Software). Auf dieser Ebene arbeitet unser Code. Dies kann sowohl ein physischer Server als auch ein virtueller sein (LXC, KVM, Docker).

Basierend auf diesem Konzept teilen wir die Aufgabenbereiche innerhalb des DI-Teams. Die ersten beiden Ebenen fallen in den Verantwortungsbereich der Entwicklungsrichtung Integro, während die letzten beiden Ebenen in den Verantwortungsbereich von DevOps fallen. Diese Trennung ermöglicht es, sich auf Aufgaben zu konzentrieren und beeinträchtigt nicht die Zusammenarbeit, da wir nahe beieinander sitzen und ständig Wissen und Erfahrungen austauschen.

Integro

Lassen Sie uns auf Integro konzentrieren und mit dem Technologie-Stack beginnen:

  • CentOs 7
  • Docker + Nomad + Consul + Vault
  • Java 11 (das alte Monolith von Integro bleibt auf Java 8)
  • Spring Boot 2.X + Spring Cloud Config
  • PostgreSql 11
  • RabbitMQ 
  • Apache Ignite
  • Camunda (eingebettet)
  • Grafana + Graphite + Prometheus + Jaeger + ELK
  • Web UI: React (CSR) + MobX
  • SSO: Keycloak

Wir folgen dem Prinzip der Mikroservices, obwohl wir auch ein Legacy-System in Form des frühen Monolithen von Integro haben. Jeder Mikroservice läuft in seinem eigenen Docker-Container, und die Dienste kommunizieren über HTTP-Anfragen und RabbitMQ-Nachrichten. Mikroservices finden sich über Consul und stellen Anfragen, bei denen die Autorisierung über SSO (Keycloak, OAuth 2/OpenID Connect) erfolgt.

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Als konkretes Beispiel betrachten wir die Interaktion mit Jenkins, die aus den folgenden Schritten besteht:

  1. Der Workflow-Management-Mikroservice (nachfolgend Flow-Mikroservice) möchte einen Build in Jenkins starten. Dazu findet er über Consul die IP:PORT des Integrationsmikroservices mit Jenkins (nachfolgend Jenkins-Mikroservice) und sendet an ihn eine asynchrone Anfrage zum Starten des Builds in Jenkins.
  2. Der Jenkins-Mikroservice generiert nach Erhalt der Anfrage eine Job-ID und gibt diese zurück, mit der später das Ergebnis identifiziert werden kann. Gleichzeitig startet er den Build in Jenkins über einen REST-API-Aufruf.
  3. Jenkins führt den Build aus und sendet nach Abschluss der Aktion ein Webhook mit den Ergebnissen an den Jenkins-Mikroservice.
  4. Der Jenkins-Mikroservice empfängt einen Webhook, erstellt eine Nachricht über den Abschluss der Anfrageverarbeitung und fügt die Ausführungsresultate hinzu. Die erstellte Nachricht wird in die RabbitMQ-Warteschlange gesendet.
  5. Durch RabbitMQ gelangt die veröffentlichte Nachricht zum Flow-Mikroservice, der den Verarbeitungsstatus seiner Aufgabe anhand der Job-ID aus der Anfrage und der erhaltenen Nachricht erfährt.

Aktuell haben wir etwa 30 Mikroservices, die in mehrere Gruppen unterteilt werden können:

  1. Verwaltung von Konfigurationen.
  2. Information und Interaktion mit Nutzern (Messenger, E-Mail).
  3. Arbeiten mit Quellcode.
  4. Integration mit Deployment-Tools (Jenkins, Nomad, Consul usw.).
  5. Monitoring (von Releases, Fehlern usw.).
  6. Web-Utilities (UI zur Verwaltung von Testumgebungen, zur Statistiksammlung usw.).
  7. Integration mit Task-Trackern und ähnlichen Systemen.
  8. Workflow-Management für verschiedene Aufgaben.

Aufgaben-Workflow

Integro automatisiert die Vorgänge im Zusammenhang mit dem Lebenszyklus einer Aufgabe. Vereinfacht gesagt, verstehen wir unter dem Lebenszyklus einer Aufgabe den Aufgaben-Workflow in Jira. In unseren Entwicklungsprozessen gibt es mehrere Varianten des Workflows, abhängig vom Projekt, von der Art der Aufgabe und von den in der jeweiligen Aufgabe gewählten Optionen. 

Betrachten wir den Workflow, den wir am häufigsten verwenden:

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Im Diagramm zeigt das Zahnrad, dass die Transition automatisch von Integro ausgelöst wird, während die menschliche Figur bedeutet, dass die Transition manuell durch eine Person ausgelöst wird. Lassen Sie uns einige Wege betrachten, auf denen die Aufgabe in diesem Workflow verlaufen kann.

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

Von Skripten zu einer eigenen Plattform: wie wir die Entwicklung bei CIAN automatisiert haben

Es kann auch andere Kombinationen von Transitionen geben. Manchmal kann der Weg, den die Aufgabe nehmen wird, über Optionen in Jira ausgewählt werden.

Bewegung der Aufgabe

Lassen Sie uns die Hauptschritte betrachten, die bei der Bewegung der Aufgabe durch den Workflow 'Testen auf DEV + Canary-Tests' ausgeführt werden:

1. Der Entwickler oder PM erstellt die Aufgabe.

2. Der Entwickler nimmt die Aufgabe in Arbeit. Nach der Fertigstellung überträgt er sie in den Status IN REVIEW.

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

4. Der Jira-Mikroservice sendet eine Anfrage an den Flow-Service (verantwortlich für interne Workflows, in denen die Arbeit durchgeführt wird), um den Workflow zu starten.

5. Innerhalb des Flow-Services:

  • Es werden Reviewer für die Aufgabe zugewiesen (User-Mikroservice, der alles über die Benutzer weiß + Jira-Mikroservice).
  • Über den Source-Mikroservice (der über Repositories und Branches Bescheid weiß, aber nicht mit dem Code selbst arbeitet) wird nach Repositories gesucht, in denen sich der Branch unserer Aufgabe befindet (zur Vereinfachung entspricht der Name des Branches der Jira-Tasknummer). Häufig hat eine Aufgabe nur einen Branch in einem Repository, was die Verwaltung der Deployment-Warteschlange erleichtert und die Abhängigkeit zwischen den Repositories verringert.
  • Für jeden gefundenen Branch wird die folgende Reihenfolge von Schritten ausgeführt:

    i) Merging des Master-Branches (Git-Mikroservice für die Arbeit mit Code).
    ii) Der Branch wird vom Entwickler vor Änderungen gesperrt (Bitbucket-Mikroservice).
    iii) Es wird ein Pull Request für diesen Branch erstellt (Bitbucket-Mikroservice).
    iv) Eine Nachricht über den neuen Pull Request wird in die Chats der Entwickler gesendet (Notify-Mikroservice für Benachrichtigungen).
    v) Der Build, die Tests und das Deployment der Aufgabe auf DEV werden gestartet (Jenkins-Mikroservice für die Arbeit mit Jenkins).
    vi) Wenn alle vorherigen Punkte erfolgreich abgeschlossen sind, gibt Integro seine Genehmigung im Pull Request (Bitbucket-Mikroservice).

  • Integro wartet auf die Genehmigung im Pull Request von den zugewiesenen Reviewern.
  • Sobald alle erforderlichen Genehmigungen vorliegen (einschließlich erfolgreich durchgeführter automatisierter Tests), überträgt Integro die Aufgabe in den Status 'Test on Dev' (Jira-Mikroservice).

6. Die Tester führen die Tests der Aufgabe durch. Bei keinen Problemen wird die Aufgabe in den Status 'Ready For Build' versetzt.

7. Integro erkennt, dass die Aufgabe bereit für die Veröffentlichung ist, und startet das Deployment im Canary-Modus (Jenkins-Mikroservice). Die Release-Bereitschaft wird durch eine Reihe von Regeln bestimmt. Zum Beispiel muss die Aufgabe im richtigen Status sein, es dürfen keine Blockaden bei anderen Aufgaben vorliegen, und es gibt derzeit keine aktiven Deployments dieses Mikroservices etc.

8. Die Aufgabe wird in den Status 'Canary' überführt (Jira-Mikroservice).

9. Jenkins startet über Nomad das Deployment der Aufgabe im Canary-Modus (normalerweise 1-3 Instanzen) und benachrichtigt den Service zur Überwachung der Releases (DeployWatch-Mikroservice).

10. Der DeployWatch-Mikroservice sammelt Fehlerprotokolle und reagiert darauf, wenn nötig. Bei Überschreitung des Fehlerprotokolls (der Normwert wird automatisch berechnet) werden die Entwickler über den Notify-Mikroservice benachrichtigt. Wenn innerhalb von 5 Minuten kein Entwickler reagiert (auf Revert oder Stay klickt), beginnt der automatische Rollback der Canary-Instanzen. Wenn das Protokoll nicht überschritten wird, muss der Entwickler den Deployment-Vorgang in der Produktion manuell starten (durch Klicken auf die Schaltfläche im UI). Wenn der Entwickler innerhalb von 60 Minuten kein Deployment in der Produktion gestartet hat, werden die Canary-Instanzen ebenfalls aus Sicherheitsgründen zurückgenommen.

11. Nach dem Start des Deployments in der Produktion:

  • Der Task wird auf den Status Produktion (Jira-Mikroservice) umgestellt.
  • Der Jenkins-Mikroservice startet den Deployment-Prozess und informiert den DeployWatch-Mikroservice über die Bereitstellung.
  • Der DeployWatch-Mikroservice überprüft, dass alle Container in der Produktion aktualisiert wurden (es gab Fälle, in denen nicht alles aktualisiert wurde).
  • Über den Notify-Mikroservice wird eine Benachrichtigung über die Ergebnisse des Deployments in der Produktion gesendet.

12. Entwickler haben 30 Minuten Zeit, um im Fall von fehlerhaftem Verhalten eines Mikrodienstes einen Rollback des Produkts zu initiieren. Nach Ablauf dieser Frist wird die Aufgabe automatisch in den Master (Git-Mikrodienst) überführt.

13. Nach erfolgreich abgeschlossenem Merge in den Master wird der Status der Aufgabe auf "Closed" (Jira-Mikrodienst) geändert.

Das Diagramm beansprucht nicht, vollständig detailliert zu sein (tatsächlich gibt es noch mehr Schritte), bietet jedoch die Möglichkeit, den Integrationsgrad in die Prozesse zu bewerten. Wir halten dieses Diagramm nicht für perfekt und verbessern die automatisierten Begleitprozesse für Releases und Deployments.

Was kommt als Nächstes

Wir haben große Pläne für die Weiterentwicklung der Automatisierung, beispielsweise den Verzicht auf manuelle Operationen bei Monolith-Releases, die Verbesserung der Überwachung bei automatischen Deployments und die Optimierung der Zusammenarbeit mit Entwicklern.

Aber hier wollen wir vorerst stoppen. Viele Themen im Überblick zur Automatisierung haben wir nur oberflächlich angesprochen, einige gar nicht behandelt, daher beantworten wir gerne Fragen. Wir freuen uns über Vorschläge, was wir detailliert beleuchten sollen, schreiben Sie in die Kommentare.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster