Vijf missers bij het implementeren van de eerste applicatie op Kubernetes

Vijf missers bij het implementeren van de eerste applicatie op KubernetesMislukt door Aris Dreamer

Veel mensen denken dat het voldoende is om de applicatie over te zetten naar Kubernetes (met Helm of handmatig) - en er zal geluk zijn. Maar niet alles is zo eenvoudig.

Team Mail.ru Cloud-oplossingen vertaalde een artikel van DevOps-engineer Julian Gindy. Hij vertelt met welke valkuilen zijn bedrijf tijdens het migratieproces te maken heeft gehad, zodat je niet op hetzelfde schuitje stapt.

Stap één: Pod-verzoeken en limieten instellen

Laten we beginnen met het opzetten van een schone omgeving waarin onze pods zullen werken. Kubernetes is geweldig in het plannen van pods en failover. Maar het bleek dat de planner soms een pod niet kan plaatsen als het moeilijk in te schatten is hoeveel resources hij nodig heeft om succesvol te werken. Dit is waar verzoeken om middelen en limieten verschijnen. Er is veel discussie over de beste aanpak voor het stellen van verzoeken en limieten. Soms lijkt het alsof dit eigenlijk meer een kunst is dan een wetenschap. Hier is onze aanpak.

Pod-verzoeken is de belangrijkste waarde die door de planner wordt gebruikt om de pod optimaal te plaatsen.

Van Kubernetes-documentatie: De filterstap definieert een set knooppunten waar een pod kan worden gepland. Het filter PodFitsResources controleert bijvoorbeeld of een knooppunt voldoende resources heeft om aan specifieke resourceverzoeken van een pod te voldoen.

We gebruiken aanvraagverzoeken zo dat we kunnen inschatten hoeveel middelen in feite De applicatie heeft het nodig om goed te kunnen functioneren. Op deze manier kan de planner de knooppunten realistisch plaatsen. In eerste instantie wilden we aanvragen overplannen om ervoor te zorgen dat er voldoende bronnen voor elke Pod waren, maar we merkten dat de planningstijd aanzienlijk toenam en dat sommige Pods niet volledig waren gepland, alsof er geen resourceverzoeken voor waren.

In dit geval zou de planner de pods vaak "uitknijpen" en ze niet opnieuw kunnen plannen omdat het besturingsvlak geen idee had hoeveel bronnen de applicatie nodig zou hebben, wat een belangrijk onderdeel is van het planningsalgoritme.

Pod-limieten is een duidelijkere limiet voor een pod. Het vertegenwoordigt de maximale hoeveelheid resources die het cluster aan de container zal toewijzen.

Nogmaals, van officiële documentatie: Als een container een geheugenlimiet heeft van 4 GiB, dan zal de kubelet (en de containerruntime) dit afdwingen. De runtime voorkomt dat de container meer gebruikt dan de opgegeven resourcelimiet. Wanneer een proces in een container bijvoorbeeld meer probeert te gebruiken dan de toegestane hoeveelheid geheugen, beëindigt de systeemkernel het proces met een "out of memory" (OOM)-fout.

Een container kan altijd meer resources gebruiken dan het resourceverzoek aangeeft, maar nooit meer dan de limiet. Deze waarde is moeilijk correct in te stellen, maar is erg belangrijk.

Idealiter willen we dat de resourcevereisten van een pod tijdens de levenscyclus van een proces veranderen zonder andere processen in het systeem te verstoren - dit is het doel van het stellen van limieten.

Ik kan helaas geen specifieke instructies geven over welke waarden ingesteld moeten worden, maar zelf houden we ons aan de volgende regels:

  1. Met behulp van een load testing tool simuleren we een basisniveau van verkeer en observeren we het gebruik van pod-resources (geheugen en processor).
  2. Stel de pod-verzoeken in op een willekeurig lage waarde (met een resourcelimiet van ongeveer 5 keer de verzoekwaarde) en observeer. Wanneer verzoeken van een te laag niveau zijn, kan het proces niet starten, wat vaak cryptische Go-runtime-fouten veroorzaakt.

Ik merk op dat hogere resourcelimieten planning moeilijker maken omdat de pod een doelknooppunt nodig heeft met voldoende beschikbare resources.

Stel je een situatie voor waarin je een lichtgewicht webserver hebt met een zeer hoge resourcelimiet, zoals 4 GB geheugen. Dit proces moet waarschijnlijk horizontaal worden uitgeschaald en elke nieuwe pod moet worden gepland op een knooppunt met ten minste 4 GB beschikbaar geheugen. Als een dergelijk knooppunt niet bestaat, moet het cluster een nieuw knooppunt introduceren om deze pod te verwerken, wat enige tijd kan duren. Het is belangrijk om een ​​minimaal verschil te bereiken tussen resourceverzoeken en limieten om een ​​snelle en soepele schaalvergroting te garanderen.

Stap twee: stel levendigheids- en gereedheidstests in

Dit is een ander subtiel onderwerp dat vaak wordt besproken in de Kubernetes-community. Het is belangrijk om een ​​goed begrip te hebben van Liveness- en Readiness-tests, aangezien deze een mechanisme bieden voor een stabiele werking van de software en downtime minimaliseren. Ze kunnen echter ernstige gevolgen hebben voor de prestaties van uw toepassing als ze niet correct zijn geconfigureerd. Hieronder is een samenvatting van wat beide voorbeelden zijn.

Levendigheid geeft aan of de container draait. Als het mislukt, doodt de kubelet de container en wordt het herstartbeleid ervoor ingeschakeld. Als de container niet is uitgerust met een Liveness Probe, is de standaardstatus geslaagd - zoals vermeld in Kubernetes-documentatie.

Liveness-sondes moeten goedkoop zijn, d.w.z. niet veel resources verbruiken, omdat ze vaak worden uitgevoerd en Kubernetes moeten informeren dat de applicatie actief is.

Als u de optie instelt om elke seconde uit te voeren, voegt dit 1 verzoek per seconde toe, dus houd er rekening mee dat er extra bronnen nodig zijn om dit verkeer te verwerken.

Bij ons bedrijf testen Liveness-tests de kerncomponenten van een applicatie, zelfs als de gegevens (bijvoorbeeld uit een externe database of cache) niet volledig beschikbaar zijn.

We hebben een 'gezondheids'-eindpunt in de applicaties ingesteld dat simpelweg een responscode retourneert van 200. Dit is een indicator dat het proces actief is en verzoeken kan verwerken (maar nog geen verkeer).

test gereedheid geeft aan of de container klaar is om verzoeken te verwerken. Als de gereedheidstest mislukt, verwijdert de eindpuntcontroller het IP-adres van de pod van de eindpunten van alle services die overeenkomen met de pod. Dit staat ook vermeld in de Kubernetes-documentatie.

Gereedheidstests verbruiken meer bronnen, omdat ze de backend zodanig moeten bereiken dat ze aantonen dat de toepassing klaar is om verzoeken te accepteren.

Er is veel discussie in de gemeenschap over het al dan niet rechtstreeks toegang krijgen tot de database. Gezien de overhead (de controles zijn frequent, maar ze kunnen worden gecontroleerd), hebben we besloten dat voor sommige applicaties de gereedheid om verkeer te verwerken pas wordt geteld nadat is gecontroleerd of records uit de database worden geretourneerd. Goed ontworpen readiness-tests zorgden voor een hogere beschikbaarheid en elimineerden downtime tijdens de implementatie.

Als u besluit de database te doorzoeken om de gereedheid van uw toepassing te testen, zorg er dan voor dat dit zo goedkoop mogelijk is. Laten we deze vraag nemen:

SELECT small_item FROM table LIMIT 1

Hier is een voorbeeld van hoe we deze twee waarden configureren in Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

U kunt enkele aanvullende configuratie-opties toevoegen:

  • initialDelaySeconds - hoeveel seconden zullen verstrijken tussen de lancering van de container en de start van de lancering van de sondes.
  • periodSeconds — wachtinterval tussen monsterruns.
  • timeoutSeconds — het aantal seconden waarna de pod als noodgeval wordt beschouwd. Normale time-out.
  • failureThreshold is het aantal mislukte tests voordat een herstartsignaal naar de pod wordt verzonden.
  • successThreshold is het aantal geslaagde pogingen voordat de pod overgaat naar de status Gereed (na een fout bij het opstarten of herstellen van de pod).

Stap drie: het standaard netwerkbeleid van de pod instellen

Kubernetes heeft een "platte" netwerktopografie, standaard communiceren alle pods direct met elkaar. In sommige gevallen is dit niet wenselijk.

Een mogelijk beveiligingsprobleem is dat een aanvaller een enkele kwetsbare applicatie kan gebruiken om verkeer naar alle pods op het netwerk te sturen. Zoals op veel gebieden van beveiliging, is hier het principe van de minste privileges van toepassing. Idealiter zou netwerkbeleid expliciet moeten aangeven welke verbindingen tussen pods zijn toegestaan ​​en welke niet.

Het volgende is bijvoorbeeld een eenvoudig beleid dat al het inkomende verkeer voor een bepaalde naamruimte weigert:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

Visualisatie van deze configuratie:

Vijf missers bij het implementeren van de eerste applicatie op Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Meer details hier.

Stap vier: Aangepast gedrag met hooks en init-containers

Een van onze belangrijkste doelen was om implementaties in Kubernetes te bieden zonder downtime voor ontwikkelaars. Dit is moeilijk omdat er veel opties zijn om applicaties af te sluiten en hun gebruikte bronnen vrij te geven.

Er deden zich bijzondere moeilijkheden voor Nginx. We hebben gemerkt dat bij het achtereenvolgens inzetten van deze pods actieve verbindingen werden onderbroken voordat ze met succes werden voltooid.

Na uitgebreid onderzoek op internet bleek dat Kubernetes niet wacht tot de Nginx-verbindingen uitgeput zijn voordat ze de pod afsluiten. Met behulp van de pre-stop hook hebben we de volgende functionaliteit geïmplementeerd en de downtime volledig verlost:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

Maar nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

Een ander uiterst nuttig paradigma is het gebruik van init-containers om de lancering van specifieke applicaties af te handelen. Dit is vooral handig als u een resource-intensief databasemigratieproces hebt dat moet worden uitgevoerd voordat de toepassing start. U kunt voor dit proces ook een hogere resourcelimiet opgeven zonder een dergelijke limiet in te stellen voor de hoofdtoepassing.

Een ander veelvoorkomend schema is om toegang te krijgen tot geheimen in de init-container, die deze inloggegevens aan de hoofdmodule levert, waardoor ongeoorloofde toegang tot geheimen van de hoofdtoepassingsmodule zelf wordt voorkomen.

Zoals gewoonlijk een citaat uit de documentatie: init-containers voeren veilig gebruikerscode of hulpprogramma's uit die anders de beveiliging van de containerimage van de toepassing in gevaar zouden brengen. Door onnodige tools gescheiden te houden, beperk je het aanvalsoppervlak van de containerimage van de applicatie.

Stap vijf: kernelconfiguratie

Laten we het tot slot hebben over een meer geavanceerde techniek.

Kubernetes is een uiterst flexibel platform waarmee u workloads kunt uitvoeren zoals u dat wilt. We hebben een aantal zeer efficiënte toepassingen die extreem veel middelen vergen. Na uitgebreide belastingstests te hebben uitgevoerd, ontdekten we dat een van de applicaties moeite had om de verwachte verkeersbelasting bij te houden wanneer de standaard Kubernetes-instellingen van kracht waren.

Met Kubernetes kunt u echter een geprivilegieerde container uitvoeren die alleen de kernelparameters voor een specifieke pod wijzigt. Dit is wat we gebruikten om het maximale aantal open verbindingen te wijzigen:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

Dit is een meer geavanceerde techniek die vaak niet nodig is. Maar als uw toepassing moeite heeft met een zware belasting, kunt u proberen enkele van deze instellingen aan te passen. Meer informatie over dit proces en het instellen van verschillende waarden - zoals altijd in de officiële documentatie.

Concluderend

Hoewel Kubernetes misschien een kant-en-klare oplossing lijkt, zijn er een paar belangrijke stappen die moeten worden genomen om applicaties soepel te laten werken.

Tijdens de migratie naar Kubernetes is het belangrijk om de "belastingtestcyclus" te volgen: voer de applicatie uit, test deze onder belasting, observeer de statistieken en het schaalgedrag, pas de configuratie aan op basis van deze gegevens en herhaal deze cyclus opnieuw.

Wees realistisch over het verwachte verkeer en probeer verder te gaan om te zien welke componenten het eerst kapot gaan. Met deze iteratieve aanpak kunnen slechts enkele van de vermelde aanbevelingen voldoende zijn om succes te behalen. Of er kan meer diepgaand maatwerk nodig zijn.

Stel jezelf altijd deze vragen:

  1. Hoeveel bronnen verbruiken applicaties en hoe verandert dit aantal?
  2. Wat zijn de echte schaalvereisten? Hoeveel verkeer verwerkt de app gemiddeld? Hoe zit het met spitsverkeer?
  3. Hoe vaak moet de service worden uitgeschaald? Hoe snel moeten nieuwe pods operationeel zijn om verkeer te ontvangen?
  4. Hoe gracieus sluiten pods af? Is het überhaupt nodig? Is het mogelijk om deployment te realiseren zonder downtime?
  5. Hoe beveiligingsrisico's te minimaliseren en schade door gecompromitteerde pods te beperken? Hebben services machtigingen of toegangen die ze niet nodig hebben?

Kubernetes biedt een ongelooflijk platform waarmee u best practices kunt gebruiken om duizenden services in een cluster te implementeren. Alle toepassingen zijn echter anders. Soms vergt de implementatie wat meer werk.

Gelukkig zorgt Kubernetes voor de nodige instellingen om alle technische doelen te behalen. Door een combinatie van resourceverzoeken en -limieten, Liveness- en Readiness-probes, init-containers, netwerkbeleid en aangepaste kerneltuning te gebruiken, kunt u hoge prestaties bereiken, samen met fouttolerantie en snelle schaalbaarheid.

Wat nog meer te lezen:

  1. Best Practices en Best Practices voor het uitvoeren van containers en Kubernetes in productieomgevingen.
  2. 90+ handige tools voor Kubernetes: implementatie, beheer, monitoring, beveiliging en meer.
  3. Ons kanaal Around Kubernetes in Telegram.

Bron: www.habr.com

Voeg een reactie