
Je schneller der Entwicklungsprozess, desto schneller wächst das Technologieunternehmen.
Leider arbeiten moderne Anwendungen gegen uns – unsere Systeme müssen in Echtzeit aktualisiert werden, ohne jemanden zu stören oder Ausfallzeiten oder Unterbrechungen zu verursachen. Die Bereitstellung auf solchen Systemen stellt eine Herausforderung dar und erfordert selbst für kleine Teams komplexe Continuous-Delivery-Pipelines.
Diese Pipelines haben typischerweise einen geringen Umfang, sind langsam und unzuverlässig. Entwickler müssen sie zunächst manuell erstellen und dann verwalten, und Unternehmen stellen dafür oft ganze DevOps-Teams ein.
Die Geschwindigkeit der Entwicklung hängt von der Geschwindigkeit dieser Pipelines ab. Die besten Teams sind innerhalb von 5 bis 10 Minuten einsatzbereit, in der Regel dauert es jedoch viel länger und erfordert mehrere Stunden pro Bereitstellung.
Im Dunkeln dauert dies 50 ms. Fünfzig. Millisekunden. Dunkel - , speziell für die kontinuierliche Bereitstellung entwickelt, und jeder Aspekt von Dark, einschließlich der Sprache selbst, ist auf eine sichere, sofortige Bereitstellung ausgelegt.
Warum sind Continuous-Delivery-Pipelines so langsam?
Nehmen wir an, wir haben eine Python-Webanwendung und haben bereits eine wunderbare und moderne Continuous-Delivery-Pipeline erstellt. Für einen Entwickler, der jeden Tag mit diesem Projekt beschäftigt ist, würde die Bereitstellung einer kleinen Änderung etwa so aussehen:
Alteration
- Erstellen eines neuen Zweigs in Git
- Änderungen hinter dem Funktionsschalter vornehmen
- Unit-Tests zum Testen von Änderungen mit und ohne Funktionsschalter
Pull-Anfrage
- Änderungen übernehmen
- Änderungen an ein Remote-Github-Repository übertragen
- Pull-Anfrage
- Der CI-Build wird automatisch im Hintergrund ausgeführt
- Code-Review
- Bei Bedarf noch ein paar Rezensionen
- Änderungen mit Git Master zusammenführen.
CI läuft auf dem Master
- Frontend-Abhängigkeiten über npm installieren
- Erstellen und Optimieren von HTML+CSS+JS-Ressourcen
- Ausführen von Unit- und Funktionstests im Frontend
- Installieren von Python-Abhängigkeiten von PyPI
- Ausführen von Unit- und Funktionstests im Backend
- Testen der Integration auf beiden Seiten
- Senden von Frontend-Ressourcen an CDN
- Erstellen eines Containers für ein Python-Programm
- Senden eines Containers an die Registrierung
- Aktualisieren Sie das Kubernetes-Manifest
Ersetzen des alten Codes durch neuen
- Kubernetes startet mehrere Instanzen eines neuen Containers
- Kubernetes wartet darauf, dass Instanzen fehlerfrei werden
- Kubernetes fügt Instanzen zum HTTP-Load-Balancer hinzu
- Kubernetes wartet, bis ältere Instanzen nicht mehr verwendet werden
- Kubernetes stoppt alte Instanzen
- Kubernetes wiederholt diese Vorgänge, bis die neuen Instanzen alle alten ersetzt haben.
Aktivieren eines neuen Funktionsschalters
- Der neue Code wird nur für Sie eingefügt, um sicherzustellen, dass alles in Ordnung ist
- Neuer Code für 10 % der Benutzer aktiviert, Betriebs- und Geschäftsmetriken verfolgt
- Neuer Code für 50 % der Benutzer aktiviert, Betriebs- und Geschäftsmetriken verfolgt
- Neuer Code für 100 % der Benutzer aktiviert, Betriebs- und Geschäftsmetriken verfolgt
- Abschließend wiederholen Sie den gesamten Vorgang, um den alten Code zu entfernen und zu wechseln
Der Prozess hängt von den Tools, der Sprache und der Verwendung serviceorientierter Architekturen ab, aber im Allgemeinen sieht es so aus. Ich habe Datenbankmigrationsbereitstellungen nicht erwähnt, da sie eine sorgfältige Planung erfordern, aber ich werde unten erklären, wie Dark damit umgeht.
Hier gibt es viele Komponenten, und viele von ihnen können leicht langsamer werden, abstürzen, vorübergehende Konflikte verursachen oder das funktionierende System zum Absturz bringen.
Und da diese Pipelines fast immer für einen Sonderfall erstellt werden, ist es schwierig, sich darauf zu verlassen. Viele Menschen haben Tage, an denen der Code nicht bereitgestellt werden kann, weil es Probleme in der Docker-Datei gibt, einer der Dutzenden Dienste abgestürzt ist oder der erforderliche Spezialist im Urlaub ist.
Schlimmer noch, viele dieser Schritte bewirken überhaupt nichts Nützliches. Diese waren früher erforderlich, als wir Code direkt für Benutzer bereitgestellt haben, aber jetzt haben wir Schalter für neuen Code und diese Prozesse wurden getrennt. Dadurch ist der Schritt, in dem der Code bereitgestellt wird (der alte wird durch den neuen ersetzt), nun einfach zu einem unnötigen Risiko geworden.
Natürlich ist dies eine sehr durchdachte Pipeline. Das Team, das es entwickelt hat, hat keine Zeit und kein Geld gescheut, um es schnell bereitzustellen. Normalerweise sind Bereitstellungspipelines viel langsamer und unzuverlässiger.
Implementierung von Continuous Delivery in Dark
Kontinuierliche Lieferung ist für Dark so wichtig, dass wir von Anfang an eine Zeitspanne von weniger als einer Sekunde angestrebt haben. Wir sind alle Schritte der Pipeline durchgegangen, um alles Unnötige zu entfernen, und haben den Rest aufpoliert. So haben wir die Stufen entfernt.
Jesse Frazel () prägte auf der Konferenz „Future of Software Development“ in Reykjavik ein neues Wort „deployless“ (keine Bereitstellung erforderlich).
Wir haben sofort entschieden, dass Dark auf dem „Deployless“-Konzept basieren würde (danke). für Neologismus). Deployless bedeutet, dass jeder Code sofort bereitgestellt wird und für die Verwendung in der Produktion bereit ist. Selbstverständlich lassen wir keinen kaputten oder unvollständigen Code durch (die Sicherheitsprinzipien beschreibe ich weiter unten).
Bei der Dark-Demo wurden wir oft gefragt, wie wir es geschafft haben, die Bereitstellung so schnell zu beschleunigen. Komische Frage. Die Leute denken wahrscheinlich, dass wir eine Art Supertechnologie entwickelt haben, die Code vergleicht, kompiliert, in einen Container packt, eine virtuelle Maschine startet, einen Container kalt startet und so weiter – und das alles in 50 ms. Es ist kaum möglich. Aber wir haben eine spezielle Deployment-Engine erstellt, die das alles nicht braucht.
Dark führt Dolmetscher in der Cloud aus. Nehmen wir an, Sie schreiben Code in einer Funktion oder einem HTTP- oder Event-Handler. Wir senden den Unterschied zum abstrakten Syntaxbaum (die Codeimplementierung, die unser Editor und unsere Server intern verwenden) an unsere Server und führen diesen Code dann aus, wenn Anfragen eingehen. Die Bereitstellung sieht also nur wie ein bescheidener Eintrag in die Datenbank aus – augenblicklich und elementar. Die Bereitstellung geht so schnell vonstatten, weil sie nur das Nötigste erfordert.
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 betreibt. Die sofortige Bereitstellung bleibt natürlich erhalten.
Sichere Bereitstellung
Strukturierter Editor
Code in Dark wird im Dark-Editor geschrieben. Der strukturierte Editor lässt keine Syntaxfehler zu. Tatsächlich verfügt Dark nicht einmal über einen Analysator. Während Sie tippen, arbeiten wir direkt mit dem Abstract Syntax Tree (AST). , , , и .
Jeder unvollendete Code in Dark verfügt über eine gültige Ausführungssemantik, so etwas wie . Wenn Sie beispielsweise einen Funktionsaufruf ändern, speichern wir die alte Funktion, bis die neue verwendbar ist.
Jedes Programm in Dark hat seine eigene Bedeutung, daher verhindert unvollendeter Code nicht, dass fertiger Code funktioniert.
Bearbeitungsmodi
In zwei Situationen schreiben Sie Code in Dark. Erstens: Sie schreiben neuen Code und sind der einzige Benutzer. Beispielsweise befindet es sich in einer REPL und andere Benutzer werden niemals darauf zugreifen, oder es handelt sich um eine neue HTTP-Route, auf die Sie nirgendwo verweisen. Hier können Sie ohne jegliche Vorsichtsmaßnahmen arbeiten, und jetzt arbeiten Sie in der Entwicklungsumgebung im Großen und Ganzen so.
Zweite Situation: Der Code wird bereits verwendet. Wenn Datenverkehr durch Ihren Code fließt (Funktionen, Ereignishandler, Datenbanken, Typen), müssen Sie vorsichtig sein. Zu diesem Zweck blockieren wir den gesamten verwendeten Code und erfordern den Einsatz strukturierterer Tools zur Bearbeitung. Im Folgenden werde ich auf die strukturellen Tools eingehen: Funktionsschalter für HTTP und Event-Handler, eine leistungsstarke Migrationsplattform für Datenbanken und eine neue Versionierungsmethode für Funktionen und Typen.
Funktionsschalter
Odins Weg im Dunkeln – Beheben Sie mehrere Probleme mit einer Lösung. Feature-Switches erfüllen viele verschiedene Aufgaben: Ersetzen der lokalen Entwicklungsumgebung, Git-Branches, Bereitstellen von Code und natürlich die traditionelle langsame und kontrollierte Veröffentlichung von neuem Code.
Das Erstellen und Bereitstellen eines Funktionsschalters erfolgt in einem Vorgang in unserem Editor. Es schafft Leerraum für neuen Code und bietet Zugriffskontrollen für alten und neuen Code sowie Schaltflächen und Befehle zum schrittweisen Einführen oder Entfernen von neuem Code.
Funktionsschalter sind in die Dark-Sprache integriert, und selbst unvollständige Schalter erfüllen ihren Zweck – wenn die Bedingung im Schalter nicht erfüllt ist, wird der alte blockierte Code ausgeführt.
Entwicklungsumgebung
Funktionsschalter ersetzen die lokale Entwicklungsumgebung. Heutzutage fällt es Teams schwer sicherzustellen, dass alle dieselben Versionen von Tools und Bibliotheken (Codeformatierer, Linters, Paketmanager, Compiler, Präprozessoren, Testtools usw.) verwenden. Mit Dark ist es nicht erforderlich, Abhängigkeiten lokal zu installieren. Verwalten Sie eine lokale Docker-Installation oder ergreifen Sie andere Maßnahmen, um zumindest einen Anschein von Gleichheit zwischen Entwicklungs- und Produktionsumgebung sicherzustellen. wir werden nicht einmal so tun, als würden wir danach streben.
Anstatt eine geklonte lokale Umgebung zu erstellen, erstellen Switches in Dark eine neue Produktions-Sandbox, die die Entwicklungsumgebung ersetzt. In Zukunft planen wir auch, eine Sandbox für andere Teile der Anwendung zu erstellen (z. B. sofortige Datenbankklone), obwohl dies derzeit keine so große Sache zu sein scheint.
Zweige und Bereitstellungen
Mittlerweile gibt es mehrere Möglichkeiten, neuen Code in Systeme zu integrieren: Git-Branches, die Bereitstellungsphase und Funktionswechsel. Sie lösen das gleiche Problem in verschiedenen Teilen des Workflows: Git für Schritte vor der Bereitstellung, Bereitstellung für den Übergang von altem Code zu neuem Code und Funktionsschalter für die kontrollierte Freigabe von neuem Code.
Der effizienteste Weg ist mit Funktionsschaltern (und auch am einfachsten zu verstehen und zu verwenden). Mit ihnen können Sie auf die beiden anderen Methoden komplett verzichten. Es ist besonders nützlich, die Bereitstellung zu entfernen. Wenn wir weiterhin Funktionsschalter verwenden, um Code zu aktivieren, birgt der Schritt der Servermigration auf den neuen Code nur unnötige Risiken.
Git ist besonders für Anfänger schwierig zu verwenden und sehr einschränkend, verfügt aber über praktische Zweige. Wir haben viele der Mängel von Git behoben. Dark kann in Echtzeit bearbeitet werden und ermöglicht eine Zusammenarbeit im Google Docs-Stil, sodass Sie keinen Code übermitteln müssen und seltener Rebases und Zusammenführungen durchführen können.
Funktionswechsel sind der Kern einer sicheren Bereitstellung. Zusammen mit sofortigen Bereitstellungen ermöglichen sie Ihnen, Konzepte schnell in kleinen, risikoarmen Teilen zu testen, anstatt eine große Änderung vorzunehmen, die das System zum Absturz bringen kann.
Versionierung
Wir verwenden Versionierung, um Funktionen und Typen zu ändern. Wenn Sie eine Funktion ändern möchten, erstellt Dark eine neue Version dieser Funktion. Sie können diese Version dann über einen Schalter im HTTP- oder Event-Handler aufrufen. (Wenn es sich um eine Funktion tief im Aufrufdiagramm handelt, wird unterwegs eine neue Version jeder Funktion erstellt. Das mag übertrieben erscheinen, aber die Funktionen stören nicht, es sei denn, Sie verwenden sie, also werden Sie es nicht einmal tun beachten.)
Aus den gleichen Gründen versionieren wir Typen. Wir haben ausführlich über unser Typensystem gesprochen .
Durch die Versionierung von Funktionen und Typen können Sie schrittweise Änderungen an Ihrer Anwendung vornehmen. Sie können überprüfen, ob jeder einzelne Handler mit der neuen Version funktioniert, ohne alle Änderungen an Ihren Anwendungen auf einmal vornehmen zu müssen (wir verfügen jedoch über Tools, mit denen Sie dies schnell erledigen können, wenn Sie möchten).
Das ist viel sicherer, als alles auf einmal komplett einzusetzen, wie es jetzt der Fall ist.
Neue Paketversionen und Standardbibliothek
Wenn Sie ein Paket in Dark aktualisieren, ersetzen wir nicht sofort die Verwendung jeder Funktion oder jedes Typs in der gesamten Codebasis. Es ist nicht sicher. Der Code verwendet weiterhin dieselbe Version, die er verwendet hat, und Sie aktualisieren die Verwendung von Funktionen und Typen von Fall zu Fall mithilfe von Schaltern auf die neue Version.
Screenshot eines Teils des automatischen Prozesses in Dark, der zwei Versionen der Dict::get-Funktion zeigt. Dict::get_v0 hat einen Any-Typ zurückgegeben (den wir verwerfen), und Dict::get_v1 hat einen Option-Typ zurückgegeben.
Wir stellen häufig eine neue Funktion der Standardbibliothek bereit und schließen ältere Versionen aus. Benutzer mit älteren Codeversionen haben weiterhin Zugriff darauf, neue Benutzer können jedoch nicht darauf zugreifen. Wir werden Tools bereitstellen, mit denen Benutzer in einem Schritt von alten Versionen auf neue migrieren können, wiederum mithilfe von Funktionsschaltern.
Dark bietet außerdem eine einzigartige Funktion: Sobald wir Ihren Produktionscode ausführen, können wir neue Versionen selbst testen und die Ausgabe neuer und alter Anfragen vergleichen, um Sie über die Änderungen zu informieren. Dadurch sind Paketaktualisierungen, die oft blind durchgeführt werden (oder umfangreiche Sicherheitstests erfordern), viel weniger riskant und können automatisch erfolgen.
Neue Versionen von Dark
Der Übergang von Python 2 zu Python 3 dauerte ein Jahrzehnt und ist immer noch ein Problem. Da wir Dark für Continuous Delivery entwickeln, müssen diese Sprachänderungen berücksichtigt werden.
Wenn wir kleine Änderungen an der Sprache vornehmen, erstellen wir eine neue Version von Dark. Der alte Code verbleibt in der alten Version von Dark und der neue Code wird in der neuen Version verwendet. Sie können Schalter oder Funktionsversionen verwenden, um auf eine neue Version von Dark zu aktualisieren.
Dies ist besonders nützlich, wenn man bedenkt, dass Dark erst kürzlich eingeführt wurde. Viele Änderungen an einer Sprache oder Bibliothek können fehlschlagen. Die inkrementelle Sprachversionierung ermöglicht es uns, kleinere Aktualisierungen vorzunehmen, was bedeutet, dass wir uns Zeit nehmen und viele Entscheidungen über die Sprache verschieben können, bis wir mehr Benutzer und damit mehr Informationen haben.
Datenbankmigrationen
Für eine sichere Datenbankmigration gibt es :
- Schreiben Sie den Code neu, um neue und alte Formate zu unterstützen
- Konvertieren Sie alle Daten in ein neues Format
- Alten Datenzugriff löschen
Daher dauert die Datenbankmigration lange und erfordert viele Ressourcen. Und am Ende häufen sich veraltete Schemata, weil sich selbst einfache Aufgaben wie das Korrigieren eines Tabellen- oder Spaltennamens nicht lohnen.
Dark verfügt über eine effiziente Datenbankmigrationsplattform, die (wir hoffen) den Prozess so einfach macht, dass Sie keine Angst davor haben werden. Alle Datenspeicher in Dark (Schlüsselwertspeicher oder persistente Hashtabellen) haben einen Typ. Um einen Datenspeicher zu migrieren, weisen Sie ihm einfach einen neuen Typ sowie eine Rollback- und Rollforward-Funktion zu, um die Werte zwischen den beiden Typen zu konvertieren.
Auf Datenspeicher in Dark wird über versionierte Variablennamen zugegriffen. Beispielsweise wird der Datenspeicher „Benutzer“ zunächst „Benutzer-v0“ heißen. Wenn eine neue Version mit einem anderen Typ erstellt wird, ändert sich der Name in Users-v1. Wenn Daten über Users-v0 gespeichert werden und Sie über Users-v1 darauf zugreifen, wird die Rollforward-Funktion angewendet. Wenn Daten über Users-v1 gespeichert werden und Sie über Users-v0 darauf zugreifen, wird die Rollback-Funktion angewendet.
Datenbankmigrationsbildschirm mit alten Datenbankfeldnamen, Rollforward- und Rollback-Ausdrücken sowie Anweisungen zum Aktivieren der Migration.
Verwenden Sie Funktionsschalter, um Anrufe an Benutzer-v0 an Benutzer-v1 weiterzuleiten. Dies kann jeweils für einen HTTP-Handler erfolgen, um Risiken zu mindern, und die Schalter funktionieren für einzelne Benutzer, sodass Sie überprüfen können, ob alles wie erwartet funktioniert. Wenn keine Users-v0s mehr übrig sind, konvertiert Dark alle verbleibenden Daten im Hintergrund vom alten Format in das neue. Sie werden es nicht einmal bemerken.
Testing
Dunkel ist und unveränderliche Werte, sodass die Testoberfläche im Vergleich zu dynamisch typisierten objektorientierten Sprachen viel kleiner ist. Aber Sie müssen noch testen.
In Dark führt der Editor im Hintergrund automatisch Komponententests für den bearbeiteten Code durch und führt diese Tests standardmäßig für alle Funktionsschalter aus. In Zukunft wollen wir Code mithilfe statischer Typen automatisch fuzzen, 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 (vorerst speichern wir alle Anfragen, wollen dann aber zum Abrufen wechseln). Wir testen neuen Code dagegen und führen Unit-Tests durch. Wenn Sie möchten, können Sie interessante Abfragen problemlos in Unit-Tests umwandeln.
Was haben wir losgeworden?
Da wir kein Deployment, aber Funktionswechsel haben, werden etwa 60 % der Deployment-Pipeline weggelassen. Wir benötigen keine Git-Branches oder Pull-Requests, keine Erstellung von Backend-Ressourcen und Containern, keine Übertragung von Ressourcen und Containern an Registrys oder Kubernetes-Bereitstellungsschritte.
Vergleich einer Standard-Continuous-Delivery-Pipeline (links) und einer Dark-Continuous-Delivery-Pipeline (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 hat eine Bereitstellung nur 6 Schritte und 1 Zyklus (Schritte, die sich mehrmals wiederholen), während die moderne Continuous-Delivery-Pipeline 35 Schritte und 3 Zyklen hat. In Dark laufen Tests automatisch ab, ohne dass Sie es überhaupt sehen; Abhängigkeiten werden automatisch installiert; alles, was mit Git oder Github zu tun hat, wird nicht mehr benötigt; Sie müssen keine Docker-Container erstellen, testen und versenden. Eine Bereitstellung auf Kubernetes ist nicht mehr erforderlich.
Sogar die restlichen Schritte in Dark wurden einfacher. Da das Umschalten von Funktionen mit einer einzigen Aktion gesteuert werden kann, ist es nicht erforderlich, die gesamte Bereitstellungspipeline ein zweites Mal zu durchlaufen, um alten Code zu entfernen.
Wir haben die Codebereitstellung so gut wie möglich vereinfacht und so den Zeitaufwand und die Risiken einer kontinuierlichen Bereitstellung reduziert. Wir haben es auch viel einfacher gemacht, Pakete, Datenbankmigrationen, Tests, Versionskontrolle, Abhängigkeitsinstallation, Gleichheit zwischen Entwicklungs- und Produktionsumgebungen sowie schnelle und sichere Sprachversions-Upgrades zu aktualisieren.
Fragen dazu beantworte ich auf .
Um mehr über das Dark-Gerät zu erfahren, lesen Sie , (oder bei ) Oder . Wenn Sie im September zu StrangeLoop gehen, .
Source: habr.com
