Es gibt einen kritischen Zeitraum im Veröffentlichungszyklus des Dienstes - von dem Moment an, in dem eine neue Version vorbereitet wird, bis zu dem Moment, in dem sie den Benutzern zur Verfügung steht. Teamaktionen zwischen diesen beiden Kontrollpunkten sollten von Release zu Release konsistent und nach Möglichkeit automatisiert sein. In seinem Bericht beschrieb der Alberist von Sergey Pomazanov die Prozesse, die jeder Yandex.Taxi-Poolanforderung folgen.
- Guten Abend! Mein Name ist Sergey, ich bin der Leiter der Automatisierungsgruppe bei Yandex.Taxi. Kurz gesagt, die Hauptaufgabe unserer Gruppe besteht darin, den Zeitaufwand für die Lösung ihrer Probleme zu minimieren. Dies umfasst alles von CI bis hin zu Entwicklungs- und Testprozessen.
Was macht unsere Entwicklung, wenn der Code geschrieben wird?
Um die neue Funktionalität zu testen, überprüfen wir zunächst alles lokal. Für lokale Tests haben wir eine große Anzahl von Tests. Wenn ein neuer Code angezeigt wird, muss dieser ebenfalls mit Tests abgedeckt werden.

Unsere Testabdeckung ist nicht so gut, wie wir möchten, aber wir versuchen, sie auf einem ausreichenden Niveau zu halten.
Zum Testen verwenden wir Google Test und ein selbst geschriebenes Pytest-Framework, mit dem wir nicht nur den "Python" -Teil, sondern auch den "Plus" -Teil testen. Mit unserem Framework können Sie vor jedem Test Dienste starten, Daten in die Datenbank hochladen, Caches aktualisieren, alle externen Anforderungen löschen usw. Ein ausreichend funktionierendes Framework, mit dem Sie alles nach Belieben ausführen und sperren können, damit wir nicht versehentlich welche erhalten Anfragen außerhalb.
Zusätzlich zu Funktionstests bieten wir Integrationstests an. Sie ermöglichen es Ihnen, ein anderes Problem zu lösen. Wenn Sie nicht sicher sind, ob Ihr Dienst ordnungsgemäß mit anderen Diensten interagiert, können Sie den Stand ausführen und eine Reihe von Tests ausführen. Bisher haben wir eine Reihe grundlegender Tests, die sich jedoch langsam erweitern.
Der Stand basiert auf der Technologie von Docker und Docker Compose, bei der jeder Container seine eigenen Dienste hat und alle miteinander interagieren. Dies geschieht in einer isolierten Umgebung. Sie haben ein eigenes isoliertes Netzwerk, eine eigene Datenbank und einen eigenen Datensatz. Und die Tests bestehen so, als würde jemand eine mobile Anwendung starten, auf Schaltflächen klicken und eine Bestellung aufgeben. In diesem Moment fahren die virtuellen Autos, bringen den Passagier, dann wird das Geld vom Passagier abgebucht und so weiter. Grundsätzlich erfordern alle Tests die Interaktion vieler Dienste und Komponenten gleichzeitig.
Natürlich testen wir nur unsere Services und nur unsere Komponenten, da wir keine externen Services testen sollten und alles extern benetzen.
Der Stand war praktisch genug, um vor Ort zu fahren und ein Taschentaxi herauszuholen. Sie können diesen Standpunkt einnehmen, ihn auf einem lokalen Computer oder auf einer virtuellen Maschine oder einer anderen Entwicklungsmaschine ausführen. Nachdem Sie den Stand gestartet haben, können Sie eine für ein Taschentaxi angepasste mobile Anwendung nehmen, auf Ihrem Computer konfigurieren und Bestellungen aufgeben. Alles ist genau das gleiche wie in der Produktion oder anderswo. Wenn Sie die neue Funktionalität testen müssen, können Sie einfach Ihren Code eingeben. Er wird in der gesamten Umgebung abgerufen und ausgeführt.
Auch hier können Sie einfach den gewünschten Dienst ausführen. Dazu müssen Sie die Datenbank erhöhen, mit den erforderlichen Inhalten füllen oder eine Basis aus vorhandenen Umgebungen entnehmen und mit dem Dienst verbinden. Und dann können Sie ihn einfach kontaktieren, einige Fragen stellen und prüfen, ob es richtig funktioniert oder nicht.
Ein weiterer wichtiger Punkt ist die Stilprüfung. Wenn für die „Pluspunkte“ alles einfach ist, verwenden wir das Clang-Format und prüfen, ob der Code mit ihm übereinstimmt oder nicht. Für Python verwenden wir bis zu vier Analysatoren: Flake8, Pylint, Mypy und anscheinend autopep8.
Wir verwenden diese Analysegeräte hauptsächlich in der Standardlieferung. Wenn die Möglichkeit besteht, einen Designstil zu wählen, verwenden wir den Google-Stil. Das einzige, was wir durch Hinzufügen unserer eigenen korrigiert haben, ist eine Überprüfung der Sortierung der Importe, damit die Importe korrekt sortiert werden.
Nachdem Sie den lokal überprüften Code erstellt haben, können Sie eine Poolanforderung ausführen. Pool-Anfragen werden auf GitHub erstellt.

Das Erstellen einer Poolanforderung auf GitHub bietet TeamCity viele Möglichkeiten. TeamCity führt automatisch alle oben genannten Tests aus, überprüft sie automatisch und in der Poolanforderung selbst wird über den Status der Passage geschrieben, ob die Tests bestanden wurden oder nicht. Das heißt, ohne TeamCity zu besuchen, können Sie sehen, ob es bestanden hat oder nicht, und indem Sie auf den Link klicken, um zu verstehen, was schief gelaufen ist und was behoben werden muss.
Wenn Sie nicht über genügend Taschen-Taxis und -Tests verfügen und die tatsächliche Interaktion mit einem echten Service überprüfen möchten, verfügen wir über eine Testumgebung, die die Produktion wiederholt. Wir haben zwei dieser Testumgebungen. Eine ist für die mobile Entwicklung von Testern und die zweite für Entwickler. Die Testumgebung ist so produktionsnah wie möglich. Wenn Anforderungen an externe Services gestellt werden, werden diese auch aus Testumgebungen gestellt. Die einzige Einschränkung besteht darin, dass die Testumgebung nach Möglichkeit externe Ressourcen testet. Und die Produktionsumgebung geht in Produktion.
Mehr über die Testumgebung, wir machen das ganz einfach über TeamCity. Es ist notwendig, das entsprechende Label auf GitHub zu platzieren und nach dem Festlegen auf die Schaltfläche "Benutzerdefiniert sammeln" zu klicken. Also nennen wir es. Dann werden alle Poolanforderungen mit diesem Label konkurriert, und dann beginnt die automatische Zusammenstellung von Paketen mit Clustering.
Zusätzlich zu Routinetests sind manchmal Lasttests erforderlich. Wenn Sie Code bearbeiten, der Teil eines hoch geladenen Dienstes ist, können wir hierfür Lasttests durchführen. In Python gibt es nur wenige hoch geladene Dienste, von denen einige in C ++ neu geschrieben wurden. Trotzdem bleiben sie bestehen, manchmal gibt es einen Ort, an dem man sein kann. Lasttests finden über das Lunapark-System statt. Es verwendet Yandex.Tank, es ist frei verfügbar, Sie können es herunterladen und ansehen. Mit dem Tank können Sie bei einem Dienst feuern, Diagramme erstellen, verschiedene Lademethoden ausführen und anzeigen, welche Last derzeit auf dem Dienst war und welche Ressourcen verwendet wurden. Es reicht aus, über TeamCity auf die Schaltfläche zu klicken, das Paket wird abgeholt und kann dann bei Bedarf gerollt werden. Oder füllen Sie es einfach manuell aus und führen Sie es dort aus.

Während Sie Ihren Code testen, kann einer der Entwickler in diesem Moment beginnen, sich Ihren Code anzusehen und ihn zu überprüfen.
Worauf wir dabei achten:

Einer der wichtigen Punkte - die Funktionalität muss deaktiviert sein. Dies bedeutet, dass unabhängig davon, um welchen Code es sich handelt, Fehler darin vorhanden waren oder nicht. Vielleicht funktioniert diese Funktionalität nicht so, wie sie ursprünglich beabsichtigt war. Vielleicht wollten die Manager etwas anderes, oder vielleicht versucht diese Funktionalität, einen anderen Dienst bereitzustellen, der nicht bereit war zu neuen Lasten, und Sie müssen die Fähigkeit haben, es schnell auszuschalten und alles in einen normalen Zustand zu versetzen.
Wir haben auch die Regel, dass beim Rollout neue Funktionen deaktiviert und erst aktiviert werden sollten, nachdem sie in allen Clustern und allen Rechenzentren eingeführt wurden.
Vergessen Sie nicht, dass wir eine API haben, die von mobilen Anwendungen verwendet wird, die möglicherweise längere Zeit nicht aktualisiert werden. Wenn wir einige rückwärts inkompatible Änderungen in unserer API vornehmen, können einige Anwendungen ausfallen und wir können nicht alle Anwendungen zwingen, nur herunterzuladen und zu aktualisieren. Dies wird sich negativ auf unseren Ruf auswirken. Daher müssen alle neuen Funktionen abwärtskompatibel sein. Dies gilt nicht nur für die externe API, sondern auch für die interne API, da Sie nicht gleichzeitig den gesamten Code für alle Rechenzentren, für alle Computer und für alle Cluster bereitstellen können. In jedem Fall werden sowohl der alte als auch der neue Code gleichzeitig bei uns leben. Infolgedessen erhalten wir einige Abfragen, die nicht irgendwo verarbeitet werden können, und es treten Fehler auf.
Sie sollten auch über das Nächste nachdenken: Wenn Ihr Code plötzlich nicht mehr funktioniert oder Sie einen neuen Microservice geschrieben haben, in dem potenzielle Probleme auftreten, müssen Sie auf die Folgen vorbereitet sein und sich verschlechtern können. Mein Kollege wird bei der nächsten Präsentation darüber sprechen.
Wenn Sie Änderungen an hoch ausgelasteten Diensten vornehmen und nicht auf das Ende einiger Vorgänge warten müssen, können Sie einige Dinge asynchron irgendwo im Hintergrund oder als separaten Prozess ausführen. Dies ist besser, da ein separater Prozess weniger Auswirkungen auf die Produktion hat und das System insgesamt stabiler arbeitet.

Es ist auch wichtig, dass alle Daten, die wir von außen erhalten, wir ihnen nicht vertrauen, sie validieren, irgendwie verifizieren usw. Alle Daten, die wir haben, sollten in Gruppen unterteilt werden, die wir gebildet haben oder Rohdaten, die die Validierung nicht bestanden haben. Dies schließt alle Daten ein, die möglicherweise von anderen externen Diensten oder direkt von Benutzern abgerufen werden könnten, da möglicherweise alles kommen könnte. Vielleicht hat jemand speziell eine böswillige Anfrage gesendet, und alles sollte mit uns besprochen werden.

Es gibt immer noch Fälle, in denen der Dienst auf Anfrage möglicherweise nicht zum richtigen Zeitpunkt reagiert. Möglicherweise ist die Verbindung unterbrochen oder es ist ein Fehler aufgetreten. Es kann viele Situationen geben. Die mobile Anwendung weiß nicht, was letztendlich passiert ist, sondern führt lediglich eine erneute Anforderung durch.
Es ist sehr wichtig, dass bei diesen erneuten Anfragen, egal wie viele es sind, am Ende alles wie ursprünglich erwartet mit einer einzigen Anfrage funktioniert. Wir sollten keine Spezialeffekte haben. Es sollte auch berücksichtigt werden, dass wir mehr als einen Service haben, viele Maschinen, viele Rechenzentren, verteilte Stützpunkte und Rennen für alle möglich sind. Der Code sollte so geschrieben sein, dass er nicht an mehreren Stellen gleichzeitig ausgeführt wird, damit wir keine Rennen haben.
Ein ebenso wichtiger Punkt ist die Fähigkeit, Probleme zu diagnostizieren. In allem gibt es immer Probleme, und Sie müssen verstehen, wo sie aufgetreten sind. Im Idealfall wurde die Existenz des Problems nicht durch den Support-Service, sondern durch Überwachung ermittelt. Und während wir einige Situationen analysierten, konnten wir schließlich verstehen, was passiert ist, indem wir einfach die Protokolle lesen, ohne den Code zu lesen. Sogar die Person, die den Code noch nie gesehen hat, damit er ihn am Ende durch die Protokolle bekommen kann.
Und im Idealfall, wenn die Situation sehr kompliziert ist, müssen Sie anhand der Protokolle überprüfen können, in welche Richtung das Programm am Ende gelaufen ist und was die Nachbesprechung erheblich vereinfacht hat. Da die Situation in der Vergangenheit so verlaufen ist und es jetzt unwahrscheinlich ist, dass sie reproduziert werden kann, gibt es bereits keine Daten oder andere Daten oder andere Situationen.
Wenn Sie neue Vorgänge in der Datenbank ausführen oder einen neuen erstellen, müssen Sie berücksichtigen, dass viele Daten vorhanden sein können. Vielleicht schreiben Sie unendlich viele Datensätze in diese Datenbank, und wenn Sie nicht daran denken, sie zu archivieren, kann es zu Problemen kommen, die Datenbank wächst einfach auf unbestimmte Zeit und es gibt keine Ressourcen mehr, keine Festplatten und kein Sharding. Es ist wichtig, Daten archivieren und nur die Betriebsdaten speichern zu können, die derzeit benötigt werden. Außerdem müssen Indexabfragen für alle Datenbanken durchgeführt werden. Eine Nicht-Index-Abfrage kann die gesamte Produktion platzieren. Eine kleine Anfrage an die am meisten geladene zentrale Sammlung kann alles stellen. Du musst sehr vorsichtig sein.
Wir begrüßen keine vorzeitigen Optimierungen. Wenn jemand versucht, eine Fabrik zu einer sehr universellen Methode zu machen, die möglicherweise Fälle für die Zukunft behandelt, möchte sie vielleicht eines Tages erweitert werden - dies ist nicht unsere Gewohnheit, da es möglich ist, dass sie sich entwickelt Es wird völlig falsch sein, und vielleicht wird dieser Code irgendwann vergraben, oder vielleicht ist alles nicht notwendig, sondern erschwert nur das Lesen und Verstehen des Codes. Weil das Lesen und Verstehen des Codes sehr wichtig ist. Es ist wichtig, dass der Code sehr einfach und leicht ist.
Wenn Sie Ihrem Code eine neue Datenbank hinzufügen oder Änderungen an der API vornehmen, verfügen wir über eine Dokumentation, die teilweise aus dem Code generiert wird und teilweise im Wiki erstellt wird. Diese Informationen sind wichtig, um auf dem neuesten Stand zu bleiben. Wenn nicht, kann dies jemanden irreführen oder anderen Entwicklern Probleme bereiten. Weil der Code alleine geschrieben ist, aber viel unterstützt wird.
Ein wichtiger Teil ist die Einhaltung des allgemeinen Stils. Die Hauptsache in diesem Fall ist die Einheitlichkeit. Wenn der gesamte Code einheitlich geschrieben ist, ist er leicht zu verstehen, leicht zu lesen und Sie müssen sich nicht mit allen Details und Nuancen befassen. Einheitlich geschriebener Code kann den gesamten Entwicklungsprozess möglicherweise in Zukunft beschleunigen.
Ein weiterer Punkt, den wir nicht speziell auf Bewertungen prüfen, ist, dass wir nicht nach Fehlern suchen. Weil der Autor mit der Suche nach Fehlern beschäftigt sein sollte. Wenn es während der Überprüfung Fehler gibt, werden sie natürlich darüber schreiben, aber es sollte keine gezielte Suche geben, dies liegt ausschließlich in der Verantwortung der Person, die den Code schreibt.
Wenn Ihr Code geschrieben ist, ist die Überprüfung abgeschlossen, Sie können ihn einfrieren, aber es kommt häufig vor, dass Sie zusätzliche Aktionen ausführen und in die Datenbank migrieren müssen.

Für Migrationen schreiben wir ein Python-Skript, das mit dem Backend kommunizieren kann. Das Backend wiederum hat eine Verbindung zu allen unseren Basen und kann alle erforderlichen Operationen ausführen. Das Skript wird über das Skriptstart-Administrationsfenster gestartet. Anschließend wird es ausgeführt. Das Protokoll und die Ergebnisse werden angezeigt. Und wenn Sie langfristige Strahloperationen benötigen, können Sie nicht alles auf einmal aktualisieren. Sie müssen dies mit Blöcken von 1000-10000 mit einigen Pausen tun, um die Basis nicht versehentlich mit diesen Operationen zu versehen.

Wenn der Code geschrieben, überprüft, getestet und alle Migrationen ausgeführt wurden, können Sie ihn sicher in GitHub zusammenführen und weiter freigeben.
Für einige Dienste haben wir eine Regelung, nach der wir zu einem bestimmten Zeitpunkt einführen müssen, aber einen wesentlichen Teil der Dienste können wir jederzeit einführen.
Dies alles geschieht mit TeamCity.

Alles beginnt mit dem Erstellen von Paketen. TeamCity macht Git Flow oder seinen Anschein. Wir bewegen uns langsam vom Git-Flow weg zu unseren Best Practices, die wir für bequemer hielten. TeamCity produziert all dies, sammelt Pakete und füllt sie aus. Wir warten weiter, bis die Tests diese Pakete weitergeben. Zum Ausrollen der Version sind bestandene Tests erforderlich. Wenn die Tests fehlschlagen, müssen Sie zuerst herausfinden, was letztendlich schief gelaufen ist. Die verwendeten Tests sind gleich, regelmäßig und integrativ. Sie prüfen das bereits montierte Paket, fertig, genau, was in Produktion gehen wird. Dies ist nur für den Fall, plötzlich gibt es Probleme in der zusammengebauten Verpackung, plötzlich wird etwas nicht kopiert, plötzlich hat sich herausgestellt, dass etwas fehlt.
Es ist auch erforderlich, dass wir ein Release-Ticket in unserem Tracker erstellen, in dem jeder Entwickler abbestellen muss, wie er diesen Code getestet hat, und das alle Aufgaben enthalten muss, die ausgeführt werden müssen.
Dies erfolgt auch automatisch in TeamCity, das die Liste der Commits durchläuft. Wir haben die Anforderung, dass in jedem Commit ein Schlüsselwort "Relates" gefolgt vom Namen der Aufgabe enthalten sein muss. Ein in Python geschriebenes Skript durchläuft all dies automatisch, erstellt eine Liste der gelösten Aufgaben, erstellt eine Liste der Autoren und erstellt ein Release-Ticket. Alle Autoren werden aufgefordert, sich von ihren Tests abzumelden und zu bestätigen, dass sie bereit sind, das Release zu "starten".

Wenn alle bereit sind, werden Bestätigungen gesammelt, und zuerst erfolgt die Einführung - im Vorstall. Dies ist ein kleiner Teil der Produktion. Für jeden Dienst werden mehrere Rechenzentren verwendet. In jedem Rechenzentrum können mehrere Maschinen vorhanden sein. Eine der Maschinen ist vorstabil, und der Code wird zuerst nur auf einer oder mehreren Maschinen bereitgestellt.
Wenn der Code entleert ist, folgen wir den Diagrammen, den Protokollen und dem, was am Ende des Dienstes passiert. Wenn alles in Ordnung ist, wenn die Grafiken zeigen, dass alles stabil ist und jeder überprüft hat, ob seine Funktionalität ordnungsgemäß funktioniert, wird sie auf den Rest der Umgebung übertragen, die wir als stabil bezeichnen. Beim Ausrollen in den Stall ist alles gleich: Wir sehen uns die Diagramme und Protokolle an und prüfen, ob bei uns alles in Ordnung ist.
Der Rollout ist vorbei, alles ist in Ordnung. Und wenn etwas schief gelaufen ist, wenn plötzlich Probleme?

Wir sammeln Hotfix. Dies geschieht nach dem gleichen Prinzip wie bei git flow, dh einem Zweig vom Hauptzweig. Vom Master wird eine separate Poolanforderung erstellt, die Korrekturen vornimmt. Das von TeamCity gestartete Skript friert sie ein, führt alle erforderlichen Vorgänge aus, sammelt alle Pakete auf dieselbe Weise und rollt weiter.

Am Ende möchte ich über die Richtung sprechen, in die wir uns bewegen. Wir bewegen uns in Richtung eines einzelnen Repositorys, wenn viele Dienste gleichzeitig in einem Repository gespeichert sind. Jeder von ihnen hat unabhängige Berechnungen: beim Testen, in Releases. Bei Poolanfragen prüfen wir auch bei Verwendung von TeamCity, welche Dateien betroffen waren und zu welchen Diensten sie gehören. Gemäß dem Abhängigkeitsdiagramm bestimmen wir, welche Tests wir letztendlich ausführen müssen und was zu überprüfen ist. Wir streben eine maximale Isolation der Dienste voneinander an. Bisher funktioniert es nicht sehr gut, aber wir bemühen uns darum, dass viele Dienste in einem Repository gespeichert werden können, einen gemeinsamen Code haben und dies keine Probleme verursacht und das Entwicklungsleben vereinfacht. Das ist alles, danke euch allen.