Das Team sollte sich darauf konzentrieren, schöne und erfolgreiche Spiele zu entwickeln, für alles andere gibt es CI.
Wo wenden wir CI an? Welche Ansätze und Konzepte verwenden wir? Warum Builds erstellen und testen? Die detaillierte Geschichte über CI und wie es in Playrix organisiert ist, wird einen Kurs mit Vorträgen zeichnen. Unter dem Schnitt - ein kurzes Drücken und ein paar Akzente.
Hallo.
Zunächst ein wenig aufwärmen: Was ist Continuous Integration? Wenn das Team das Repository verwendet und Nacht-Builds sammelt - ist das CI bereits? Was ist der Unterschied zwischen Continuous Deployment und Delivery? Es ist fast unwichtig. Details - für einen engen Kreis von Spezialisten. Wenn Sie das gesamte Unternehmen in einen Prozess einbeziehen möchten, überlegen Sie sich einen einfachen und guten Namen. In Playrix bezeichnen wir alle diese Ansätze als CI. Dies ist so eine lokale Marke und ein cooles Logo:
Idee
CI ist kein Ziel, es ist ein Werkzeug. Es muss ein Ziel geben, das Sie mit Continuous Integration erreichen möchten, ein Problem, das gelöst werden muss. Es kommt vor, dass die Entwicklungs- und Freigabeprozesse in einem Team auf dem Prinzip "Gebet und Produktion" basieren. Manchmal ist es gerechtfertigt, aber selten.
Wir haben unser Ziel wie folgt formuliert: Minimierung der Wahrscheinlichkeit von Integrationsproblemen, Minimierung der Ressourcen, die zur Behebung der gefundenen Fehler erforderlich sind, Verkürzung der Zeit der Projektentwicklungsteams zur Unterstützung und Unterstützung von CI-Prozessen.
CI ist die Automatisierung von Erstellungsprozessen, das Testen von Code und dessen Bereitstellung in verschiedenen Umgebungen, die Automatisierung von Routineentwicklungsprozessen und die gegenseitige Integration von Diensten, die wir alle nutzen.
Die Idee ist ein System, das automatisch alles sammelt, es oft tut, einen Build testet und ausliefert und außerdem einen praktischen Bericht Punkt für Punkt erstellt, wenn etwas schief gelaufen ist.
Wo wenden wir CI an?
- Motor und Versorgung,
- unsere Spiele für alle Plattformen,
- Servercode
- Analytik, Marketing-Dienstleistungen, verschiedene Automatisierungs-, CI-Dienstleistungen,
- die Infrastruktur.
Das ist überall oder fast überall.
Das Zusammenstellen und Testen von Containern, die automatische Bereitstellung in Test-, Staging- und Prod-, Rolling- und Canary-Updates - all dies haben wir und dies gilt insbesondere für Services und Webanwendungen. Heute werden wir uns auf CI für Spiele konzentrieren: Builds bauen, testen und liefern.
Paradigmen
Um die oben genannten Ziele zu erreichen, müssen Sie mehrere Probleme lösen. Im Folgenden finden Sie einen Plan, den wir befolgen, wenn wir einen Prozess in der Entwicklung automatisieren, z. B. das Zusammenstellen eines Clients für mobile Spiele. Es ist sehr praktisch, eine Liste mit Fragen zu haben, die Sie beantworten können, um jedes Problem zu lösen, das in das CI-Team fällt.
Bauanleitungen sind Dokumentationen, eine Beschreibung unseres automatisierten Prozesses. Oft liegt eine solche Dokumentation im Kopf der Programmierer. Wenn ein Team einen Superspezialisten für das Erstellen von Builds hat und niemand mehr einen schnellen und fehlerfreien Build erstellen kann - es ist Zeit, etwas zu ändern, es wird keine Übergabe geben.
Nun, wenn eine solche Dokumentation in Form eines Skripts vorliegt: Ich habe eine Befehlszeile und eine Reihe von Parametern auf einem Computer mit einer vorbereiteten Umgebung eingegeben - ich habe einen Build erhalten.
Die beste Prozessdokumentation ist der
Cat- Code. Auch wenn Sie den Vorgang aus irgendeinem Grund manuell wiederholen müssen, können Sie ihn jederzeit überprüfen.
Mit dem Build-Protokoll können Sie immer sicher sagen, wer, wann, von welchem Commit und mit welchem Ergebnis dieser oder jener Build gesammelt wurde. Gestern war der Bau kurz davor, heute jedoch nicht. Wir sehen im Protokoll nach, wir finden den ersten Floppy-Build, wir sehen die Liste der Commits, die dort ankamen - Profit.
Das Magazin ist noch nützlicher, wenn es zum Beispiel um Server-Code geht. Wenn keine Informationen darüber vorliegen, wer und wann das Produkt aktualisiert hat, ist im Allgemeinen nicht bekannt, welcher Code derzeit dort ausgeführt wird. Und manchmal ist es sehr wichtig, sehr.
Sie können ein solches Tagebuch im Hauptbuch führen, vorzugsweise in einer Tabelle oder einem Wiki. Die Liste der Builds im CI-System ist von unschätzbarem Wert.
Wenn es darum geht, einen Build zu erstellen und in einer bestimmten Umgebung bereitzustellen, stellt sich immer die Frage, wo Anmelde- / Zugriffskennwörter gespeichert werden sollen. Sie benötigen normalerweise viel: in das Repository, um die Quelldaten herunterzuladen, in den Dateispeicher, um die Spielressourcen auszufüllen, in die HockeyApp, um Zeichen zu senden, auf den Server, um den Code zu aktualisieren usw.
Es kommt vor, dass alle erforderlichen Zugriffe im Repository gespeichert werden. Es gibt eine Hypothese, dass dies nicht sehr gut ist. In Jenkins wird häufig das Feld "Passwort eingeben" angezeigt, in dem der Autor des Builds die verborgenen Zeichen eingibt.
Sich alle Passwörter auswendig zu merken, ist eine gute Fähigkeit. Unser CI-Server selbst erhält je nach Baugruppe die notwendigen Zugriffe. In der Regel sind dies kurzlebige Token, die zu Beginn des Builds generiert werden und minimale Rechte genau dort gewähren, wo wir etwas bereitstellen oder wo wir etwas lesen.
Durch die zentrale Verwaltung der Assembly und Bereitstellung können Sie das Problem der Unterscheidung der Zugriffsrechte auf die Infrastruktur lösen. Warum jemandem Zugriff auf den Server gewähren, wenn Sie nur Zugriff auf die Assembly des entsprechenden Builds gewähren können, der die erforderliche Operation auf diesem Server ausführt? Und da es einen Build gibt, haben wir Dokumentation und Journal, verstehen Sie?
Während der Erstellungszeit sind normalerweise viele Spuren hinterlassen. Nein, nicht so: Beim Build-Build müssen so viele Spuren wie möglich hinterlassen werden. Im Repository, im Task-Tracker, im Build-Verteilungssystem. Überall dort, wo Sie auf einen Build stoßen, sollten sich Spuren befinden, die Sie zu vollständigen Informationen darüber führen.
Diese Spuren müssen nicht weggefegt werden, im Gegenteil, sie müssen sorgfältig hinterlassen und sorgfältig aufbewahrt werden. Des Weiteren werde ich Ihnen mehr darüber erzählen, aber zuerst müssen wir unseren Build sammeln. Lass uns gehen.
Hooks vorab festschreiben
Die Idee ist wieder ein System, das alles sammelt, testet und meldet. Aber warum einen Build erstellen, wenn Sie ihn nicht erstellen können?
Bei allen Entwicklern unserer Spiele sind Pre-Commit-Hooks installiert, d. H. Eine Reihe von Überprüfungen, die durchgeführt werden, wenn versucht wird, etwas festzuschreiben. Überprüfungen werden nur für geänderte Dateien ausgeführt, aber wir haben ein sehr ausgeklügeltes Suchsystem implementiert, um alle zugehörigen Inhalte zu überprüfen. Das heißt Wenn der Künstler eine Textur hinzugefügt hat, überprüfen Hooks, ob sie nicht vergessen haben, diese zu registrieren, wo immer dies erforderlich ist, und haben sie nie versiegelt.
Es stellt sich heraus, dass Haken einen erheblichen Teil kleiner Fehler auffangen. Sie sparen die Ressourcen des Build-Systems und helfen dem Entwickler, das Problem schnell zu beheben: Er sieht eine Meldung, die detailliert angibt, was schief gelaufen ist. Und er muss nicht zwischen den Aufgaben wechseln: Er hat buchstäblich nur Änderungen vorgenommen und ist im Kontext. Die Fehlerkorrekturzeit ist minimal.
Uns hat es so gut gefallen, dass wir sogar ein System erstellt haben, das überprüft, ob Hooks für ein Commit erstellt wurden, das in das Repository gelangt ist. Ist dies nicht der Fall, erhält der Autor eines solchen Commits automatisch eine Aufforderung, diese zu konfigurieren, und detaillierte Anweisungen dazu.
Haken sind für alle Projekte standardisiert. Die Anzahl der benutzerdefinierten Tests ist minimal. Es gibt bequeme Anpassungsmöglichkeiten, auch je nach Benutzer, der gerade ausgeführt wird: Dies ist sehr praktisch zum Testen von Tests.
Bauen
Um das Problem im Build so früh wie möglich zu erkennen, müssen Sie diese Builds so oft wie möglich erfassen und testen. Kunden unserer Spiele versammeln sich für alle Plattformen, für jedes Commit, für alle Projekte. Es gibt wahrscheinlich einige Ausnahmen, aber nicht viele.
Typischerweise hat ein Client, insbesondere ein mobiler, mehrere verschiedene Versionen: mit und ohne Cheats, unterschiedlich signiert usw. Für jedes Commit sammeln wir "reguläre" Builds, die Entwickler und Tester ständig verwenden.
Es gibt Builds, die sehr selten verwendet werden, z. B. den Build des Stores - nur einmal in einer Übermittlung, d. H. etwa einmal im Monat. Wir sind jedoch der Meinung, dass alle Builds regelmäßig eingesammelt werden sollten. Wenn bei dieser Art von Baugruppe ein Problem auf der Entwicklungs- oder Infrastrukturseite auftritt, weiß das Projektteam dies nicht am Tag der Lieferung des Builds, sondern viel früher und kann im Voraus reagieren und das Problem beheben.
Als Ergebnis haben wir eine einfache Regel: Jeder Build wird mindestens einmal am Tag gestartet. Das Projektentwicklungsteam findet heraus, ob auf einer Plattform Probleme vorliegen, und zwar im schlimmsten Fall am nächsten Morgen, nachdem dieses Problem im Repository aufgetreten ist.
Eine solche Häufigkeit von Baugruppen und Tests erfordert einen besonderen Ansatz zur Optimierung ihrer Ausführungszeit.
- Alle regulären Kundenbuilds sind inkrementell.
- Das Packen von Atlanten und das Vorbereiten von Ressourcen erfolgt ebenfalls inkrementell.
- Assemblies sind granular: Einige der Schritte befinden sich in separaten Build-Konfigurationen. Auf diese Weise können Sie sie parallel ausführen und Zwischenergebnisse wiederverwenden.
Dies ist ein fast vollständiger Screenshot der Build- und Testkette für WildScapes. Voll konnte nicht gemacht werden: es ist etwa doppelt so groß.
Statische Tests
Nach dem Zusammenbau werden statische Tests durchgeführt: Wir nehmen den Ordner mit dem Build und führen eine Reihe von Überprüfungen des gesamten Inhalts durch, der dort vorhanden ist. Code ist auch Inhalt, daher ist auch die statische Analyse (cppcheck + PVS-Studio) hier.
Auf einem habr gab
es ein detailliertes Material darüber, wie wir statische Tests implementiert haben, ich empfehle es. Ich betone nur, dass die statischen Tests nach dem Build und in den Pre-Commit-Hooks vom selben Code ausgeführt werden. Dies vereinfacht die Systemunterstützung erheblich.
Laufzeit-Tests
Wenn die Erstellung der statischen Tests erfolgreich war, können Sie versuchen, die zusammengestellte Erstellung auszuführen. Wir testen Builds auf allen Plattformen außer UWP, d. H. Windows, MacOS, iOS, Android. UWP - wird auch sein, aber etwas später.
Warum Desktop-Builds testen, wenn sie nur in der Entwicklung benötigt werden? Die Antwort auf die Frage lautet: Es ist schlimm, wenn ein Künstler oder Leveldesigner einen Build bekommt, der beim Start aus irgendeinem lächerlichen Grund abstürzt. Aus diesem Grund wird der Smoke-Test, der Mindestsatz an Überprüfungen auf Runability und grundlegendes Gameplay, für alle Plattformen durchgeführt.
Alles, was oben über Builds geschrieben wurde, gilt auch für Tests an Geräten - mindestens einmal am Tag. Mit wenigen Ausnahmen: Es gibt sehr lange Tests, für die an einem Tag keine Zeit zur Verfügung steht.
Bei jedem Commit werden Smoke-Tests durchgeführt. Der erfolgreiche Abschluss grundlegender Überprüfungen ist eine Voraussetzung dafür, dass der Build in das Verteilungssystem aufgenommen werden kann. Normalerweise macht es keinen Sinn, jemandem Zugriff auf einen Build zu gewähren, der offensichtlich nicht funktioniert. Hier können Sie Einspruch einlegen und sich Ausnahmen einfallen lassen. Projekte haben eine Problemumgehung, um auf einen nicht funktionierenden Build zuzugreifen, aber sie verwenden ihn kaum.
Welche anderen Tests gibt es:
- Benchmark: Wir überprüfen die Leistung auf FPS und Speicher in verschiedenen Situationen und auf allen Geräten.
- 3-Gewinntests: Jedes Element und jede Mechanik werden einzeln und in allen Interaktionskombinationen getestet.
- Der Durchgang des gesamten Spiels von Anfang bis Ende.
- Verschiedene Regressionstests, z. B. Lokalisierungstests oder das korrekte Öffnen aller Benutzeroberflächenfenster oder das fehlerfreie Abspielen von Fishdom-Szenen in Fishdom.
- Trotzdem , aber mit AddressSanitizer .
- Kompatibilitätstests für Spielversionen: Nehmen Sie die vom Benutzer gespeicherte Datei aus früheren Versionen, öffnen Sie sie in der neuen Version und stellen Sie sicher, dass alles in Ordnung ist.
- Verschiedene kundenspezifische Tests, die für den Mechaniker eines bestimmten Projekts relevant sind.

Zur Durchführung der Tests verwenden wir unseren eigenen Teststand für iOS- und Android-Geräte. Auf diese Weise können wir die Builds, die wir auf Geräten benötigen, flexibel starten und über den Code mit dem Gerät interagieren. Wir haben die volle Kontrolle, ein verständliches Maß an Zuverlässigkeit, wir wissen, mit welchen Problemen wir konfrontiert sind und wie lange es dauern wird, sie zu lösen. Keiner der Cloud-Dienste, die Testgeräte bereitstellen, bietet einen solchen Komfort.
KATZEN
Die oben aufgeführten Tests sind im Projektcode implementiert. Dies ermöglicht theoretisch einen Test jeder Komplexität, erfordert jedoch Aufwand und Ressourcen für die Entwicklung des Projekts, um diese Tests zu implementieren und zu unterstützen. Diese Ressourcen sind häufig nicht verfügbar, und das Testen mehrerer Regressionen per Hand ist schwierig und nicht erforderlich. Ich wollte wirklich, dass die Tester selbst Testautomatisierung durchführen. Und wir haben ein Framework dafür entwickelt - das Continuous Automation Testing System (CATS).
Was ist die Idee: Autoren von Testskripten die Möglichkeit zu geben, mit der Spieleanwendung zu interagieren, ohne sich darum zu kümmern, wie alles funktioniert. Wir schreiben Skripte in primitivem Python und greifen über eine Reihe von Abstraktionen auf die Anwendung zu. Zum Beispiel: "Homescapes, öffne mir ein Schaufenster und kaufe so und so ein Produkt." Überprüfen Sie das Ergebnis, Bingo.
Die gesamte Implementierung von Skriptbefehlen ist hinter einer Reihe von Abstraktionen verborgen. Mit der API, die die Interaktion mit der Anwendung implementiert, können Sie Aktionen auf verschiedene Arten ausführen:
- Senden Sie mit einem Befehl eine http-Anfrage an den Server, der in die Spiel-Engine integriert ist. Dieser Befehl wird vom Spielcode verarbeitet. In der Regel handelt es sich hierbei um einen Cheat, der beliebig einfach oder komplex sein kann. Beispiel: "Geben Sie mir die Koordinaten der Schaltflächenmitte mit der angegebenen Kennung." Oder "gib mir das Spiel von hier bis zum Level mit der angegebenen Nummer."
- Wir können ein Fenster durch den Cheat öffnen oder die Koordinaten der Schaltfläche herausfinden, mit der dieses Fenster geöffnet wird. Wir können das Klicken auf diese Schaltfläche emulieren und einen virtuellen Klick darauf ausführen.
- Zum Schluss können wir die angegebenen Koordinaten mit einem „echten“ Klick anklicken, als ob dies mit einem Finger auf dem Bildschirm geschehen wäre.
Die letztere Methode eröffnet den Spielraum für die Vorstellungskraft von Testern, die häufig "Kampf" -Bauten testen möchten, bei denen es keine Cheats gibt. Solche Szenarien beizubehalten ist schwieriger, aber ein "Kampfbau" ist ein "Kampfbau".
Es hat sich als sehr praktisch erwiesen, mit den Koordinaten der Knopfmitte zu arbeiten: Die Koordinaten ändern sich manchmal, aber die Knopfkennungen sind selten. Dies führte zu einer weiteren wichtigen Eigenschaft des Systems: Die Möglichkeit, ein Testskript für alle Plattformen und alle Bildschirmauflösungen zu schreiben.
Lieferung, Berichte & Traces
Bei der Auslieferung stellte sich heraus, dass alles ganz einfach war: Wir verwenden einen einzigen gemeinsamen Speicher für Build-Artefakte und für die Speicherung im Distributionssystem. Das "Laden" des Builds läuft darauf hinaus, ein Paar von Anforderungen an die API des Build-Verteilungsdienstes aufzurufen und sich im Wesentlichen zu registrieren. Auf diese Weise haben wir ein wenig Zeit beim Pumpen von Bauteilen und Geld für deren Lagerung gespart.
Denken Sie daran, Sie haben darüber gesprochen, die Ressourcen zu minimieren, die zur Behebung von Fehlern in Builds erforderlich sind. Berichte und Tracks - so ungefähr:
- Das Melden eines gefundenen Problems ist eine Aufgabe in Asana. Es ist einfach zu steuern, es dem richtigen Entwickler zuzuweisen und es an das CI-Team weiterzuleiten, wenn in der Infrastruktur etwas schief gelaufen ist.
- Wir sammeln Builds für jedes Commit. Wir kennen den Autor dieses Commits, daher wird nur er diese Aufgabe sehen. So sparen wir Zeit für andere Entwickler: Sie müssen sich nicht von Problemen ablenken lassen, mit denen sie nichts zu tun haben, und helfen bei der Lösung, die sie höchstwahrscheinlich nicht lösen können.
- Wenn Sie einen Build aus dem nächsten Commit erstellen, ist er höchstwahrscheinlich immer noch fehlerhaft. Es wird einen Kommentar in der Aufgabe geben: "Der Build ist noch nicht fertig", der Autor des neuen Commits wird die Aufgabe nicht sehen und keine Zeit mit dem Problem einer anderen Person verschwenden.
- Wir senden Berichte an Slack. Notwendigerweise - persönlich an denjenigen, der den Build "gebrochen" hat, und wenn das Projekt dies wünscht - an einen speziellen Channel oder an einen Playrix-Mitarbeiter. Alles ist so flexibel wie möglich.
Traces werden benötigt, damit überall vollständige Informationen über den Build und die Änderungen, von denen er gesammelt wurde, zur Verfügung stehen. Um nicht nach irgendetwas zu suchen, damit alles zur Hand war und man keine Zeit damit verbringen musste, nach Details zu suchen, die bei der Suche nach einem Problem häufig erforderlich sind.
- Der Bericht enthält einen Link zum Build, zum Build-Protokoll, den Text des gefundenen Kompilierungsfehlers und die Namen der umgedrehten Tests. Häufig kann ein Programmierer, der eine Berichtsaufgabe erhalten hat, den Fehler sofort beheben: Der Dateiname, die Zeile und der Fehlertext befinden sich im Bericht.
- Die Nachricht in Slack enthält trotzdem + einen Link zur Aufgabe in Asana.
- In Teamcity ein Link zu einer Aufgabe. Build Engineer kann sofort in die Aufgabe einsteigen, mit einem Klick müssen Sie nichts suchen.
- Im Github - Status mit einem Link zum Build, im Kommentar zum Commit - einem Link zu der Task, für die dieser Build erstellt wurde. In der Aufgabe - ein Kommentar mit einem Link zum Commit.
- Im Build-Verteilungsdienst: Link zum Build, Link zum Commit.
Es gibt nichts zu merken, aber Sie haben die Idee verstanden: Links zu allem, überall. Dies beschleunigt das Studium jeder unverständlichen Situation erheblich.
Bauernhof
Wir sammeln und testen Builds für alle Plattformen. Dafür brauchen wir viele verschiedene Agenten. Sie manuell zu verfolgen und zu warten ist lang und schwierig. Alle Agenten werden automatisch vorbereitet. Wir verwenden Packer und Ansible.
Alle Protokolle aller Agenten, Teamcity, aller Dienste, die es gibt, speichern wir (in unserem Fall - in ELK). Alle Dienste, die einen Build verarbeiten, fügen jeder Protokollzeile die Nummer dieses Builds hinzu. Wir können in einer einzelnen Anfrage den gesamten Lebenszyklus des Builds von seinem Erscheinen in der Warteschlange bis zum Ende des Sendens aller Berichte sehen.
Wir haben unseren eigenen Warteschlangenoptimierungsmechanismus implementiert. Das in Teamcity funktioniert bei unseren Zahlen nicht sehr gut. Apropos Zahlen:
- Wir sammeln jeden Tag ungefähr 5.000 Builds. Das sind ca. 500 Maschinenstunden.
- Der dreimillionste Bau war vor einem Monat.
- Wir haben über 50 Build-Server an 10 verschiedenen Standorten.
- Über 40 mobile Geräte auf einem Prüfstand.
- Genau 1 Teamcity Server.
CI als Dienstleistung
Playrix CI ist ein Dienst. Es gibt viele Projekte, viele Ideen.
Wir optimieren die Zeit vom Einreihen des Builds in die Warteschlange bis zum Ende seiner Ausführung, da der Benutzer des Service, der Entwickler, genau diese "Erstellungszeit" berücksichtigt. Auf diese Weise können wir ein Gleichgewicht zwischen der Erstellungszeit und der in der Warteschlange verbrachten Zeit suchen und finden. Es erscheint logisch, dass mit dem Wachstum des Unternehmens und der Anzahl der Projekte auch die Build-Farm, in der diese Projekte gesammelt werden, wächst. Dank Optimierungen liegt die Wachstumsrate der Farm jedoch weit hinter der Wachstumsrate des Unternehmens.
Jede Optimierung beginnt mit der Überwachung und einer methodischen Erfassung von Statistiken. Wir sammeln viele Statistiken und wissen absolut alles über unsere Builds. Neben dem Volumen der Buildfarm gibt es auch ein Team, das das CI-System unterstützt und dafür sorgt, dass niemand darüber nachdenken muss, woher die Builds stammen.
Die Prozessoptimierung in diesem Team ist ebenfalls ein interessanter und unterhaltsamer Prozess.
Zum Beispiel schreiben wir Tests zum Festlegen von Build-Konfigurationen, da es viele dieser Konfigurationen gibt. Ohne ähnliche Tests ist es nicht einfach, alle Stellen zu finden, die bearbeitet werden müssen. Für fast alle Änderungen schreiben wir zuerst einen Test und nehmen sie dann vor, d. H. Tatsächlich haben wir TDD. Es gibt viele Prozesse im Zusammenhang mit der Aufgabe, dem Vorfallmanagement und der Planung des Ablaufs eingehender Aufgaben.Entwickler sollten sich darauf konzentrieren, großartige und erfolgreiche Spiele zu entwickeln, ohne sich Gedanken darüber zu machen, woher Builds stammen. Dafür hat Playrix ein CI. Es muss ein Ziel geben, das Sie mit Continuous Integration erreichen möchten, ein Problem, das gelöst werden muss. Es ist wichtig, kein Problem zu haben, nämlich es zu finden. Und wenn Sie sie finden, erinnern Sie sich an unsere Erfahrung und verbessern Sie sie. Und denkdran : CIch schlafe nie Wirsehen uns!