
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 dabei zu stören oder Ausfälle zu verursachen. Die Bereitstellung in solchen Systemen wird zu einer komplexen Herausforderung und erfordert selbst in kleinen Teams anspruchsvolle Continuous-Delivery-Pipelines.
Diese Pipelines haben in der Regel eine eingeschränkte Anwendung, funktionieren langsam und sind nicht besonders zuverlässig. Entwickler müssen sie zuerst manuell erstellen und dann verwalten, was häufig dazu führt, dass ganze DevOps-Teams eingestellt werden.
Die Geschwindigkeit dieser Pipelines bestimmt die Entwicklungsgeschwindigkeit. Bei den besten Teams dauert das Bereitstellen 5–10 Minuten, aber in der Regel dauert es viel länger, und für eine Bereitstellung werden mehrere Stunden benötigt.
Bei Dark dauert dies 50 ms. Fünfzig. Millisekunden. Dark – , speziell für kontinuierliche Bereitstellung entwickelt, und alle Aspekte von Dark, einschließlich der Sprache selbst, sind mit Blick auf sichere, sofortige Bereitstellung aufgebaut.
Warum sind Pipelines für kontinuierliche Bereitstellung so langsam?
Angenommen, wir haben eine Python-Webanwendung und haben bereits eine hervorragende und moderne Pipeline für die kontinuierliche Bereitstellung erstellt. Für einen Entwickler, der täglich an diesem Projekt arbeitet, sieht die Bereitstellung einer kleinen Änderung ungefähr so aus:
Änderungen vornehmen
- Erstellen eines neuen Branches in git
- Änderungen über den Funktionsschalter vornehmen
- Modultests zur Überprüfung der Änderungen mit und ohne Funktionsschalter
Pull-Request
- Änderungen committen
- Änderungen in das entfernte Repository auf GitHub senden
- Pull-Request
- CI-Bau läuft automatisch im Hintergrund
- Code-Überprüfung
- Noch ein paar Überprüfungen, wenn nötig
- Änderungen mit dem Master von git mergen.
CI wird auf dem Master ausgeführt
- Installation der Frontend-Abhängigkeiten über npm
- Bau und Optimierung der HTML+CSS+JS-Ressourcen
- Durchführung von modularen und funktionalen Tests im Frontend
- Installation der Python-Abhängigkeiten aus PyPI
- Durchführung von modularen und funktionalen Tests im Backend
- Integrationstests an beiden Enden
- Bereitstellung von Frontend-Ressourcen im CDN
- Container für das Python-Programm erstellen
- Container in das Registry hochladen
- Kubernetes-Manifest aktualisieren
Alten Code durch neuen ersetzen
- Kubernetes startet mehrere Instanzen des neuen Containers
- Kubernetes wartet, bis die Instanzen betriebsbereit sind
- Kubernetes fügt die Instanzen dem HTTP-Load-Balancer hinzu
- Kubernetes wartet, bis die alten Instanzen nicht mehr verwendet werden
- Kubernetes stoppt die alten Instanzen
- Kubernetes wiederholt diese Vorgänge, bis alle alten Instanzen ersetzt sind
Neuen Funktionsschalter aktivieren
- Neuer Code wird zunächst nur für sich selbst aktiviert, um sicherzustellen, dass alles funktioniert
- Neuer Code wird für 10% der Nutzer aktiviert, betriebliche und geschäftliche Metriken werden überwacht
- Neuer Code wird für 50% der Nutzer aktiviert, betriebliche und geschäftliche Metriken werden überwacht
- Neuer Code wird für 100% der Nutzer aktiviert, betriebliche und geschäftliche Metriken werden überwacht
- Schließlich wiederholen Sie den gesamten Prozess, um den alten Code und den Schalter zu entfernen
Der Prozess hängt von den Werkzeugen, der Sprache und der Nutzung serviceorientierter Architekturen ab, sieht aber im Großen und Ganzen so aus. Ich habe die Bereitstellungen mit Datenbankmigrationen nicht erwähnt, da hierfür eine sorgfältige Planung erforderlich ist, aber weiter unten erkläre ich, wie Dark damit umgeht.
Es gibt viele Komponenten, von denen viele leicht ins Stocken geraten, abstürzen, zeitliche Konflikte verursachen oder das Arbeitssystem zum Absturz bringen können.
Da diese Pipelines fast immer für spezielle Anlässe erstellt werden, ist es schwierig, sich auf sie zu verlassen. Viele erleben Tage, an denen der Code nicht bereitgestellt werden kann, weil es Probleme mit der Dockerfile gibt, einer der vielen Services ausgefallen ist oder der benötigte Spezialist im Urlaub ist.
Schlimmer noch, viele dieser Schritte bringen überhaupt keinen Nutzen. Sie waren früher notwendig, als wir den Code direkt für die Nutzer bereitstellten, aber jetzt haben wir Umschalter für neuen Code, und diese Prozesse haben sich getrennt. Dadurch ist der Schritt, bei dem der Code bereitgestellt wird (alter Code wird durch neuen ersetzt), zu einem unnötigen Risiko geworden.
Natürlich ist dies ein sehr durchdachter Pipeline. Das Team, das ihn erstellt hat, hat keine Zeit und Kosten gescheut, um eine schnelle Implementierung zu gewährleisten. Üblicherweise sind Deployment-Pipelines deutlich langsamer und unzuverlässiger.
Implementierung der kontinuierlichen Lieferung bei Dark
Die kontinuierliche Lieferung ist für Dark so wichtig, dass wir von Anfang an auf eine Zeit von weniger als einer Sekunde abgezielt haben. Wir haben alle Schritte der Pipeline durchgesehen, um Überflüssiges zu eliminieren und den Rest zu optimieren. So haben wir Schritte entfernt.
Jessie Frazelle () prägte das neue Wort „deployless“ (keine Bereitstellung erforderlich) auf der Konferenz Future of Software Development in Reykjavik.
Wir haben sofort entschieden, dass Dark auf dem Konzept „deployless“ basieren wird (danke für das Neologismus). Deployless bedeutet, dass jeder Code sofort bereitgestellt wird und zur Verwendung in der Produktion bereit ist. Natürlich überspringen wir keinen fehlerhaften oder unvollständigen Code (die Sicherheitsprinzipien werde ich weiter unten erläutern).
Während der Dark-Demo wurden wir häufig gefragt, wie wir die Bereitstellung so schnell beschleunigen konnten. Eine seltsame Frage. Die Leute scheinen zu denken, wir hätten eine Supertechnologie erfunden, die den Code vergleicht, ihn kompiliert, in ein Container-Paket packt, eine virtuelle Maschine startet und den Container kalt startet — und das alles in 50 ms. Wahrscheinlich ist das nicht möglich. Aber wir haben einen speziellen Bereitstellungs-Engine entwickelt, der all das nicht benötigt.
Dark startet Interpreter in der Cloud. Angenommen, Sie schreiben Code in einer Funktion oder einem HTTP-Handler oder Ereignis. Wir senden die Differenz zu einem abstrakten Syntaxbaum (die Code-Implementierung, die intern von unserem Editor und unseren Servern verwendet wird) an unsere Server und führen diesen Code aus, sobald Anfragen eintreffen. So sieht die Bereitstellung einfach wie ein bescheidener Datensatz in der Datenbank aus – sofort und elementar. Die Bereitstellung erfolgt so schnell, weil sie das absolute Minimum umfasst.
In Zukunft planen wir, Dark zu einem Infrastrukturkompiler zu machen, der die ideale Infrastruktur für hohe Leistung und Zuverlässigkeit von Anwendungen erstellen und bereitstellen kann. Die sofortige Bereitstellung wird dabei selbstverständlich nicht wegfallen.
Sichere Bereitstellung
Strukturierter Editor
Der Code in Dark wird im Dark-Editor geschrieben. Der strukturierte Editor verhindert syntaktische Fehler. Tatsächlich gibt es in Dark nicht einmal einen Parser. Während Sie Text eingeben, arbeiten wir direkt mit dem abstrakten Syntaxbaum (AST), so wie , , , und .
Jeder unvervollständigte Code in Dark hat eine zulässige Ausführungssemantik, ähnlich wie . Zum Beispiel, wenn Sie einen Funktionsaufruf ändern, speichern wir die alte Funktion, bis die neue brauchbar ist.
Jedes Programm in Dark hat seine eigene Bedeutung, sodass unvollständiger Code nicht die Ausführung des vollständigen Codes beeinträchtigt.
Bearbeitungsmodi
In Dark schreiben Sie Code in zwei Fällen. Erster Fall: Sie schreiben neuen Code und sind der einzige Benutzer. Zum Beispiel, wenn er sich in REPL befindet und andere Benutzer niemals darauf zugreifen können, oder es sich um einen neuen HTTP-Routen handelt, auf den Sie nirgendwo verweisen. Hier können Sie ohne besondere Vorsichtsmaßnahmen arbeiten, und genau so arbeiten Sie derzeit in der Entwicklungsumgebung.
Zweite Situation: Der Code wird bereits verwendet. Wenn über den Code Verkehr läuft (Funktionen, Ereignis-Handler, Datenbanken usw.), ist Vorsicht geboten. Daher blockieren wir den gesamten verwendeten Code und fordern, dass strukturiert gearbeitet wird, um ihn zu bearbeiten. Dazu informiere ich Sie weiter unten über strukturelle Werkzeuge: Funktions-Schalter für HTTP-Handler und Events, eine leistungsstarke Migrationsplattform für Datenbanken und eine neue Methoden zur Versionsverwaltung für Funktionen und Datentypen.
Funktions-Schalter
Eine der Möglichkeiten In Dark können mehrere Probleme mit einer einzigen Lösung behoben werden. Funktionstaster erfüllen viele verschiedene Aufgaben: Sie ersetzen die lokale Entwicklungsumgebung, verwalten Git-Zweige, setzen Code in Betrieb und ermöglichen natürlich eine herkömmliche, langsame und kontrollierte Veröffentlichung neuer Codes.
Die Erstellung und Bereitstellung eines Funktionstasters erfolgt in unserem Editor in einem einzigen Schritt. Er schafft einen leeren Raum für neuen Code und bietet Zugriffskontrolle für den alten und neuen Code sowie Schaltflächen und Befehle für einen schrittweisen Übergang zu neuem Code oder dessen Ausschluss.
Funktionstaster sind in die Dark-Sprache integriert, und selbst unvollständige Taster erfüllen ihre Aufgabe – wenn die Bedingung im Taster nicht erfüllt ist, wird der alte blockierte Code ausgeführt.
Entwicklungsumgebung
Funktionsschalter ersetzen die lokale Entwicklungsumgebung. Heute fällt es Teams schwer, sicherzustellen, dass alle dieselben Versionen von Tools und Bibliotheken (Code-Formatter, Linter, Paketmanager, Compiler, Preprozessoren, Testwerkzeuge usw.) verwenden. Mit Dark ist es nicht erforderlich, Abhängigkeiten lokal zu installieren, die lokale Docker-Installation zu verwalten oder andere Maßnahmen zu ergreifen, um zumindest eine Art Gleichheit zwischen Entwicklungs- und Produktionsumgebung zu erreichen. , werden wir nicht einmal so tun, als ob wir danach streben.
Anstatt eine geklonte lokale Umgebung zu schaffen, erstellt Dark mit den Schaltern eine neue Sandbox in der Produktion, die die Entwicklungsumgebung ersetzt. In Zukunft planen wir auch, Sandboxes für andere Teile der Anwendung zu erstellen (z. B. sofortige Klone von Datenbanken), obwohl dies derzeit nicht so wichtig erscheint.
Branches und Deployments
Es gibt jetzt mehrere Möglichkeiten, neuen Code in Systeme einzuführen: Git-Branches, Bereitstellungsstufen und Feature-Flags. Sie lösen dasselbe Problem in unterschiedlichen Teilen des Workflows: Git – in den Phasen vor der Bereitstellung, die Bereitstellung – beim Wechsel von altem zu neuem Code, und Feature-Flags – für die kontrollierte Einführung neuen Codes.
Der effektivste Weg sind Feature-Flags (dabei auch der einfachste zu verstehen und zu nutzen). Damit kann man vollständig auf die anderen beiden Methoden verzichten. Es ist besonders vorteilhaft, die Bereitstellung zu eliminieren – wenn wir ohnehin Feature-Flags nutzen, um den Code zu aktivieren, dann birgt der Schritt, Server auf neuen Code zu übertragen, nur unnötige Risiken.
Git ist schwer zu bedienen, besonders für Anfänger, und das schränkt seine Nutzung stark ein, aber es verfügt über nützliche Branches. Wir haben viele Nachteile von Git gemildert. Dark wird in Echtzeit bearbeitet und ermöglicht die Zusammenarbeit im Stil von Google Docs, sodass man den Code nicht verschicken und weniger häufig das Rebase und Mergen durchführen muss.
Funktionsumschalter sind die Grundlage für eine sichere Bereitstellung. Zusammen mit sofortigen Bereitstellungen ermöglichen sie es, Konzepte schnell in kleinen, risikoarmen Fragmenten zu testen, anstatt eine große Änderung vorzunehmen, die das System zum Absturz bringen könnte.
Versionsverwaltung
Für Änderungen an Funktionen und Typen verwenden wir Versionsverwaltung. Wenn Sie eine Funktion ändern möchten, erstellt Dark eine neue Version dieser Funktion. Sie können dann diese Version mit einem Schalter im HTTP-Handler oder in Ereignissen aufrufen. (Wenn diese Funktion tief im Aufrufgraphen liegt, wird währenddessen für jede Funktion eine neue Version erstellt. Das mag übertrieben erscheinen, aber die Funktionen stören sich nicht, solange Sie sie nicht verwenden, sodass Sie es nicht einmal bemerken werden.)
Aus denselben Gründen verwalten wir auch die Typen versioniert. Über unser Typensystem haben wir ausführlich berichtet. .
Dank der Versionierung von Funktionen und Typen können Sie Änderungen schrittweise am Programm vornehmen. Sie können überprüfen, ob jeder einzelne Handler mit der neuen Version funktioniert, ohne sofort alle Änderungen im Programm umzusetzen (aber wir haben Werkzeuge, um dies schnell zu tun, wenn Sie möchten).
Das ist viel sicherer, als alles auf einmal auszurollen, 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 zuvor verwendet wurde, und Sie aktualisieren die Verwendung von Funktionen und Typen auf die neue Version für jeden einzelnen Fall mit Hilfe von Schaltern.
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 absehen), während Dict::get_v1 den Typ Option zurückgibt.
Wir führen häufig neue Funktionen in der Standardbibliothek ein und entfernen alte Versionen. Nutzer mit älteren Versionen im Code haben weiterhin Zugriff darauf, während neue Nutzer diese nicht erhalten können. Wir planen, Werkzeuge bereitzustellen, die den Wechsel von alten zu neuen Versionen in nur einem Schritt ermöglichen, auch mithilfe von Funktion-Umschaltern.
Dark bietet außerdem eine 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. Dadurch wird das Aktualisieren von Paketen, welches oft blind (oder mit sorgfältigen Sicherheitstests) erfolgt, erheblich risikominimiert und kann automatisch geschehen.
Neue Versionen von Dark
Der Übergang von Python 2 zu Python 3 zog sich über ein Jahrzehnt und bleibt ein Problem. Da wir Dark für kontinuierliche Lieferung entwickeln, müssen diese Sprachänderungen berücksichtigt werden.
Bei kleinen Änderungen an der Sprache 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. Zum Wechsel zur neuen Version von Dark können Schalter oder Funktionsversionen eingesetzt werden.
Dies ist besonders hilfreich, da Dark erst vor kurzem eingeführt wurde. Viele Änderungen an der Sprache oder Bibliothek könnten sich als problematisch erweisen. Die schrittweise Versionierung der Sprache ermöglicht es uns, kleinere Updates durchzuführen, das heißt, wir können uns Zeit nehmen und viele Entscheidungen zur Sprache aufschieben, bis wir mehr Nutzer haben, was wiederum mehr Informationen bedeutet.
Datenbankmigrationen
Für eine sichere Migration der Datenbank gibt es :
- den Code so umzuschreiben, dass er neue und alte Formate unterstützt
- alle Daten in das neue Format umzuwandeln
- den alten Datenzugriff zu entfernen
Letztendlich zieht sich die Migration der Datenbank in die Länge und erfordert viele Ressourcen. Dabei sammeln sich veraltete Schemata an, denn selbst einfache Aufgaben wie die Korrektur des Namens einer Tabelle oder Spalte rechtfertigen nicht den Aufwand.
Dark bietet eine effiziente Plattform zur Migration von Datenbanken, die den Prozess so stark vereinfachen sollte, dass Sie keine Angst mehr davor haben. Alle Datenspeicher in Dark (entweder "Key-Value"-Stores oder persistente Hashtabellen) haben einen Typ. Um einen Datenspeicher zu migrieren, weisen Sie ihm einfach einen neuen Typ zu und verwenden eine Rollback- und Rollforward-Funktion, um die Werte zwischen den beiden Typen zu konvertieren.
Der Zugriff auf die Datenspeicher in Dark erfolgt über versionierte Variablenamen. Zum Beispiel wird der Datenspeicher "Users" zunächst "Users-v0" genannt. Wenn eine neue Version mit einem anderen Typ erstellt wird, ändert sich der Name in "Users-v1". Wenn Daten über "Users-v0" gespeichert wurden und Sie diese über "Users-v1" abfragen, wird die Rollforward-Funktion angewendet. Wenn Daten über "Users-v1" gespeichert wurden und Sie diese über "Users-v0" abfragen, kommt die Rollback-Funktion zur Anwendung.
Der Bildschirm zur Migration der Datenbank zeigt die Feldnamen der alten Datenbank, die Rollforward- und Rollback-Ausdrücke sowie Anweisungen zur Aktivierung der Migration an.
Verwenden Sie die Funktionstaster, um Anrufe von Users-v0 auf die Version Users-v1 umzuleiten. Dies kann handlerweise erfolgen, um Risiken zu minimieren, und die Taster arbeiten benutzerspezifisch, sodass Sie überprüfen können, ob alles wie erwartet funktioniert. Wenn keine Benutzer mehr in Users-v0 vorhanden sind, wird Dark alle verbleibenden Daten im Hintergrund vom alten Format in das neue konvertieren. Sie werden es kaum bemerken.
Testen
Dark ist und unveränderlichen Werten, weshalb die Testoberfläche im Vergleich zu objektorientierten Sprachen mit dynamischer Typisierung deutlich kleiner ist. Dennoch muss alles getestet werden.
In Dark führt der Editor automatisch im Hintergrund Modul-Tests für den bearbeiteten Code aus und führt standardmäßig diese Tests für alle Funktionstasten durch. In Zukunft möchten wir durch statische Typen automatisch Fuzz-Testing des Codes durchführen, um Fehler zu finden.
Darüber hinaus betreibt Dark Ihre Infrastruktur in der Produktion, was neue Möglichkeiten eröffnet. Wir speichern HTTP-Anfragen automatisch in der Dark-Infrastruktur (derzeit speichern wir alle Anfragen, planen jedoch, auf eine Auswahl umzusteigen). Wir testen neuen Code anhand dieser Anfragen und führen Modultests durch. Wenn Sie wünschen, können Sie interessante Anfragen problemlos in Modultests umwandeln.
Wovon wir uns getrennt haben
Da wir kein Deployment haben, sondern Funktionsschalter, bleiben etwa 60 % des Deployment-Pipelines außen vor. Wir benötigen keine Git-Branches oder Pull-Requests, keinen Bau von Backend-Ressourcen und Containern, kein Hochladen von Ressourcen und Containern in Repositories oder Deployment-Schritte in Kubernetes.
Vergleich zwischen einem Standard-Pipeline für kontinuierliche Lieferung (links) und der kontinuierlichen Lieferung von Dark (rechts). Bei Dark besteht die Lieferung aus 6 Schritten und einem Zyklus, während die traditionelle Version 35 Schritte und 3 Zyklen umfasst.
In Dark umfasst die Bereitstellung nur 6 Schritte und 1 Zyklus (Schritte, die mehrmals wiederholt werden), während moderne Continuous Delivery-Pipelines aus 35 Schritten und 3 Zyklen bestehen. In Dark werden Tests automatisch gestartet, und das geschieht, ohne dass Sie es bemerken; Abhängigkeiten werden automatisch installiert; alles, was mit Git oder Github zu tun hat, ist nicht mehr erforderlich; das Erstellen, Testen und Bereitstellen von Docker-Containern entfällt; die Bereitstellung in Kubernetes entfällt ebenfalls.
Selbst die verbleibenden Schritte in Dark sind einfacher geworden. Da die Feature-Flags mit einer einzigen Aktion gesteuert werden können, müssen Sie den gesamten Bereitstellungs-Pipeline nicht erneut durchlaufen, um alten Code zu entfernen.
Wir haben die Bereitstellung von Code so weit wie möglich vereinfacht, indem wir die Zeit und Risiken der kontinuierlichen Bereitstellung minimiert haben. Außerdem haben wir das Aktualisieren von Paketen, die Migration von Datenbanken, das Testen, das Version Management, die Installation von Abhängigkeiten, die Gleichheit zwischen Entwicklungs- und Produktionsumgebungen sowie schnelle und sichere Version-Upgrades des Programmiersprachen erheblich vereinfacht.
Ich beantworte Fragen dazu auf .
Um mehr über Dark zu erfahren, lesen Sie die , (oder ) oder . Wenn Sie im September zur StrangeLoop fahren, .
Quelle: habr.com
