
Jo raskere utviklingsprosessen er, jo raskere utvikler teknologi-selskapet seg.
Dessverre jobber moderne applikasjoner mot oss — systemene våre må oppdateres i sanntid uten å forstyrre noen eller forårsake nedetid. Distribusjon i slike systemer blir en komplisert oppgave og krever komplekse pipeliner for kontinuerlig levering selv i små team.
Disse pipeline-ene har vanligvis smal anvendelse, fungerer sakte og er ikke pålitelige. Utviklere må først opprette dem manuelt, og deretter administrere dem; ofte ansetter selskapene hele DevOps-team for dette.
Hastigheten til disse pipeline-ene avgjør hastigheten på utviklingen. For de beste teamene tar distribusjon 5–10 minutter, men vanligvis tar det mye lenger tid, og for en enkelt distribusjon kreves det flere timer.
I Dark tar dette 50 ms. Femtio. Millisekund.. Dark — , skapt spesielt for kontinuerlig levering, og alle aspekter av Dark, inkludert selve språket, er bygget med tanke på sikker, øyeblikkelig distribusjon.
Hvorfor er pipeliner for kontinuerlig levering så trege?
La oss si at vi har en Python webapplikasjon og vi allerede har laget en fantastisk og moderne pipeline for kontinuerlig levering. For utvikleren som jobber med dette prosjektet hver dag, vil distribusjon av en liten endring se omtrent slik ut:
Gjøre endringer
- Opprette en ny gren i git
- Gjøre endringer bak funksjonsbryteren
- Modulært testing for å sjekke endringer med og uten funksjonsbryteren
Pull-forespørsel
- Committe endringer
- Push endringer til den eksterne lagringsplassen på github
- Pull-forespørsel
- CI bygget utføres automatisk i bakgrunnen
- Kodegjennomgang
- Noen flere vurderinger, om nødvendig
- Slå sammen endringer med master i git.
CI kjører på master
- Installere frontend-avhengigheter via npm
- Bygge og optimalisere HTML+CSS+JS ressurser
- Kjøre modul- og funksjonstester på frontend
- Installere Python-avhengigheter fra PyPI
- Kjøre modul- og funksjonstester på backend
- Integrasjonstesting på begge ender
- Push frontend-ressurser til CDN
- Bygge container for Python-programmet
- Sending the container to the registry
- Updating the Kubernetes manifest
Replacing old code with new
- Kubernetes launches multiple instances of the new container
- Kubernetes waits for the instances to become operational
- Kubernetes adds instances to the HTTP load balancer
- Kubernetes waits for the old instances to stop being used
- Kubernetes stops the old instances
- Kubernetes repeats these operations until the new instances replace all old ones
Enabling the new feature switch
- The new code is enabled only for itself to ensure everything is fine
- The new code is enabled for 10% of users, operational and business metrics are tracked
- The new code is enabled for 50% of users, operational and business metrics are tracked
- The new code is enabled for 100% of users, operational and business metrics are tracked
- Finally, you repeat the entire procedure to remove the old code and switch
The process depends on the tools, language, and service-oriented architecture used, but generally looks like this. I didn't mention deployments with database migrations because they require careful planning, but below I will explain how Dark handles that.
There are many components here, and many of them can easily throttle, crash, cause temporary contention, or bring down the working system.
And since these pipelines are almost always created for a specific case, they are hard to rely on. Many have days when the code cannot be deployed because there are issues in the Dockerfile, one of dozens of services has crashed, or the necessary specialist is on vacation.
Worse yet, many of these steps don't do anything useful at all. They were needed before when we deployed code directly for users, but now we have switches for new code, and these processes have split. As a result, the step where the code is deployed (old is replaced with new) has now become just an unnecessary risk.
Of course, this is a very thoughtful pipeline. The team that created it did not spare time and money for quick deployment. Typically, deployment pipelines are much slower and less reliable.
Implementing continuous delivery in Dark
Den kontinuerlige leveransen er så viktig for Dark at vi fra starten av hadde som mål å bruke mindre enn ett sekund. Vi har gått gjennom alle trinnene i pipeline for å eliminere alt unødvendig, og perfeksjonert det som gjensto. Slik har vi fjernet trinnene.
Jessie Frazelle () fant opp det nye ordet deployless (uten behov for distribusjon) under konferansen Future of Software Development i Reykjavik.
Vi bestemte oss umiddelbart for at Dark skulle være basert på konseptet "deployless" (takk til for neologismen). Deployless betyr at enhver kode distribueres umiddelbart og er klar for produksjon. Selvfølgelig vil vi ikke slippe gjennom feil eller ufullstendig kode (jeg vil beskrive sikkerhetsprinsippene nedenfor).
Under demonstrasjonen av Dark ble vi ofte spurt hvordan vi klarte å akselerere utrullingen så mye. Merkelig spørsmål. Folk tror kanskje vi har utviklet noen superteknologi som sammenligner kode, kompilere den, pakker den inn i en container, kjører en virtuell maskin, starter containeren kaldt og alt det der – alt dette på 50 ms. Det er det lite sannsynlig at vi kan. Men vi har laget en spesialutviklet distribusjonsmotor som ikke har bruk for det.
Dark kjører tolker i skyen. La oss si at du skriver kode i en funksjon eller en HTTP- eller hendelsesbehandler. Vi sender diffs til et abstrakt syntakstrær (implementeringen av koden som internt brukes av vår editor og servere) til våre servere, og så kjører vi denne koden når forespørslene kommer inn. Så distribusjonen ser bare ut som en beskjeden oppføring i en database – øyeblikkelig og elementær. Distribusjonen skjer så raskt fordi den inkluderer det aller minste.
I fremtiden planlegger vi å gjøre av Dark til en infrastrukturkompilator som vil bygge og kjøre den ideelle infrastrukturen for høy ytelse og pålitelighet av applikasjoner. Øyeblikkelig distribusjon, selvfølgelig, vil ikke gå bort.
Sikker distribusjon
Strukturert editor
Koden i Dark skrives i Dark-editoren. Den strukturerte editoren tillater ikke syntaksfeil. I bunn og grunn har Dark ikke engang en parser. Mens du taster inn tekst, jobber vi direkte med det abstrakte syntakstrær (AST), som , , , og .
Hver ufullstendig kode i Dark har en gyldig semantikk for utførelse, omtrent som . For example, if you change a function call, we keep the old function until the new one becomes suitable.
Every program in Dark has its own purpose, so incomplete code does not interfere with completed code.
Editing Modes
You write code in Dark in two situations. First: you write new code and are the only user. For example, it is in REPL, and other users will never have access to it, or it is a new HTTP route that you do not reference anywhere. Here, you can work without any precautions, and right now you are doing just that in the development environment.
The second situation: the code is already in use. If traffic is passing through the code (functions, event handlers, databases, etc.), caution is required. For this, we block all used code and require more structured tools for editing it. I will explain about structured tools below: function switches for HTTP and event handlers, a powerful migration platform for databases, and a new version control method for functions and types.
Function Switches
One way in Dark is to solve multiple problems with one solution. Function switches perform many different tasks: replacing local development environments, git branches, code deployment, and of course, the traditional slow and controlled release of new code.
Creating and deploying a function switch is done in our editor in one operation. It creates an empty space for new code and provides access controls to old and new code, as well as buttons and commands for gradually transitioning to the new code or excluding it.
Function switches are integrated into the Dark language, and even incomplete switches perform their task — if the condition in the switch is not met, the old blocked code will execute.
Development Environment
Funksjonsbrytere erstatter det lokale utviklingsmiljøet. I dag er det vanskelig for team å sørge for at alle bruker de samme versjonene av verktøy og biblioteker (som kodeformateringsverktøy, linters, pakkebehandlere, kompilatorer, preprosessorer, testverktøy, osv.). Med Dark trenger man ikke å installere avhengigheter lokalt, håndtere lokal Docker-installasjon eller ta andre tiltak for å sikre en viss likhet mellom utviklingsmiljøet og produksjonen. , vil vi ikke engang late som vi streber etter det.
I stedet for å skape et klonet lokalt miljø, skaper bryterne i Dark en ny sandkasse i produksjonen som erstatter utviklingsmiljøet. I fremtiden planlegger vi også å lage sandkasser for andre deler av applikasjonen (for eksempel umiddelbare kloner av databasen), selv om det for nå ikke virker så viktig.
Grener og distribusjoner
Det finnes for øyeblikket flere måter å legge inn ny kode i systemene: git-grener, distribusjonsfaser og funksjonsbrytere. De løser ett problem i forskjellige deler av arbeidsflyten: git – på trinn før distribusjonen, distribusjon – i overgangen fra gammel kode til ny, og funksjonsbrytere – for kontrollert utgivelse av ny kode.
Den mest effektive metoden er funksjonsbrytere (som også er den enkleste å forstå og bruke). Med dem kan man helt unngå de to andre metodene. Det er spesielt nyttig å fjerne distribusjon – dersom vi uansett bruker funksjonsbrytere for å aktivere koden, vil trinnet med å oppdatere serverne til ny kode bare skape unødvendige risikoer.
Git kan være vanskelig å bruke, spesielt for nybegynnere, og det begrenser bruken kraftig, men det har praktiske grener. Vi har jevnet ut mange av manglene med git. Dark redigeres i sanntid og gir mulighet for samarbeid i stil med Google Dokumenter, så man slipper å sende kode og kan gjøre rebasing og sammenslåing sjeldnere.
Funksjonsbryterne er grunnlaget for sikker distribusjon. Sammen med øyeblikkelig distribusjon gjør de det mulig å teste konsepter raskt i små biter med lav risiko i stedet for å implementere en stor endring som kan krasje systemet.
Versjonering
For å endre funksjoner og typer bruker vi versjonering. Hvis du ønsker å endre en funksjon, oppretter Dark en ny versjon av denne funksjonen. Deretter kan du kalle denne versjonen med en bryter i HTTP-håndtereren eller hendelser. (Hvis det er en funksjon dypt i anropsgrafen, opprettes det automatisk en ny versjon av hver funksjon underveis. Det kan virke overdrevet, men funksjoner blandes ikke hvis de ikke brukes, så du legger ikke merke til det engang.)
Av de samme grunnene versjonerer vi typer. Vi har gitt en detaljert gjennomgang av typen vårt. .
Takket være versjonering av funksjoner og typer kan du gjøre endringer i applikasjonen gradvis. Du kan sjekke at hver enkelt håndterer fungerer med den nye versjonen, uten å måtte gjøre alle endringene i applikasjonene samtidig (men vi har verktøy for å gjøre det raskt hvis du vil).
Dette er mye tryggere enn å distribuere alt samtidig, slik det gjøres nå.
Nye versjoner av pakker og standardbiblioteket
Når du oppdaterer en pakke i Dark, erstatter vi ikke umiddelbart bruken av hver funksjon eller type i hele kodebasen. Det er usikkert. Koden fortsetter å bruke den samme versjonen som den brukte, mens du oppdaterer bruken av funksjoner og typer til den nye versjonen for hvert enkelt tilfelle med bryterne.
Skjermbilde av en del av den automatiske prosessen i Dark, som viser to versjoner av funksjonen Dict::get. Dict::get_v0 returnerte typen Any (som vi går bort fra), mens Dict::get_v1 returnerer typen Option.
Vi tilbyr ofte en ny funksjon i standardbiblioteket og ekskluderer gamle versjoner. Brukere med eldre versjoner i koden vil fortsatt ha tilgang til dem, men nye brukere vil ikke kunne få dem. Vi planlegger å tilby verktøy for å hjelpe brukere fra gamle versjoner til nye i ett trinn, også med funksjonsbrytere.
Dark gir deg også en unik mulighet: siden vi kjører koden din, kan vi selv teste nye versjoner ved å sammenligne utdataene for nye og gamle forespørselene for å informere deg om endringene. Som et resultat medfører oppdateringer av pakker, som ofte gjøres i blinde (eller krever grundig testing av sikkerhetshensyn), langt færre risikoer og kan skje automatisk.
Nye versjoner av Dark
Overgangen fra Python 2 til Python 3 strakte seg over et tiår og er fortsatt et problem. Siden vi utvikler Dark for kontinuerlig leveranse, må vi ta hensyn til disse språkendringene.
Når vi gjør små endringer i språket, lager vi en ny versjon av Dark. Gammel kode forblir i den gamle versjonen av Dark, mens ny kode brukes i den nye versjonen. For å gå over til den nye versjonen av Dark kan man bruke brytere eller funksjonsversjoner.
Dette er spesielt nyttig, gitt at Dark er relativt nytt. Mange endringer i språket eller biblioteket kan vise seg å være mislykkede. Gradvis versjonering av språket lar oss gjøre mindre oppdateringer, noe som betyr at vi kan ta oss tid og utsette mange beslutninger om språket til vi har flere brukere, som igjen gir oss mer informasjon.
Databasemigrasjoner
For sikker migrering av databaser finnes det :
- Skrive om kode for å støtte både nye og gamle formater
- Konvertere alle data til det nye formatet
- Fjerne gammel tilgang til data
Som et resultat tar databasemigreringen tid og krever mye ressurser. Og vi samler opp utdaterte skjemaer, siden selv enkle oppgaver, som å endre navnet på en tabell eller kolonne, ikke er verdt innsatsen.
Dark har en effektiv plattform for databasemigrasjon som (vi håper) vil forenkle prosessen så mye at du slutter å være redd for den. Alle datalagre i Dark (lagre av "nøkkel-verdi" eller permanente hash-tabeller) har en type. For å overføre et datalager tilordner du det ganske enkelt en ny type og en tilbakestillings- og rullefunksjon for å konvertere verdiene mellom de to typene.
Tilgang til datalagring i Dark skjer via versjonerte variabelnavn. For eksempel vil datalageret Users opprinnelig bli kalt Users-v0. Når en ny versjon med en annen type opprettes, endres navnet til Users-v1. Hvis data er lagret via Users-v0, men du henvender deg til dem via Users-v1, brukes rollback-funksjonen. Hvis data er lagret via Users-v1, men du henvender deg til dem via Users-v0, brukes rollback-funksjonen.
Skjerm for migrering av databasene med navnene på feltene fra den gamle databasen, uttrykk for rollback og instruksjoner for å aktivere migreringen.
Bruk funksjonsbrytere for å dirigere kall fra Users-v0 til versjonen Users-v1. Dette kan gjøres en HTTP-behandler om gangen for å redusere risikoene, og bryterne fungerer også for enkeltbrukere, slik at du kan sjekke at alt fungerer som forventet. Når det ikke er flere brukere av Users-v0, konverterer Dark alle gjenværende data i bakgrunnen fra det gamle formatet til det nye. Du vil knapt merke det.
Testing
Dark er en og uforanderlige verdier, så testoverflaten er betydelig mindre sammenlignet med objektorienterte språk med dynamisk typekontroll. Men testing er fortsatt nødvendig.
I Dark kjører redaktøren automatisk modulære tester i bakgrunnen for den redigerte koden og kjører som standard disse testene for alle funksjonsbrytere. I fremtiden ønsker vi å utføre fuzzing av koden automatisk ved hjelp av statiske typer for å finne feil.
I tillegg kjører Dark infrastrukturen din i produksjon, noe som åpner for nye muligheter. Vi lagrer automatisk HTTP-forespørslene i Dark-infrastrukturen (for nå lagrer vi alle forespørslene, men senere ønsker vi å gå over til sampling). Vi tester ny kode basert på disse forespørslene og kjører modulære tester, og hvis ønskelig kan du enkelt konvertere interessante forespørslene til modulære tester.
Hva vi har kvittet oss med
Siden vi ikke har distribusjon, men funksjonsbrytere, er cirka 60% av distribusjonsflyten ute av spill. Vi trenger ikke git-grener eller pull-forespørsel, bygging av backend-ressurser og containere, sending av ressurser og containere til registre eller distribusjonstrinn i Kubernetes.
Samanlik av standard kontinuerlig leveranse (til venstre) og kontinuerlig leveranse Dark (til høyre). I Dark består leveransen av 6 trinn og 1 syklus, mens den tradisjonelle versjonen inneholder 35 trinn og 3 sykluser.
I Dark er det kun 6 trinn og 1 syklus (trinn som gjentas flere ganger) under utrullingen, mens den moderne kontinuerlige leveransepipen består av 35 trinn og 3 sykluser. I Dark kjøres tester automatisk, og du ser dem ikke engang; avhengigheter installeres automatisk; alt relatert til git eller Github er ikke lenger nødvendig; bygge, teste og sende Docker-containere er ikke nødvendig; utrullingen til Kubernetes er ikke lenger nødvendig.
Selv de gjenværende trinnene i Dark har blitt enklere. Siden funksjonsbrytere kan styres med ett enkelt tiltak, er det ikke nødvendig å gå gjennom hele utrullingspipen på nytt for å fjerne gammel kode.
Vi har forenklet kodenleveransen så mye vi kunne, redusert tid og risiko i kontinuerlig leveranse. Vi har også betydelig forenklet oppdatering av pakker, databasemigreringer, testing, versjonsstyring, installasjon av avhengigheter, likhet mellom utviklings- og produksjonsmiljø og raske, sikre oppgraderinger av språkversjoner.
Jeg svarer på spørsmål om dette på .
For å lære mer om Dark-enheten, les , (eller på ) eller . Hvis du skal til StrangeLoop i september, .
Kilde: habr.com
