werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

27 mei in de grote zaal van de DevOpsConf 2019-conferentie, gehouden als onderdeel van het festival RIT++ 2019, als onderdeel van de sectie “Continuous Delivery” werd een rapport gegeven “werf - onze tool voor CI/CD in Kubernetes”. Daar gaat het over problemen en uitdagingen waarmee iedereen te maken krijgt bij de implementatie in Kubernetes, maar ook over nuances die misschien niet meteen opvallen. Door mogelijke oplossingen te analyseren, laten we zien hoe dit wordt geïmplementeerd in een Open Source-tool werf.

Sinds de presentatie heeft ons nutsbedrijf (voorheen bekend als dapp) een historische mijlpaal bereikt van 1000 sterren op GitHub — we hopen dat de groeiende gemeenschap van gebruikers het leven van veel DevOps-ingenieurs gemakkelijker zal maken.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Dus, we presenteren video van het rapport (~47 minuten, veel informatiever dan het artikel) en het belangrijkste fragment daaruit in tekstvorm. Gaan!

Code aanleveren aan Kubernetes

Het gesprek gaat niet langer over werf, maar over CI/CD in Kubernetes, wat impliceert dat onze software is verpakt in Docker-containers (Ik heb hierover gesproken in Rapport 2016), en K8's zullen worden gebruikt om het in productie te nemen (meer hierover in 2017 jaar).

Hoe ziet de levering eruit in Kubernetes?

  • Er is een Git-repository met de code en instructies om deze te bouwen. De applicatie is ingebouwd in een Docker-image en gepubliceerd in de Docker Registry.
  • Dezelfde repository bevat ook instructies voor het implementeren en uitvoeren van de applicatie. In de implementatiefase worden deze instructies naar Kubernetes gestuurd, die de gewenste image uit het register ontvangt en start.
  • Bovendien zijn er meestal tests. Sommige hiervan kunnen worden gedaan bij het publiceren van een afbeelding. U kunt ook (volgens dezelfde instructies) een kopie van de applicatie implementeren (in een aparte K8s-naamruimte of een apart cluster) en daar tests uitvoeren.
  • Ten slotte heb je een CI-systeem nodig dat gebeurtenissen van Git ontvangt (of klikken op knoppen) en alle aangewezen fasen aanroept: bouwen, publiceren, implementeren, testen.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Er zijn hier een paar belangrijke opmerkingen:

  1. Omdat we een onveranderlijke infrastructuur hebben (onveranderlijke infrastructuur), de applicatie-image die in alle fasen wordt gebruikt (staging, productie, enz.), er moet er een zijn. Ik heb hier uitgebreider en met voorbeelden over gesproken. hier.
  2. Omdat we de infrastructuur als codebenadering volgen (IaC), de applicatiecode, instructies voor het samenstellen en starten ervan zouden moeten zijn precies in één opslagplaats. Voor meer informatie hierover, zie hetzelfde rapport.
  3. Leveringsketen (levering) we zien het meestal zo: de applicatie is samengesteld, getest, vrijgegeven (releasefase) en dat is alles: de levering heeft plaatsgevonden. Maar in werkelijkheid krijgt de gebruiker wat je hebt uitgerold, geen toen je het aan de productie afleverde, en toen hij daarheen kon gaan en deze productie werkte. Dus ik geloof dat de leveringsketen eindigt alleen in de operationele fase (rennen), of preciezer gezegd, zelfs op het moment dat de code uit productie werd gehaald (vervangen door een nieuwe).

Laten we terugkeren naar het bovenstaande leveringsschema in Kubernetes: het is niet alleen door ons uitgevonden, maar ook door letterlijk iedereen die met dit probleem te maken heeft gehad. In feite heet dit patroon nu GitOps (je kunt meer lezen over de term en de ideeën erachter hier). Laten we eens kijken naar de fasen van het schema.

Bouw podium

Het lijkt erop dat je kunt praten over het bouwen van Docker-images in 2019, wanneer iedereen weet hoe Dockerfiles moeten worden geschreven en uitgevoerd docker build?.. Hier zijn de nuances waar ik op zou willen letten:

  1. Gewicht afbeelding is belangrijk, dus gebruik meerstapsom alleen de applicatie in de afbeelding achter te laten die echt nodig is voor de bewerking.
  2. Aantal lagen moeten worden geminimaliseerd door het combineren van ketens van RUN-opdrachten volgens betekenis.
  3. Dit voegt echter problemen toe debuggen, omdat wanneer de assembly crasht, je het juiste commando moet vinden in de keten die het probleem veroorzaakte.
  4. Montage snelheid belangrijk omdat we veranderingen snel willen doorvoeren en resultaat willen zien. U wilt bijvoorbeeld niet elke keer dat u een toepassing bouwt, de afhankelijkheden in taalbibliotheken opnieuw opbouwen.
  5. Vaak vanuit één Git-repository die je nodig hebt veel afbeeldingen, wat kan worden opgelost door een set Dockerfiles (of benoemde fasen in één bestand) en een Bash-script met hun sequentiële montage.

Dit was nog maar het topje van de ijsberg waar iedereen mee te maken heeft. Maar er zijn nog meer problemen, met name:

  1. Vaak hebben we in de montagefase iets nodig monteren (Cache bijvoorbeeld het resultaat van een opdracht zoals apt in een map van derden).
  2. Wij willen Ansible in plaats van in shell te schrijven.
  3. Wij willen bouwen zonder Docker (waarom hebben we een extra virtuele machine nodig waarin we alles hiervoor moeten configureren, terwijl we al een Kubernetes-cluster hebben waarin we containers kunnen draaien?).
  4. Parallelle montage, wat op verschillende manieren kan worden begrepen: verschillende opdrachten uit het Dockerfile (als multi-stage wordt gebruikt), verschillende commits van dezelfde repository, verschillende Dockerfiles.
  5. Gedistribueerde montage: We willen dingen verzamelen in peulen die ‘efemere’ zijn omdat hun cache verdwijnt, wat betekent dat deze ergens apart moet worden opgeslagen.
  6. Ten slotte noemde ik het toppunt van verlangens automagisch: Het zou ideaal zijn om naar de repository te gaan, een commando te typen en een kant-en-klare afbeelding te krijgen, samengesteld met inzicht in hoe en wat correct te doen. Persoonlijk ben ik er echter niet zeker van dat alle nuances op deze manier kunnen worden voorzien.

En dit zijn de projecten:

  • moby/bouwkit — een bouwer van Docker Inc (al geïntegreerd in de huidige versies van Docker), die al deze problemen probeert op te lossen;
  • Kaniko — een builder van Google waarmee je zonder Docker kunt bouwen;
  • Buildpacks.io — de poging van CNCF om automatische magie te maken en, in het bijzonder, een interessante oplossing met rebase voor lagen;
  • en een heleboel andere hulpprogramma's, zoals bouwen, authentictools/img...

...en kijk hoeveel sterren ze hebben op GitHub. Dat wil zeggen, enerzijds docker build bestaat en kan iets doen, maar in werkelijkheid het probleem is niet volledig opgelost - Een bewijs hiervan is de parallelle ontwikkeling van alternatieve verzamelaars, die elk een deel van de problemen oplossen.

Montage in werf

Dus we moeten werf (voorheen bekend zoals dapp) — Een open source-hulpprogramma van het bedrijf Flant, dat we al vele jaren maken. Het begon allemaal 5 jaar geleden met Bash-scripts die de assemblage van Dockerfiles optimaliseerden, en de afgelopen 3 jaar is er een volwaardige ontwikkeling uitgevoerd binnen het raamwerk van één project met een eigen Git-repository (eerst in Ruby, en dan herschreven to Go, en tegelijkertijd hernoemd). Welke montagevraagstukken worden op de werf opgelost?

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

De blauw gearceerde problemen zijn al geïmplementeerd, de parallelle bouw is binnen dezelfde host uitgevoerd en de geel gemarkeerde problemen zullen naar verwachting tegen het einde van de zomer zijn voltooid.

Fase van publicatie in het register (publiceren)

Wij hebben gebeld docker push... - wat kan er moeilijk zijn aan het uploaden van een afbeelding naar het register? En dan rijst de vraag: “Welke tag moet ik op de afbeelding zetten?” Het ontstaat om de reden die wij hebben Gitstroom (of een andere Git-strategie) en Kubernetes, en de industrie probeert ervoor te zorgen dat wat er in Kubernetes gebeurt, volgt wat er in Git gebeurt. Git is tenslotte onze enige bron van waarheid.

Wat is hier zo moeilijk aan? Zorg voor reproduceerbaarheid: van een commit in Git, die onveranderlijk van aard is (onveranderlijk), naar een Docker-installatiekopie, die hetzelfde moet blijven.

Het is ook belangrijk voor ons herkomst bepalen, omdat we willen begrijpen uit welke commit de applicatie die in Kubernetes draait, is gebouwd (dan kunnen we diffs en soortgelijke dingen doen).

Tagstrategieën

De eerste is eenvoudig git-tag. We hebben een register met een afbeelding getagd als 1.0. Kubernetes heeft podium en productie, waar deze afbeelding wordt geüpload. In Git maken we commits en op een gegeven moment taggen we 2.0. We verzamelen het volgens de instructies uit de repository en plaatsen het in het register met de tag 2.0. We rollen het uit naar het podium en, als alles goed gaat, naar de productie.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Het probleem met deze aanpak is dat we eerst de tag hebben geplaatst en deze pas daarna hebben getest en uitgerold. Waarom? Ten eerste is het simpelweg onlogisch: we brengen een softwareversie uit die we nog niet eens hebben getest (we kunnen niet anders, want om dit te controleren moeten we een tag plaatsen). Ten tweede is dit pad niet compatibel met Gitflow.

De tweede optie - git commit + tag. De masterbranch heeft een tag 1.0; ervoor in het register: een afbeelding die in productie is genomen. Daarnaast beschikt het Kubernetes-cluster over preview- en staging-contouren. Vervolgens volgen we Gitflow: in de hoofdtak voor ontwikkeling (develop) maken we nieuwe functies, resulterend in een commit met de identifier #c1. We verzamelen het en publiceren het in het register met behulp van deze identificatie (#c1). Met dezelfde ID rollen we uit naar preview. Hetzelfde doen we met commits #c2 и #c3.

Toen we ons realiseerden dat er voldoende functies zijn, beginnen we alles te stabiliseren. Maak een branch in Git release_1.1 (op de basis #c3 van develop). Het is niet nodig om deze release te verzamelen, omdat... dit is in de vorige stap gedaan. Daarom kunnen we het eenvoudig uitrollen naar enscenering. We repareren bugs in #c4 en op dezelfde manier uitrollen naar enscenering. Tegelijkertijd is er sprake van een ontwikkeling in develop, waar periodiek wijzigingen uit worden overgenomen release_1.1. Op een gegeven moment krijgen we een commit samengesteld en geüpload naar staging, waar we blij mee zijn (#c25).

Vervolgens mergen we (met vooruitspoelen) de release branch (release_1.1) in meester. We hebben een tag met de nieuwe versie op deze commit geplaatst (1.1). Maar deze afbeelding is al in het register verzameld, dus om deze niet opnieuw te verzamelen, voegen we eenvoudigweg een tweede tag toe aan de bestaande afbeelding (nu heeft deze tags in het register #c25 и 1.1). Daarna rollen we het uit naar productie.

Er is een nadeel dat er slechts één afbeelding wordt geüpload naar staging (#c25), en in de productie is het een beetje anders (1.1), maar we weten dat dit “fysiek” dezelfde afbeelding uit het register is.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Het echte nadeel is dat er geen ondersteuning is voor merge commits, je moet snel vooruitspoelen.

We kunnen nog verder gaan en een trucje doen... Laten we eens kijken naar een voorbeeld van een eenvoudig Dockerbestand:

FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb

FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/public

Laten we er een bestand van maken volgens het volgende principe:

  • SHA256 van de identificatiegegevens van de gebruikte afbeeldingen (ruby:2.3 и nginx:alpine), dit zijn controlesommen van hun inhoud;
  • alle teams (RUN, CMD enzovoort.);
  • SHA256 uit bestanden die zijn toegevoegd.

... en neem de controlesom (opnieuw SHA256) uit zo'n bestand. Dit handtekening alles dat de inhoud van de Docker-image definieert.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Laten we teruggaan naar het diagram en in plaats van commits zullen we dergelijke handtekeningen gebruiken, d.w.z. afbeeldingen taggen met handtekeningen.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Als het nu bijvoorbeeld nodig is om wijzigingen van een release naar de master samen te voegen, kunnen we een echte merge-commit doen: deze zal een andere identificatie hebben, maar dezelfde handtekening. Met dezelfde ID rollen we de afbeelding uit naar productie.

Het nadeel is dat het nu niet mogelijk zal zijn om te bepalen welk soort commit naar productie is gepusht - checksums werken slechts in één richting. Dit probleem wordt opgelost door een extra laag met metadata. Ik vertel je er later meer over.

Taggen in werf

In de werf zijn we zelfs nog verder gegaan en bereiden we ons voor op een gedistribueerde build met een cache die niet op één machine is opgeslagen... We bouwen dus twee soorten Docker-images, we noemen ze stadium и beeld.

De werf Git-repository slaat bouwspecifieke instructies op die de verschillende fasen van de bouw beschrijven (vóórInstalleren, installeren, vóórInstelling, setup). We verzamelen de afbeelding van de eerste fase met een handtekening die is gedefinieerd als de controlesom van de eerste stappen. Vervolgens voegen we de broncode toe, voor de nieuwe podiumafbeelding berekenen we de controlesom... Deze bewerkingen worden herhaald voor alle fasen, waardoor we een reeks podiumafbeeldingen krijgen. Vervolgens maken we het uiteindelijke beeld, dat ook metadata bevat over de herkomst ervan. En we taggen deze afbeelding op verschillende manieren (details later).

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Stel dat hierna een nieuwe commit verschijnt waarin alleen de applicatiecode is gewijzigd. Wat zal er gebeuren? Voor codewijzigingen wordt een patch gemaakt en wordt een nieuwe stage-image voorbereid. De handtekening ervan wordt bepaald als de controlesom van de oude podiumafbeelding en de nieuwe patch. Uit deze afbeelding wordt een nieuw eindbeeld gevormd. Soortgelijk gedrag zal optreden bij veranderingen in andere fasen.

Stage-images zijn dus een cache die gedistribueerd kan worden opgeslagen, en de images die er al van zijn gemaakt, worden geüpload naar de Docker Registry.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Het register opschonen

We hebben het niet over het verwijderen van lagen die bleven hangen na verwijderde tags - dit is een standaardfunctie van de Docker Registry zelf. We hebben het over een situatie waarin veel Docker-tags zich ophopen en we begrijpen dat we sommige ervan niet langer nodig hebben, maar dat ze ruimte in beslag nemen (en/of ervoor betalen).

Wat zijn de schoonmaakstrategieën?

  1. Je kunt gewoon niets doen niet schoonmaken. Soms is het echt gemakkelijker om wat te betalen voor extra ruimte dan om een ​​enorme wirwar aan tags te ontrafelen. Maar dit werkt maar tot op zekere hoogte.
  2. Volledige reset. Als u alle afbeeldingen verwijdert en alleen de huidige in het CI-systeem opnieuw opbouwt, kan er een probleem optreden. Als de container opnieuw in productie wordt genomen, wordt er een nieuwe image voor geladen - eentje die nog door niemand is getest. Dit doodt het idee van een onveranderlijke infrastructuur.
  3. Blauw groen. Het ene register begon over te lopen - we uploaden afbeeldingen naar het andere. Hetzelfde probleem als bij de vorige methode: op welk punt kunt u het register wissen dat overloopt?
  4. Tegen de tijd. Alle afbeeldingen ouder dan 1 maand verwijderen? Maar er zal zeker een dienst zijn die al een maand niet is bijgewerkt...
  5. handmatig bepalen wat al kan worden verwijderd.

Er zijn twee echt haalbare opties: niet schoonmaken of een combinatie van blauw-groen + handmatig. In dat laatste geval hebben we het over het volgende: wanneer je begrijpt dat het tijd is om het register op te schonen, maak je in de loop van bijvoorbeeld een maand een nieuw register aan en voeg je daar alle nieuwe afbeeldingen aan toe. En kijk na een maand welke pods in Kubernetes nog steeds het oude register gebruiken, en breng deze ook over naar het nieuwe register.

Waar zijn we toe gekomen werf? We verzamelen:

  1. Git head: alle tags, alle branches - ervan uitgaande dat we alles nodig hebben dat in Git in de afbeeldingen is getagd (en zo niet, dan moeten we het in Git zelf verwijderen);
  2. alle pods die momenteel naar Kubernetes worden gepompt;
  3. oude ReplicaSets (wat onlangs is uitgebracht), en we zijn ook van plan Helm-releases te scannen en daar de nieuwste images te selecteren.

... en maak een witte lijst van deze set - een lijst met afbeeldingen die we niet zullen verwijderen. We ruimen al het andere op, waarna we verweesde podiumafbeeldingen vinden en deze ook verwijderen.

Podium implementeren

Betrouwbare declarativiteit

Het eerste punt waar ik bij de implementatie de aandacht op zou willen vestigen, is de uitrol van de bijgewerkte resourceconfiguratie, declaratief verklaard. Het originele YAML-document waarin Kubernetes-bronnen worden beschreven, wijkt altijd sterk af van het resultaat dat daadwerkelijk in het cluster wordt uitgevoerd. Omdat Kubernetes bijdraagt ​​aan de configuratie:

  1. identificatiegegevens;
  2. dienstinformatie;
  3. veel standaardwaarden;
  4. sectie met huidige status;
  5. wijzigingen aangebracht als onderdeel van de toelatingswebhook;
  6. het resultaat van het werk van verschillende controllers (en de planner).

Daarom, wanneer een nieuwe bronconfiguratie verschijnt (nieuwe), kunnen we er niet zomaar de huidige, “live” configuratie mee overnemen en overschrijven (leven). Om dit te doen zullen we moeten vergelijken nieuwe met de laatst toegepaste configuratie (laatst toegepast) en rol verder leven pleister ontvangen.

Deze aanpak heet 2-weg samenvoegen. Het wordt bijvoorbeeld gebruikt in Helm.

Er zijn ook 3-weg samenvoegen, wat daarin verschilt:

  • vergelijken laatst toegepast и nieuwe, we kijken naar wat er is verwijderd;
  • vergelijken nieuwe и leven, we kijken naar wat er is toegevoegd of veranderd;
  • waarop de gesommeerde patch wordt toegepast leven.

We implementeren meer dan 1000 applicaties met Helm, dus we leven eigenlijk met 2-way merge. Er zijn echter een aantal problemen die we hebben opgelost met onze patches, waardoor Helm normaal kan werken.

Echte uitrolstatus

Nadat ons CI-systeem op basis van de volgende gebeurtenis een nieuwe configuratie voor Kubernetes heeft gegenereerd, verzendt het deze voor gebruik (toepassen) naar een cluster - met behulp van Helm of kubectl apply. Vervolgens vindt de reeds beschreven N-way merge plaats, waarop de Kubernetes API goedkeurend reageert op het CI-systeem, en daarmee op de gebruiker ervan.

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Er is echter een groot probleem: tenslotte succesvolle toepassing betekent niet een succesvolle uitrol. Als Kubernetes begrijpt welke veranderingen er moeten worden doorgevoerd en deze toepast, weten we nog steeds niet wat het resultaat zal zijn. Het bijwerken en opnieuw opstarten van pods in de frontend kan bijvoorbeeld succesvol zijn, maar niet in de backend, en we krijgen verschillende versies van de actieve applicatie-images.

Om alles correct te doen, heeft dit schema een extra link nodig: een speciale tracker die statusinformatie van de Kubernetes API ontvangt en deze verzendt voor verdere analyse van de werkelijke stand van zaken. We hebben een Open Source-bibliotheek gemaakt in Go - kubushond (zie de aankondiging hier), die dit probleem oplost en in de werf wordt ingebouwd.

Het gedrag van deze tracker op werfniveau wordt geconfigureerd met behulp van annotaties die op Deployments of StatefulSets worden geplaatst. Belangrijkste annotatie - fail-mode - begrijpt de volgende betekenissen:

  • IgnoreAndContinueDeployProcess — we negeren de problemen bij het uitrollen van dit onderdeel en zetten de implementatie voort;
  • FailWholeDeployProcessImmediately — een fout in dit onderdeel stopt het implementatieproces;
  • HopeUntilEndOfDeployProcess — we hopen dat dit onderdeel aan het einde van de implementatie zal werken.

Bijvoorbeeld deze combinatie van bronnen en annotatiewaarden fail-mode:

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Wanneer we voor de eerste keer implementeren, is de database (MongoDB) mogelijk nog niet gereed. Implementaties zullen mislukken. Maar je kunt wachten op het moment waarop het begint en de implementatie zal nog steeds plaatsvinden.

Er zijn nog twee annotaties voor kubedog in werf:

  • failures-allowed-per-replica — het aantal toegestane valpartijen voor elke replica;
  • show-logs-until — regelt het moment tot wanneer de werf (in stdout) logs toont van alle uitgerolde pods. De standaardwaarde is PodIsReady (om berichten te negeren die we waarschijnlijk niet willen als er verkeer naar de pod komt), maar waarden zijn ook geldig: ControllerIsReady и EndOfDeploy.

Wat willen we nog meer van de implementatie?

Naast de twee reeds beschreven punten willen wij:

  • te zien logboeken - en alleen de noodzakelijke, en niet alles op een rij;
  • spoor vooruitgang, want als de taak enkele minuten ‘stil’ blijft hangen, is het belangrijk om te begrijpen wat daar gebeurt;
  • иметь automatisch terugdraaien voor het geval er iets mis is gegaan (en daarom is het van cruciaal belang om de werkelijke status van de implementatie te kennen). De uitrol moet atomair zijn: óf het gaat door tot het einde, óf alles keert terug naar de vorige staat.

Resultaten van

Voor ons als bedrijf zijn een CI-systeem en een hulpprogramma voldoende om alle beschreven nuances in verschillende fasen van de oplevering (bouwen, publiceren, implementeren) te implementeren werf.

In plaats van een conclusie:

werf - onze tool voor CI/CD in Kubernetes (overzicht en videoverslag)

Met de hulp van werf hebben we goede vooruitgang geboekt bij het oplossen van een groot aantal problemen voor DevOps-ingenieurs en we zouden blij zijn als de bredere gemeenschap dit hulpprogramma in ieder geval in actie zou proberen. Samen bereik je makkelijker een goed resultaat.

Video's en dia's

Video van de voorstelling (~47 minuten):

Presentatie van het rapport:

PS

Andere reportages over Kubernetes op onze blog:

Bron: www.habr.com

Voeg een reactie