Come Dark distribuisce il codice in 50 ms

Come Dark distribuisce il codice in 50 ms

Più veloce è il processo di sviluppo, più velocemente si sviluppa l'azienda tecnologica.

Sfortunatamente, le applicazioni moderne lavorano contro di noi: i nostri sistemi devono aggiornarsi in tempo reale senza disturbare nessuno e senza causare fermi o interruzioni. La distribuzione in questi sistemi diventa un compito complesso e richiede pipeline di consegna continua anche per team piccoli.

Queste pipeline di solito hanno un'applicazione ristretta, lavorano lentamente e non sono affidabili. Gli sviluppatori devono prima crearle manualmente e poi gestirle, e le aziende spesso assumono interi team DevOps per questo.

La velocità di queste pipeline determina la velocità di sviluppo. Nei migliori team la distribuzione richiede 5-10 minuti, ma di solito tutto richiede molto più tempo e una sola distribuzione può richiedere diverse ore.

In Dark ci vogliono 50 ms. Cinquanta. Millisecondi. Dark — è una soluzione complessiva con un linguaggio di programmazione, un editor e un'infrastruttura, progettata specificamente per la consegna continua, e tutti gli aspetti di Dark, incluso il linguaggio stesso, sono costruiti con l'obiettivo di una distribuzione sicura e istantanea.

Perché le pipeline di consegna continua sono così lente?

Supponiamo di avere un'applicazione web Python e di aver già creato una pipeline di consegna continua eccellente e moderna. Per uno sviluppatore che lavora su questo progetto ogni giorno, la distribuzione di una singola modifica insignificante apparirà più o meno così:

Apportare modifiche

  • Creare un nuovo branch in git
  • Apportare modifiche dietro il selettore della funzionalità
  • Test di unità per verificare le modifiche con e senza il selettore della funzionalità

Pull request

  • Commit delle modifiche
  • Invio delle modifiche al repository remoto su github
  • Pull request
  • Il build CI viene eseguito automaticamente in background
  • Revisione del codice
  • Altre revisioni se necessario
  • Unire le modifiche nel master di git.

Il CI viene eseguito sul master

  • Installazione delle dipendenze frontend tramite npm
  • Compilazione e ottimizzazione delle risorse HTML+CSS+JS
  • Esecuzione di test unitari e funzionali nel frontend
  • Installazione delle dipendenze Python da PyPI
  • Esecuzione di test unitari e funzionali nel backend
  • Test di integrazione su entrambi i lati
  • Invio delle risorse frontend nel CDN
  • Assemblaggio del contenitore per il programma Python
  • Invio del contenitore al registro
  • Aggiornamento del manifesto Kubernetes

Sostituzione del vecchio codice con il nuovo

  • Kubernetes avvia più istanze del nuovo contenitore
  • Kubernetes attende che le istanze diventino operative
  • Kubernetes aggiunge le istanze al bilanciatore di carico HTTP
  • Kubernetes attende che le vecchie istanze smettano di essere utilizzate
  • Kubernetes ferma le vecchie istanze
  • Kubernetes ripete queste operazioni finché le nuove istanze non sostituiscono tutte le vecchie

Abilitazione del nuovo switch di funzionalità

  • Il nuovo codice viene abilitato solo per se stesso, per assicurarsi che tutto sia a posto
  • Il nuovo codice viene abilitato per il 10% degli utenti, vengono monitorati metriche operative e di business
  • Il nuovo codice viene abilitato per il 50% degli utenti, vengono monitorati metriche operative e di business
  • Il nuovo codice viene abilitato per il 100% degli utenti, vengono monitorati metriche operative e di business
  • Infine, ripeti l'intera procedura per rimuovere il vecchio codice e lo switch

Il processo dipende dagli strumenti, dal linguaggio e dall'uso di architetture orientate ai servizi, ma in linea generale appare così. Non ho menzionato il deploy con migrazione dei database, perché richiede una pianificazione attenta, ma di seguito parlerò di come Dark gestisce questa situazione.

Ci sono molti componenti, e molti di questi possono facilmente rallentare, guastarsi, causare competizione temporale o far collassare un sistema in funzione.

E poiché questi pipeline sono quasi sempre creati per casi speciali, è difficile fare affidamento su di essi. Molti hanno giorni in cui il codice non può essere distribuito a causa di problemi nel Dockerfile, di un guasto in uno dei decine di servizi o perché il tecnico necessario è in ferie.

Peggio ancora, molti di questi passaggi non fanno nulla di utile. Erano necessari prima, quando distribuivamo il codice direttamente agli utenti, ma ora abbiamo switch per il nuovo codice, e questi processi sono stati separati. Di conseguenza, il passaggio in cui viene distribuito il codice (il vecchio sostituito dal nuovo), è diventato semplicemente un rischio inutile.

Certo, questo è un pipeline molto ben progettato. Il team che l'ha creato non ha lesinato tempo e denaro per una distribuzione rapida. Di solito, i pipeline di distribuzione sono molto più lenti e inaffidabili.

Implementazione della consegna continua in Dark

La fornitura continua è così importante per Dark che sin dall'inizio ci siamo posti l'obiettivo di meno di un secondo. Abbiamo analizzato ogni passaggio del pipeline per eliminare tutto ciò che era superfluo e perfezionare il resto. Ecco come abbiamo rimosso i passaggi.

Jessie Frazelle (Jessie Frazelle) ha coniato una nuova parola, deployless (senza necessità di distribuzione), alla conferenza Future of Software Development a Reykjavik

Abbiamo subito deciso che Dark sarebbe stato basato sul concetto di «deployless» (grazie Jessie Frazelle per il neologismo). Deployless significa che qualsiasi codice viene distribuito istantaneamente ed è pronto per l'uso in produzione. Certo, non ignoreremo codice difettoso o incompleto (i principi di sicurezza li descriverò più avanti).

Durante la dimostrazione di Dark ci hanno spesso chiesto come fossimo riusciti a velocizzare così tanto la distribuzione. Una domanda strana. La gente probabilmente pensa che abbiamo inventato qualche super tecnologia che confronta il codice, lo compila, lo impacchetta in un container, avvia una macchina virtuale, avvia il container a freddo e tutto il resto in 50 ms. Difficilmente è possibile. Ma abbiamo creato un motore di distribuzione speciale, di cui tutto questo non ha bisogno.

Dark esegue gli interpreti nel cloud. Supponiamo che tu scriva codice in una funzione o in un gestore HTTP o eventi. Inviamo il diff in un albero di sintassi astratta (l'implementazione del codice che il nostro editor e i server utilizzano internamente) ai nostri server e poi eseguiamo questo codice quando arrivano le richieste. Quindi, la distribuzione appare semplicemente come una modesta registrazione in un database — istantanea e elementare. La distribuzione avviene così rapidamente perché include il minimo indispensabile.

In futuro, prevediamo di trasformare Dark in un compilatore di infrastruttura che creerà e avvierà un'infrastruttura ideale per alte prestazioni e affidabilità delle applicazioni. La distribuzione istantanea, ovviamente, non andrà da nessuna parte.

Distribuzione sicura

Editor strutturato

Il codice in Dark viene scritto nell'editor di Dark. L'editor strutturato non consente errori di sintassi. Fondamentalmente, in Dark non abbiamo nemmeno un analizzatore. Mentre inserisci testo, lavoriamo direttamente con l'albero di sintassi astratta (AST), come Paredit, Sketch-n-Sketch, Tofu, Prune e MPS.

Qualsiasi codice incompleto in Dark ha una semantica di esecuzione valida, circa come typed holes in Hazel. Ad esempio, se cambi la chiamata di una funzione, manteniamo la vecchia funzione finché la nuova non diventa utilizzabile.

Ogni programma in Dark ha il proprio significato, quindi il codice incompleto non interferisce con quello completato.

Modalità di editing

Scrivi codice in Dark in due casi. Primo: scrivi nuovo codice ed è l'unico utilizzatore. Ad esempio, è in REPL e altri utenti non avranno mai accesso, oppure è un nuovo percorso HTTP a cui non fai riferimento da nessuna parte. Qui puoi lavorare senza alcuna precauzione, e ora stai operando in un ambiente di sviluppo in questo modo.

Seconda situazione: il codice è già in uso. Se il codice riceve traffico (funzioni, gestori di eventi, basi di dati, ecc.), è necessario essere cauti. Per questo bloccamo tutto il codice in uso e richiediamo l'uso di strumenti più strutturati per la sua modifica. Parlerò di strumenti strutturati qui sotto: switch delle funzioni per gestori HTTP ed eventi, una potente piattaforma di migrazione per basi di dati e un nuovo metodo di gestione delle versioni per funzioni e tipi.

Switch delle funzioni

Uno dei modi per ridurre la complessità in Dark è affrontare diversi problemi con una sola soluzione. Gli switch delle funzioni svolgono molteplici compiti: sostituzione dell'ambiente di sviluppo locale, rami git, distribuzione del codice e, naturalmente, il tradizionale rilascio lento e controllato di nuovo codice.

Creazione e distribuzione di uno switch di funzione avviene nel nostro editor in un'unica operazione. Viene creato uno spazio vuoto per il nuovo codice e vengono forniti controlli di accesso al vecchio e al nuovo codice, insieme a pulsanti e comandi per una transizione graduale al nuovo codice o la sua esclusione.

Gli switch delle funzioni sono integrati nel linguaggio Dark, e anche gli switch incompleti svolgono la loro funzione: se la condizione nello switch non viene soddisfatta, verrà eseguito il vecchio codice bloccato.

Ambiente di sviluppo

I selettori di funzionalità sostituiscono l'ambiente di sviluppo locale. Oggi i team faticano a garantire che tutti utilizzino le stesse versioni degli strumenti e delle librerie (strumenti di formattazione del codice, linter, gestori di pacchetti, compilatori, preprocessori, strumenti di testing, ecc.). Con Dark non è necessario installare dipendenze localmente, gestire installazioni locali di Docker o prendere altre misure per garantire almeno una parvenza di uguaglianza tra l'ambiente di sviluppo e il prodotto finale. Dato che tale uguaglianza è comunque impossibile, non ci faremo nemmeno illudere che ci stiamo provando.

Invece di creare un ambiente locale clonato, i selettori in Dark creano una nuova sandbox nel prodotto finale, che sostituisce l'ambiente di sviluppo. In futuro prevediamo anche di creare una sandbox per altre parti dell'applicazione (come i cloni istantanei del database), anche se al momento non sembra così importante.

Rami e distribuzioni

Attualmente ci sono diversi modi per immettere nuovo codice nei sistemi: rami git, fasi di distribuzione e selettori di funzionalità. Risolvono un problema in diverse parti del flusso di lavoro: git - nelle fasi prima della distribuzione, la distribuzione - nel momento del passaggio dal vecchio codice al nuovo, e i selettori di funzione - per rilasciare nuovo codice in modo controllato.

Il modo più efficace sono i selettori di funzionalità (che sono anche i più facili da comprendere e utilizzare). Con essi è possibile rinunciare completamente agli altri due metodi. È particolarmente utile eliminare la distribuzione: se stiamo già utilizzando selettori di funzionalità per attivare il codice, il passaggio dei server al nuovo codice comporta solo rischi aggiuntivi.

Git è difficile da usare, soprattutto per i principianti, e questo lo limita molto, ma ha il vantaggio dei rami. Abbiamo mitigato molti dei difetti di git. Dark consente di modificare in tempo reale e offre la possibilità di collaborazione nello stile di Google Documenti, così non è necessario inviare codice e si può fare meno spesso il rebase e il merge.

Gli switch delle funzionalità sono alla base di un dispiegamento sicuro. Insieme ai dispiegamenti instantanei, consentono di testare rapidamente concetti in piccoli frammenti a basso rischio invece di applicare un unico grande cambiamento che potrebbe far crollare il sistema.

Versionamento

Per modificare funzionalità e tipi utilizziamo il versioning. Se desideri modificare una funzione, Dark crea una nuova versione di quella funzione. Poi puoi chiamare questa versione tramite lo switch nel gestore HTTP o degli eventi. (Se questa funzione è profondamente annidata nel grafo delle chiamate, si crea una nuova versione per ogni funzione. Potrebbe sembrare eccessivo, ma le funzioni non si sovrappongono se non le utilizzi, quindi non te ne accorgerai nemmeno.)

Per le stesse ragioni, versioniamo anche i tipi. Abbiamo parlato approfonditamente del nostro sistema di tipi nel post precedente..

Grazie al versioning di funzioni e tipi è possibile apportare modifiche all'applicazione in modo graduale. Puoi verificare che ogni singolo gestore funzioni con la nuova versione, senza dover apportare immediatamente tutte le modifiche all'applicazione (ma abbiamo strumenti per farlo rapidamente se lo desideri).

È molto più sicuro rispetto a un dispiegamento completo di tutto in una volta, come avviene attualmente.

Nuove versioni dei pacchetti e della libreria standard

Quando aggiorni un pacchetto in Dark, non sostituiamo immediatamente l'uso di ogni funzione o tipo nell'intera base di codice. Questo non è sicuro. Il codice continua a utilizzare la stessa versione precedentemente impiegata, mentre aggiorni l'uso di funzioni e tipi alla nuova versione per ogni singolo caso tramite switch.

Come Dark distribuisce il codice in 50 ms
Screenshot di una parte del processo automatico in Dark, che mostra due versioni della funzione Dict::get. Dict::get_v0 restituiva il tipo Any (che stiamo abbandonando), mentre Dict::get_v1 restituisce il tipo Option.

Spesso forniamo una nuova funzione nella libreria standard escludendo le versioni obsolete. Gli utenti con versioni più vecchie nel codice manterranno accesso a esse, ma i nuovi utenti non potranno ottenerle. Prevediamo di fornire strumenti per facilitare il passaggio degli utenti dalle versioni obsolete a quelle nuove in un singolo passaggio, ancora una volta usando gli switch delle funzionalità.

Dark offre anche un'opportunità unica: poiché eseguiamo il tuo codice di lavoro, possiamo testare le nuove versioni confrontando i risultati per le nuove e le vecchie richieste, così da informarti sulle modifiche. Di conseguenza, l'aggiornamento dei pacchetti, che spesso avviene alla cieca (o richiede un test accurato per motivi di sicurezza), comporta molto meno rischio e può avvenire automaticamente.

Nuove versioni di Dark

Il passaggio da Python 2 a Python 3 si è prolungato per un decennio e rappresenta ancora un problema. Poiché stiamo creando Dark per una fornitura continua, dobbiamo tenere conto di queste modifiche nel linguaggio.

Quando apportiamo piccole modifiche al linguaggio, creiamo una nuova versione di Dark. Il vecchio codice rimane nella vecchia versione di Dark, mentre il nuovo codice viene utilizzato nella nuova versione. Per passare alla nuova versione di Dark si possono utilizzare switch o versioni di funzionalità.

Questo è particolarmente utile, considerando che Dark è stato introdotto di recente. Molte modifiche nel linguaggio o nella libreria potrebbero rivelarsi problematiche. La versioning graduale del linguaggio ci consente di apportare aggiornamenti minori, il che significa che possiamo prendere tempo e differire molte decisioni sul linguaggio finché non avremo più utenti e, di conseguenza, più informazioni.

Migrazioni del database

Per una migrazione sicura del database esiste una formula standard:

  • Riscrivere il codice per supportare i nuovi e i vecchi formati
  • Convertire tutti i dati nel nuovo formato
  • Rimuovere l'accesso ai dati obsoleti

Di conseguenza, la migrazione del database si protrae e richiede molte risorse. E accumuliamo schemi obsoleti, poiché anche le operazioni semplici, come correggere il nome di una tabella o di una colonna, non valgono lo sforzo.

Dark ha una piattaforma di migrazione del database efficace, che (ci auguriamo) semplificherà tanto il processo che smetterai di averne paura. Tutti i data store in Dark (store di coppie 'chi-valore' o tabelle hash persistenti) hanno un tipo. Per trasferire un data store, semplicemente gli assegni un nuovo tipo e una funzione di rollback e ripristino per convertire i valori tra i due tipi.

L'accesso ai magazzini di dati in Dark avviene tramite nomi di variabili versionati. Ad esempio, il magazzino dati Users sarà inizialmente denominato Users-v0. Quando viene creata una nuova versione con un tipo diverso, il nome cambia in Users-v1. Se i dati sono stati salvati tramite Users-v0 e si accede ad essi tramite Users-v1, viene applicata la funzione di retroazione. Se i dati sono stati salvati tramite Users-v1 e si accede ad essi tramite Users-v0, viene applicata la funzione di ripristino.

Come Dark distribuisce il codice in 50 ms
Schermata di migrazione del database con i nomi dei campi del vecchio database, espressioni di retroazione e ripristino, e istruzioni per abilitare la migrazione.

Utilizza i switch di funzionalità per reindirizzare le chiamate da Users-v0 alla versione Users-v1. Questo può essere fatto un gestore HTTP alla volta, per ridurre i rischi, e inoltre i switch funzionano per singoli utenti, in modo da poter verificare che tutto funzioni come previsto. Quando non ci saranno più utenti di Users-v0, Dark convertirà in background tutti i dati rimanenti dal vecchio formato al nuovo. Non te ne accorgerai nemmeno.

Test

Dark è un linguaggio di programmazione funzionale con tipizzazione statica e valori immutabili, quindi la superficie di test è significativamente inferiore rispetto ai linguaggi orientati agli oggetti con tipizzazione dinamica. Ma bisogna comunque testare.
In Dark, l'editor esegue automaticamente i test modulari in background per il codice modificabile e di default esegue questi test per tutti i switch di funzionalità. In futuro, vogliamo utilizzare tipi statici per eseguire automaticamente il fuzzing del codice per trovare bug.

Inoltre, Dark gestisce la tua infrastruttura in produzione, il che apre nuove possibilità. Salviamo automaticamente le richieste HTTP nell'infrastruttura Dark (per ora salviamo tutte le richieste, ma vogliamo passare a una selezione). Testiamo il nuovo codice su di esse e eseguiamo test modulari, e se lo desideri puoi facilmente convertire richieste interessanti in test modulari.

Di cosa ci siamo liberati

Poiché non abbiamo un deployment, ma abbiamo switch di funzionalità, circa il 60% della pipeline di deployment rimane fuori. Non abbiamo bisogno di rami git o richieste di pull, costruzione di risorse backend e container, invio di risorse e container ai registri o passaggi di deployment in Kubernetes.

Come Dark distribuisce il codice in 50 ms
Confronto tra un pipeline standard di consegna continua (a sinistra) e la consegna continua Dark (a destra). In Dark, la consegna consiste di 6 passaggi e un ciclo, mentre la versione tradizionale include 35 passaggi e 3 cicli.

In Dark ci sono solo 6 passaggi e 1 ciclo (passaggi che si ripetono più volte) nel deployment, mentre un moderno pipeline di consegna continua è composto da 35 passaggi e 3 cicli. In Dark i test vengono eseguiti automaticamente, e voi non lo vedete nemmeno; le dipendenze vengono installate automaticamente; tutto ciò che riguarda git o Github non è più necessario; non è necessario costruire, testare e inviare contenitori Docker; il deployment su Kubernetes non è più richiesto.

Anche i passaggi rimanenti in Dark sono diventati più semplici. Poiché i controlli delle funzionalità possono essere gestiti con un'unica azione, non è necessario ripetere l'intero pipeline di deployment per rimuovere il codice obsoleto.

Abbiamo semplificato il più possibile la consegna del codice, riducendo tempi e rischi legati alla consegna continua. Inoltre, abbiamo notevolmente semplificato l'aggiornamento dei pacchetti, le migrazioni di database, i test, la gestione delle versioni, l'installazione delle dipendenze, l'equivalenza tra ambiente di sviluppo e produzione e gli aggiornamenti rapidi e sicuri delle versioni del linguaggio.

Rispondo a domande al riguardo su HackerNews.

Per saperne di più sul dispositivo Dark, leggete l'articolo su Dark, seguiteci su Twitter (o su di me) oppure iscrivetevi per la versione beta e riceverete avvisi sui prossimi post. Se andate a StrangeLoop a settembre, venite a trovarci al lancio.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster