
Je schneller der Entwicklungsprozess, desto schneller entwickelt sich das Technologieunternehmen.
Leider arbeiten moderne Anwendungen gegen uns – unsere Systeme müssen in Echtzeit aktualisiert werden, ohne Störungen oder Ausfallzeiten zu verursachen. Die Bereitstellung in solchen Systemen wird zu einer komplexen Aufgabe und erfordert komplexe Continuous Delivery-Pipelines, selbst in kleinen Teams.
Diese Pipelines haben typischerweise eine enge Anwendung, arbeiten langsam und sind unzuverlässig. Entwickler müssen sie zunächst manuell erstellen und dann verwalten, und Unternehmen stellen oft ganze DevOps-Teams dafür ein.
Die Geschwindigkeit dieser Pipelines beeinflusst die Geschwindigkeit der Entwicklung. Bei den besten Teams dauert die Bereitstellung 5–10 Minuten, aber normalerweise dauert alles viel länger, und eine einzelne Bereitstellung benötigt mehrere Stunden.
Bei Dark dauert das 50 ms. Fünfzig. Millisekunden. Dark — , die speziell für Continuous Delivery entwickelt wurde, und alle Aspekte von Dark, einschließlich der Sprache selbst, sind mit dem Ziel einer sicheren sofortigen Bereitstellung aufgebaut.
Warum sind Continuous Delivery-Pipelines so langsam?
Angenommen, wir haben eine Python-Webanwendung und wir haben bereits eine großartige und moderne Continuous Delivery-Pipeline erstellt. Für einen Entwickler, der täglich an diesem Projekt arbeitet, sieht die Bereitstellung einer geringfügigen Änderung ungefähr so aus:
Änderungen vornehmen
- Neuen Branch in git erstellen
- Änderungen am Feature-Flag vornehmen
- Modultests zur Überprüfung der Änderungen mit und ohne Feature-Flag
Pull-Request
- Änderungen committen
- Änderungen an das Remote-Repository auf github senden
- Pull-Request
- CI-Build wird automatisch im Hintergrund ausgeführt
- Code-Review
- Weitere Reviews, falls erforderlich
- Änderungen mit dem Master-Branch mergen.
CI wird am Master ausgeführt
- Frontend-Abhängigkeiten über npm installieren
- Ressourcen HTML+CSS+JS bauen und optimieren
- Frontend-modular und funktionale Tests durchführen
- Python-Abhängigkeiten aus PyPI installieren
- Backend-modular und funktionale Tests durchführen
- Integrationstests an beiden Enden
- Ressourcen des Frontends an das CDN senden
- Zusammenstellung des Containers für das Python-Programm
- Versand des Containers ins Registry
- Aktualisierung des Kubernetes-Manifests
Austausch von altem Code mit neuem
- Kubernetes startet mehrere Instanzen des neuen Containers
- Kubernetes wartet, bis die Instanzen betriebsbereit sind
- Kubernetes fügt die Instanzen dem HTTP-Lastenausgleich hinzu
- Kubernetes wartet, bis die alten Instanzen nicht mehr verwendet werden
- Kubernetes stoppt die alten Instanzen
- Kubernetes wiederholt diese Vorgänge, bis alle neuen Instanzen die alten ersetzt haben
Aktivierung des neuen Funktionsschalters
- Neuer Code wird nur für sich selbst aktiviert, um sicherzustellen, dass alles in Ordnung ist
- Neuer Code wird für 10% der Benutzer aktiviert, betriebliche und geschäftliche Metriken werden verfolgt
- Neuer Code wird für 50% der Benutzer aktiviert, betriebliche und geschäftliche Metriken werden verfolgt
- Neuer Code wird für 100% der Benutzer aktiviert, betriebliche und geschäftliche Metriken werden verfolgt
- Schließlich wiederholen Sie den gesamten Prozess, um den alten Code und den Schalter zu entfernen
Der Prozess hängt von den Tools, der Sprache und der Nutzung serviceorientierter Architekturen ab, sieht aber im Allgemeinen so aus. Ich habe Bereitstellungen mit Datenbankmigrationen nicht erwähnt, da hierfür eine gründliche Planung erforderlich ist, aber unten erfahren Sie, wie Dark damit umgeht.
Hier gibt es viele Komponenten, und viele davon können leicht verzögern, fehlerhaft sein, zeitweilige Konflikte verursachen oder das Betriebssystem zum Absturz bringen.
Da diese Pipelines fast immer für einen speziellen Fall erstellt werden, ist es schwierig, sich auf sie zu verlassen. Viele haben Tage, an denen der Code nicht bereitgestellt werden kann, weil es Probleme mit dem Dockerfile gibt, eines der vielen Services ausgefallen ist oder der benötigte Spezialist im Urlaub ist.
Schlimmer noch, viele dieser Schritte leisten überhaupt nichts Nützliches. Sie waren früher notwendig, als wir den Code direkt für die Benutzer bereitgestellt haben, aber jetzt haben wir Schalter für den neuen Code, und diese Prozesse haben sich getrennt. Letztendlich ist der Schritt, bei dem der Code bereitgestellt wird (alter wird durch neuen ersetzt), nun einfach ein zusätzliches Risiko geworden.
Natürlich handelt es sich um eine sehr durchdachte Pipeline. Das Team, das sie erstellt hat, hat keine Zeit und kein Geld gescheut, um eine schnelle Bereitstellung zu gewährleisten. In der Regel sind Bereitstellungspipelines jedoch viel langsamer und unzuverlässiger.
Implementierung der kontinuierlichen Bereitstellung in Dark
Die kontinuierliche Bereitstellung ist für Dark so wichtig, dass wir von Anfang an darauf abgezielt haben, die Zeit unter einer Sekunde zu halten. Wir haben alle Schritte der Pipeline überprüft, um alles Überflüssige zu entfernen und den Rest zu optimieren. So haben wir die Schritte eliminiert.
Jessie Frazelle () hat auf der Konferenz Future of Software Development in Reykjavík ein neues Wort, deployless (bereitstellungsfrei), erfunden.
Wir haben uns sofort entschieden, dass Dark auf dem Konzept «deployless» basieren wird (danke für das Neologismus). Deployless bedeutet, dass jeder Code sofort bereitgestellt wird und bereit ist, in der Produktion verwendet zu werden. Natürlich übersehen wir keinen fehlerhaften oder unvollständigen Code (ich werde die Sicherheitsprinzipien unten beschreiben).
Bei der Präsentation von Dark wurden wir oft gefragt, wie wir es geschafft haben, die Bereitstellung so zu beschleunigen. Eine seltsame Frage. Die Leute denken wahrscheinlich, dass wir eine Supertechnologie erfunden haben, die den Code vergleicht, ihn kompiliert, in einen Container packt, eine virtuelle Maschine startet, den Container kalt startet und all das innerhalb von 50 ms. Das ist kaum möglich. Aber wir haben eine spezielle Bereitstellungs-Engine entwickelt, die all das nicht benötigt.
Dark führt Interpretatoren in der Cloud aus. Angenommen, Sie schreiben Code in einer Funktion oder einem HTTP-Handler oder Event-Handler. Wir senden einen Diff an den abstrakten Syntaxbaum (die Code-Implementierung, die unser Editor und unsere Server intern verwenden) auf unsere Server und führen diesen Code aus, wenn Anfragen eingehen. Daher sieht die Bereitstellung einfach aus wie ein bescheidener Datensatz in einer Datenbank — sofort und elementar. Die Bereitstellung erfolgt so schnell, weil sie das absolute Minimum umfasst.
In Zukunft planen wir, Dark zu einem Infrastruktur-Compiler zu machen, der die ideale Infrastruktur für hohe Leistung und Zuverlässigkeit von Anwendungen erstellt und startet. Die sofortige Bereitstellung wird natürlich nicht verschwinden.
Sichere Bereitstellung
Strukturierter Editor
Code in Dark wird im Dark-Editor geschrieben. Der strukturierte Editor erlaubt keine Syntaxfehler. Im Grunde gibt es in Dark nicht einmal einen Parser. Solange Sie Text eingeben, arbeiten wir direkt mit dem abstrakten Syntaxbaum (AST), wie , , , und .
Jeder unvollständige Code in Dark hat eine gültige Ausführungssemantik, ähnlich wie Zum Beispiel, wenn Sie den Funktionsaufruf ändern, speichern wir die alte Funktion, bis die neue einsatzbereit ist.
Jedes Programm in Dark hat seine eigene Bedeutung, daher stört unvollständiger Code das Funktionieren des vollständigen Codes nicht.
Bearbeitungsmodi
Sie schreiben Code in Dark in zwei Fällen. Erstens: Sie schreiben neuen Code und sind der einzige Benutzer. Zum Beispiel befindet es sich in REPL, und andere Benutzer haben niemals Zugriff darauf, oder es handelt sich um einen neuen HTTP-Routen, auf den Sie sich nirgendwo beziehen. Hier können Sie ohne Vorsichtsmaßnahmen arbeiten, und so arbeiten Sie derzeit in Ihrer Entwicklungsumgebung.
Die zweite Situation: Der Code wird bereits verwendet. Wenn durch den Code Datenverkehr fließt (Funktionen, Ereignis-Handler, Datenbanken usw.), muss Vorsicht walten. Daher sperren wir den gesamten verwendeten Code und verlangen, dass strukturiertete Werkzeuge zum Bearbeiten verwendet werden. Über diese strukturierten Werkzeuge werde ich später sprechen: Funktionsumschalter für HTTP- und Ereignis-Handler, eine leistungsstarke Migrationplattform für Datenbanken und eine neue Versionierungsmethode für Funktionen und Typen.
Funktionsumschalter
Eine der Möglichkeiten in Dark zu beseitigen, besteht darin, mehrere Probleme mit einer einzigen Lösung zu lösen. Funktionsumschalter führen viele verschiedene Aufgaben aus: Ersetzen der lokalen Entwicklungsumgebung, Git-Branches, Bereitstellung von Code und natürlich die traditionelle langsame und kontrollierte Veröffentlichung neuen Codes.
Die Erstellung und Bereitstellung eines Funktionsumschalters erfolgt in unserem Editor in einem einzigen Schritt. Er schafft leeren Raum für neuen Code und bietet Steuerungselemente für den Zugriff auf alten und neuen Code sowie Schaltflächen und Befehle für einen schrittweisen Übergang zum neuen Code oder dessen Ausschluss.
Funktionsumschalter sind in die Sprache Dark integriert, und selbst unvollständige Umschalter erfüllen ihren Zweck – wenn die Bedingung im Umschalter nicht erfüllt ist, wird der alte gesperrte Code ausgeführt.
Entwicklungsumgebung
Funktionsschalter ersetzen die lokale Entwicklungsumgebung. Heute haben Teams Schwierigkeiten, sicherzustellen, dass alle dieselben Versionen von Werkzeugen und Bibliotheken (Codeformatierungstools, Linter, Paketmanager, Compiler, Preprozessoren, Testwerkzeuge usw.) verwenden. Mit Dark müssen keine Abhängigkeiten lokal installiert, lokale Docker-Installationen verwaltet oder andere Maßnahmen ergriffen werden, um zumindest eine gewisse Gleichheit zwischen Entwicklungs- und Produktionsumgebung zu gewährleisten. , werden wir nicht einmal so tun, als strebten wir danach.
Anstatt eine geklonte lokale Umgebung zu erstellen, schaffen Funktionsschalter in Dark eine neue Sandbox in der Produktion, die die Entwicklungsumgebung ersetzt. In Zukunft planen wir auch, Sandboxes für andere Teile der Anwendung (z. B. sofortige Datenbankklone) zu erstellen, obwohl dies derzeit nicht als besonders wichtig erscheint.
Branches und Deployments
Derzeit gibt es mehrere Möglichkeiten, neuen Code in die Systeme einzuführen: Git-Branches, Deployment-Stufen und Funktionsschalter. Sie lösen dasselbe Problem in verschiedenen Teilen des Workflows: Git — in den Phasen vor dem Deployment, Deployment — beim Wechsel von altem Code zu neuem, und Funktionsschalter — für die kontrollierte Einführung neuen Codes.
Der effektivste Weg sind Funktionsschalter (und gleichzeitig die einfachste Lösung zum Verstehen und Nutzen). Mit ihnen kann man auf die anderen beiden Methoden vollständig verzichten. Besonders nützlich ist es, das Deployment zu entfernen – wenn wir ohnehin Funktionsschalter verwenden, um Code zu aktivieren, dann schafft der Schritt, Server auf neuen Code umzuschalten, nur zusätzliche Risiken.
Git ist schwierig zu nutzen, besonders für Anfänger, was es stark einschränkt, aber es hat dafür einige nützliche Branches. Wir haben viele Nachteile von Git geglättet. Dark wird in Echtzeit bearbeitet und bietet die Möglichkeit zur Zusammenarbeit im Stil von Google Docs, sodass man keinen Code senden muss und seltener Rebases und Merges durchführen kann.
Funktionsschalter bilden die Grundlage für ein sicheres Deployment. Zusammen mit Instant Deployments ermöglichen sie ein schnelles Testen von Konzepten in kleinen, risikoarmen Fragmenten, anstatt eine große Änderung anzuwenden, die das System zum Absturz bringen könnte.
Versionskontrolle
Zur Änderung von Funktionen und Typen verwenden wir die Versionskontrolle. Wenn Sie eine Funktion ändern möchten, erstellt Dark eine neue Version dieser Funktion. Dann können Sie diese Version über den Schalter im HTTP-Handler oder in den Ereignissen aufrufen. (Wenn die Funktion tief im Aufrufgraphen liegt, wird währenddessen für jede Funktion eine neue Version erstellt. Es mag übertrieben erscheinen, aber Funktionen stören sich nicht, wenn Sie sie nicht verwenden; das werden Sie nicht einmal bemerken.)
Aus denselben Gründen versionieren wir auch Typen. Über unser System von Typen haben wir ausführlich berichtet .
Dank der Versionskontrolle von Funktionen und Typen können Sie Änderungen schrittweise in die Anwendung einbringen. Sie können überprüfen, ob jeder einzelne Handler mit der neuen Version funktioniert, ohne sofort alle Änderungen in die Anwendungen einzubringen (aber wir haben Tools, um dies schnell zu tun, falls Sie es wünschen).
Das ist viel sicherer als die vollständige Bereitstellung aller Änderungen gleichzeitig, wie es derzeit der Fall ist.
Neue Versionen von Paketen und der Standardbibliothek
Wenn Sie ein Paket in Dark aktualisieren, ersetzen wir nicht sofort die Verwendung jeder Funktion oder jedes Typs im gesamten Code. Das wäre unsicher. Der Code verwendet weiterhin die gleiche Version, die er verwendet hat, während Sie die Verwendung von Funktionen und Typen für jeden einzelnen Fall auf die neue Version mit Hilfe von Schaltern aktualisieren.
Screenshot eines Teils des automatischen Prozesses in Dark, der zwei Versionen der Funktion Dict::get zeigt. Dict::get_v0 gab den Typ Any zurück (von dem wir uns distanzieren), während Dict::get_v1 den Typ Option zurückgibt.
Wir bieten oft eine neue Funktion der Standardbibliothek an und schließen alte Versionen aus. Benutzer mit alten Versionen im Code behalten den Zugang zu ihnen, aber neue Benutzer können sie nicht erhalten. Wir planen, Tools bereitzustellen, um Benutzer von alten Versionen in einem Schritt auf neue zu migrieren, ebenfalls mit Funktionen-Schaltern.
Dark bietet auch die einzigartige Möglichkeit: Da wir Ihren Arbeitscode ausführen, können wir neue Versionen selbst testen, indem wir die Ausgaben für neue und alte Anfragen vergleichen, um Sie über Änderungen zu informieren. Schließlich stellt ein Update der Pakete, das oft im Dunklen durchgeführt wird (oder sorgfältige Sicherheitstests erfordert), ein wesentlich geringeres Risiko dar und kann automatisch erfolgen.
Neue Versionen von Dark
Der Übergang von Python 2 zu Python 3 zog sich über ein ganzes Jahrzehnt und bleibt ein Problem. Da wir Dark für kontinuierliche Bereitstellung entwickeln, müssen wir diese Sprachänderungen berücksichtigen.
Wenn wir kleine Änderungen an der Sprache vornehmen, erstellen wir eine neue Version von Dark. Der alte Code bleibt in der alten Version von Dark, während der neue Code in der neuen Version verwendet wird. Für den Übergang zur neuen Version von Dark können Schalter oder Funktion-Versionen verwendet werden.
Dies ist besonders nützlich, da Dark noch recht neu ist. Viele Änderungen in der Sprache oder Bibliothek könnten misslingen. Das schrittweise Versionieren der Sprache ermöglicht es uns, kleinere Aktualisierungen vorzunehmen, sodass wir uns nicht beeilen müssen und viele Sprachentscheidungen aufschieben können, bis wir mehr Benutzer und damit mehr Informationen haben.
Datenbankmigrationen
Für eine sichere Migration von Datenbanken gibt es :
- Den Code neu schreiben, um neue und alte Formate zu unterstützen
- Alle Daten in das neue Format umwandeln
- Den alten Datenzugriff entfernen
Insgesamt zieht sich die Migration von Datenbanken und erfordert viele Ressourcen. Und wir haben veraltete Schemata, denn selbst einfache Aufgaben, wie das Umbenennen einer Tabelle oder einer Spalte, sind den Aufwand nicht wert.
Dark verfügt über eine effektive Plattform für die Migration von Datenbanken, die (so hoffen wir) den Prozess so stark vereinfacht, dass Sie keine Angst mehr davor haben werden. Alle Datenspeicher in Dark (Schlüssel-Wert-Paare oder permanente Hash-Tabellen) haben einen Typ. Um den Datenspeicher zu verschieben, weisen Sie ihm einfach einen neuen Typ und eine Rückfall- und Rollback-Funktion zu, um die Werte zwischen zwei Typen umzuwandeln.
Der Zugang zu Datenbanken in Dark erfolgt über versionierte Variablenamen. Beispielsweise wird das Datenbank-Repository Users zunächst Users-v0 genannt. Wenn eine neue Version mit einem anderen Typ erstellt wird, ändert sich der Name zu Users-v1. Wenn Daten über Users-v0 gespeichert sind und Sie darauf über Users-v1 zugreifen, wird die Rollforward-Funktion angewendet. Wenn Daten über Users-v1 gespeichert wurden und Sie darauf über Users-v0 zugreifen, wird die Rollback-Funktion angewendet.
Der Bildschirm zur Migration der Datenbank mit den Feldnamen der alten Datenbank, den Rollforward- und Rollback-Ausdrücken sowie Anweisungen zur Aktivierung der Migration.
Verwenden Sie Feature-Schalter, um Aufrufe von Users-v0 an die Version Users-v1 weiterzuleiten. Dies kann nach einem HTTP-Handler auf einmal gemacht werden, um die Risiken zu minimieren, und die Schalter funktionieren für einzelne Benutzer, sodass Sie überprüfen können, ob alles wie erwartet funktioniert. Wenn keine Benutzer mehr bei Users-v0 sind, konvertiert Dark alle verbleibenden Daten im Hintergrund vom alten Format in das neue. Sie werden das nicht einmal bemerken.
Tests
Dark ist eine und unveränderlichen Werten, weshalb die Testfläche wesentlich kleiner ist als bei objektorientierten Sprachen mit dynamischer Typisierung. Aber man muss trotzdem testen.
In Dark führt der Editor automatisch Modultests im Hintergrund für den bearbeitbaren Code aus und führt standardmäßig diese Tests für alle Feature-Schalter durch. In Zukunft möchten wir durch statische Typen automatisch Fuzzing des Codes durchführen, um Bugs zu finden.
Darüber hinaus führt Dark Ihre Infrastruktur in der Produktion aus, was neue Möglichkeiten eröffnet. Wir speichern HTTP-Anfragen automatisch in der Dark-Infrastruktur (derzeit speichern wir alle Anfragen, möchten aber später zu einer Auswahl übergehen). Wir testen neuen Code anhand dieser Anfragen und führen Modultests durch, und falls gewünscht, können Sie interessante Anfragen leicht in Modultests umwandeln.
Wovon wir uns verabschiedet haben
Da wir kein Deployment haben, sondern Feature-Schalter, bleiben etwa 60 % der Deployment-Pipeline außen vor. Wir benötigen keine Git-Branches oder Pull-Requests, keine Erstellung von Backend-Ressourcen und Containern, kein Versand von Ressourcen und Containern in Registries oder Deployment-Schritte in Kubernetes.
Vergleich der Standard-Pipeline für kontinuierliche Lieferung (links) und der Dunkel-Pipeline (rechts). In der Dunkel-Pipeline besteht die Lieferung aus 6 Schritten und einem Zyklus, während die traditionelle Version 35 Schritte und 3 Zyklen umfasst.
In der Dunkel-Pipeline gibt es insgesamt 6 Schritte und 1 Zyklus (Schritte, die mehrmals wiederholt werden), während die moderne Pipeline für kontinuierliche Lieferung aus 35 Schritten und 3 Zyklen besteht. In Dunkel werden die Tests automatisch gestartet, und Sie sehen das sogar nicht; Abhängigkeiten werden automatisch installiert; alles, was mit Git oder Github zu tun hat, ist nicht mehr notwendig; das Erstellen, Testen und Versenden von Docker-Containern ist nicht erforderlich; das Bereitstellen in Kubernetes ist nicht mehr nötig.
Selbst die verbleibenden Schritte in Dunkel sind einfacher geworden. Da Funktionstasten mit einer einzigen Aktion gesteuert werden können, muss man nicht ein zweites Mal die gesamte Bereitstellungspipeline durchlaufen, um alten Code zu entfernen.
Wir haben unser Bestes getan, um die Bereitstellung von Code zu vereinfachen, indem wir Zeit und Risiken der kontinuierlichen Lieferung reduziert haben. Außerdem haben wir die Aktualisierung von Paketen, Datenbankmigrationen, Tests, Versionsverwaltung, Abhängigkeitsmanagement, Konsistenz zwischen Entwicklungs- und Produktionsumgebung und schnelle sowie sichere Version-Upgrades des Codes erheblich vereinfacht.
Ich beantworte Fragen dazu auf .
Um mehr über die Dunkel-Pipeline zu erfahren, lesen Sie , (oder auf ) oder . Wenn Sie im September zur StrangeLoop fahren, .
Quelle: habr.com
