“Kubernetes verhoogde de latentie met 10 keer”: wie is hier verantwoordelijk voor?

Opmerking. vert.: Dit artikel, geschreven door Galo Navarro, die de functie bekleedt van Principal Software Engineer bij het Europese bedrijf Adevinta, is een fascinerend en leerzaam ‘onderzoek’ op het gebied van infrastructuuroperaties. De oorspronkelijke titel werd in de vertaling enigszins uitgebreid om een ​​reden die de auteur helemaal aan het begin uitlegt.

“Kubernetes verhoogde de latentie met 10 keer”: wie is hier verantwoordelijk voor?

Opmerking van de auteur: Lijkt op dit bericht aangetrokken veel meer aandacht dan verwacht. Ik krijg nog steeds boze opmerkingen dat de titel van het artikel misleidend is en dat sommige lezers bedroefd zijn. Ik begrijp de redenen voor wat er gebeurt, daarom wil ik, ondanks het risico de hele intriges te verpesten, onmiddellijk vertellen waar dit artikel over gaat. Het merkwaardige dat ik heb gezien toen teams naar Kubernetes migreerden, is dat wanneer er zich een probleem voordoet (zoals een langere latentie na een migratie), Kubernetes de eerste schuld krijgt, maar dan blijkt dat de orkestrator dat niet echt doet. schuld. Dit artikel vertelt over een dergelijk geval. De naam herhaalt de uitroep van een van onze ontwikkelaars (later zul je zien dat Kubernetes er niets mee te maken heeft). Verrassende onthullingen over Kubernetes vind je hier niet, maar wel een paar goede lessen over complexe systemen.

Een paar weken geleden migreerde mijn team een ​​enkele microservice naar een kernplatform dat CI/CD, een op Kubernetes gebaseerde runtime, statistieken en andere goodies omvatte. De verhuizing had een proefkarakter: we waren van plan om op basis daarvan de komende maanden nog ongeveer 150 diensten over te dragen. Ze zijn allemaal verantwoordelijk voor de werking van enkele van de grootste online platforms in Spanje (Infojobs, Fotocasa, enz.).

Nadat we de applicatie in Kubernetes hadden geïmplementeerd en wat verkeer ernaar hadden omgeleid, wachtte ons een alarmerende verrassing. Vertraging (latentie) verzoeken in Kubernetes waren 10 keer hoger dan in EC2. Over het algemeen was het nodig om ofwel een oplossing voor dit probleem te vinden, ofwel de migratie van de microservice (en mogelijk het hele project) te staken.

Waarom is de latentie zoveel hoger in Kubernetes dan in EC2?

Om het knelpunt te vinden, hebben we statistieken verzameld over het gehele verzoekpad. Onze architectuur is eenvoudig: een API-gateway (Zuul) proxy-aanvragen naar microservice-instances in EC2 of Kubernetes. In Kubernetes gebruiken we NGINX Ingress Controller, en de backends zijn gewone objecten zoals Deployment met een JVM-applicatie op het Spring-platform.

                                  EC2
                            +---------------+
                            |  +---------+  |
                            |  |         |  |
                       +-------> BACKEND |  |
                       |    |  |         |  |
                       |    |  +---------+  |                   
                       |    +---------------+
             +------+  |
Public       |      |  |
      -------> ZUUL +--+
traffic      |      |  |              Kubernetes
             +------+  |    +-----------------------------+
                       |    |  +-------+      +---------+ |
                       |    |  |       |  xx  |         | |
                       +-------> NGINX +------> BACKEND | |
                            |  |       |  xx  |         | |
                            |  +-------+      +---------+ |
                            +-----------------------------+

Het probleem leek verband te houden met de initiële latentie in de backend (ik heb het probleemgebied in de grafiek gemarkeerd als "xx"). Op EC2 duurde het antwoord van de applicatie ongeveer 20 ms. In Kubernetes nam de latentie toe tot 100-200 ms.

We hebben de waarschijnlijke verdachten met betrekking tot de runtimewijziging snel afgewezen. De JVM-versie blijft hetzelfde. Ook containerisatieproblemen hadden er niets mee te maken: de applicatie draaide al succesvol in containers op EC2. Bezig met laden? Maar we hebben hoge latenties waargenomen, zelfs bij 1 verzoek per seconde. Pauzes voor de afvalinzameling kunnen ook worden verwaarloosd.

Een van onze Kubernetes-beheerders vroeg zich af of de applicatie externe afhankelijkheden had, omdat DNS-query's in het verleden soortgelijke problemen hadden veroorzaakt.

Hypothese 1: DNS-naamresolutie

Voor elk verzoek heeft onze applicatie één tot drie keer toegang tot een AWS Elasticsearch-instantie in een domein zoals elastic.spain.adevinta.com. In onze containers er is een schil, zodat we kunnen controleren of het zoeken naar een domein daadwerkelijk lang duurt.

DNS-query's uit container:

[root@be-851c76f696-alf8z /]# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 22 msec
;; Query time: 22 msec
;; Query time: 29 msec
;; Query time: 21 msec
;; Query time: 28 msec
;; Query time: 43 msec
;; Query time: 39 msec

Soortgelijke verzoeken van een van de EC2-instanties waarop de applicatie draait:

bash-4.4# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 77 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec

Gezien het feit dat de zoekopdracht ongeveer 30 ms duurde, werd het duidelijk dat DNS-resolutie bij toegang tot Elasticsearch inderdaad bijdroeg aan de toename van de latentie.

Dit was echter om twee redenen vreemd:

  1. We hebben al een heleboel Kubernetes-applicaties die communiceren met AWS-bronnen zonder te lijden onder hoge latentie. Wat de reden ook is, het heeft specifiek betrekking op deze zaak.
  2. We weten dat de JVM DNS-caching in het geheugen uitvoert. In onze afbeeldingen is de TTL-waarde geschreven $JAVA_HOME/jre/lib/security/java.security en ingesteld op 10 seconden: networkaddress.cache.ttl = 10. Met andere woorden: de JVM moet alle DNS-query's gedurende 10 seconden in de cache opslaan.

Om de eerste hypothese te bevestigen, hebben we besloten een tijdje niet meer naar DNS te bellen en te kijken of het probleem verdween. Ten eerste hebben we besloten om de applicatie opnieuw te configureren, zodat deze rechtstreeks met Elasticsearch communiceerde via IP-adres, in plaats van via een domeinnaam. Hiervoor zouden codewijzigingen en een nieuwe implementatie nodig zijn, dus hebben we het domein eenvoudigweg aan het IP-adres toegewezen /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Nu kreeg de container vrijwel onmiddellijk een IP-adres. Dit resulteerde in enige verbetering, maar we waren slechts iets dichter bij de verwachte latentieniveaus. Hoewel de DNS-resolutie lang duurde, ontging de echte reden ons nog steeds.

Diagnose via netwerk

We besloten het verkeer vanuit de container te analyseren met behulp van tcpdumpom te zien wat er precies op het netwerk gebeurt:

[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap

Vervolgens hebben we verschillende verzoeken verzonden en hun opname gedownload (kubectl cp my-service:/capture.pcap capture.pcap) voor verdere analyse in Wireshark.

Er was niets verdachts aan de DNS-query's (behalve één klein dingetje waar ik het later over zal hebben). Maar er waren bepaalde eigenaardigheden in de manier waarop onze dienst met elk verzoek omging. Hieronder ziet u een schermafdruk van de opname, waarop te zien is dat het verzoek wordt geaccepteerd voordat het antwoord begint:

“Kubernetes verhoogde de latentie met 10 keer”: wie is hier verantwoordelijk voor?

Pakketnummers vindt u in de eerste kolom. Voor de duidelijkheid heb ik de verschillende TCP-stromen een kleurcode gegeven.

De groene stroom die begint met pakket 328 laat zien hoe de client (172.17.22.150) een TCP-verbinding met de container (172.17.36.147) tot stand heeft gebracht. Na de eerste handdruk (328-330) werd pakket 331 gebracht HTTP GET /v1/.. — een binnenkomend verzoek bij onze dienst. Het hele proces duurde 1 ms.

De grijze stroom (uit pakket 339) laat zien dat onze service een HTTP-verzoek naar de Elasticsearch-instantie heeft verzonden (er is geen TCP-handshake omdat deze een bestaande verbinding gebruikt). Dit duurde 18 ms.

Tot nu toe is alles in orde en komen de tijden grofweg overeen met de verwachte vertragingen (20-30 ms gemeten vanaf de client).

Het blauwe gedeelte duurt echter 86 ms. Wat gebeurt er daarin? Met pakket 333 stuurde onze service een HTTP GET-verzoek naar /latest/meta-data/iam/security-credentials, en onmiddellijk daarna, via dezelfde TCP-verbinding, nog een GET-verzoek /latest/meta-data/iam/security-credentials/arn:...

We ontdekten dat dit zich tijdens de tracering bij elk verzoek herhaalde. DNS-resolutie is inderdaad iets langzamer in onze containers (de verklaring voor dit fenomeen is best interessant, maar ik bewaar deze voor een apart artikel). Het bleek dat de oorzaak van de lange vertragingen bij elk verzoek naar de AWS Instance Metadata-service lag.

Hypothese 2: onnodige oproepen naar AWS

Beide eindpunten behoren tot API voor metagegevens van AWS-instanties. Onze microservice gebruikt deze service tijdens het uitvoeren van Elasticsearch. Beide oproepen maken deel uit van het basisautorisatieproces. Het eindpunt waartoe bij het eerste verzoek toegang wordt verkregen, geeft de IAM-rol uit die aan het exemplaar is gekoppeld.

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
arn:aws:iam::<account_id>:role/some_role

Bij het tweede verzoek wordt het tweede eindpunt om tijdelijke machtigingen voor deze instantie gevraagd:

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role`
{
    "Code" : "Success",
    "LastUpdated" : "2012-04-26T16:39:16Z",
    "Type" : "AWS-HMAC",
    "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
    "SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "Token" : "token",
    "Expiration" : "2017-05-17T15:09:54Z"
}

De klant kan ze voor een korte periode gebruiken en moet periodiek nieuwe certificaten verkrijgen (voordat ze dat zijn). Expiration). Het model is eenvoudig: AWS roteert tijdelijke sleutels regelmatig om veiligheidsredenen, maar klanten kunnen deze een paar minuten in de cache bewaren om de prestatievermindering te compenseren die gepaard gaat met het verkrijgen van nieuwe certificaten.

De AWS Java SDK zou de verantwoordelijkheid voor het organiseren van dit proces moeten overnemen, maar om de een of andere reden gebeurt dit niet.

Na het zoeken naar problemen op GitHub kwamen we een probleem tegen #1921. Ze hielp ons bepalen in welke richting we verder moesten ‘graven’.

De AWS SDK werkt certificaten bij wanneer een van de volgende omstandigheden zich voordoet:

  • Uiterste houdbaarheidsdatum (Expiration) Val erin EXPIRATION_THRESHOLD, hardgecodeerd tot 15 minuten.
  • Er is meer tijd verstreken sinds de laatste poging om certificaten te vernieuwen REFRESH_THRESHOLD, hardgecodeerd gedurende 60 minuten.

Om de werkelijke vervaldatum te zien van de certificaten die we ontvangen, hebben we de bovenstaande cURL-opdrachten uitgevoerd vanuit zowel de container als de EC2-instantie. De geldigheidsduur van het uit de container ontvangen certificaat bleek veel korter: precies 15 minuten.

Nu is alles duidelijk geworden: voor de eerste aanvraag ontving onze dienst tijdelijke certificaten. Omdat ze niet langer dan 15 minuten geldig waren, zou de AWS SDK besluiten ze bij een volgend verzoek bij te werken. En dit gebeurde bij elk verzoek.

Waarom is de geldigheidsduur van certificaten korter geworden?

AWS Instance Metadata is ontworpen om te werken met EC2-instanties, niet met Kubernetes. Aan de andere kant wilden we de applicatie-interface niet veranderen. Hiervoor hebben wij gebruikt KIAM - een tool waarmee gebruikers (ingenieurs die applicaties in een cluster implementeren) met behulp van agenten op elk Kubernetes-knooppunt IAM-rollen kunnen toewijzen aan containers in pods alsof het EC2-instanties zijn. KIAM onderschept oproepen naar de AWS Instance Metadata-service en verwerkt ze vanuit de cache, nadat ze ze eerder van AWS hebben ontvangen. Vanuit het oogpunt van de toepassing verandert er niets.

KIAM levert kortetermijncertificaten aan pods. Dit is logisch gezien het feit dat de gemiddelde levensduur van een pod korter is dan die van een EC2-instantie. Standaard geldigheidsduur voor certificaten gelijk aan dezelfde 15 minuten.

Als gevolg hiervan ontstaat er een probleem als u beide standaardwaarden over elkaar legt. Elk certificaat dat aan een applicatie wordt verstrekt, vervalt na 15 minuten. De AWS Java SDK dwingt echter een verlenging af van elk certificaat dat minder dan 15 minuten te gaan heeft vóór de vervaldatum.

Als gevolg hiervan wordt het tijdelijke certificaat bij elk verzoek gedwongen vernieuwd, wat een aantal aanroepen naar de AWS API met zich meebrengt en een aanzienlijke toename van de latentie veroorzaakt. In AWS Java SDK hebben we gevonden functieverzoek, waarin een soortgelijk probleem wordt vermeld.

De oplossing bleek eenvoudig. We hebben KIAM eenvoudigweg opnieuw geconfigureerd om certificaten met een langere geldigheidsduur aan te vragen. Toen dit eenmaal gebeurde, begonnen er verzoeken binnen te stromen zonder de deelname van de AWS Metadata-service, en daalde de latentie naar een nog lager niveau dan in EC2.

Bevindingen

Op basis van onze ervaring met migraties zijn bugs in Kubernetes of andere elementen van het platform een ​​van de meest voorkomende oorzaken van problemen. Het lost ook geen fundamentele tekortkomingen op in de microservices die we porteren. Problemen ontstaan ​​vaak simpelweg omdat we verschillende elementen samenvoegen.

We mixen complexe systemen die nog nooit eerder met elkaar hebben samengewerkt, in de verwachting dat ze samen één enkel, groter systeem zullen vormen. Helaas, hoe meer elementen, hoe meer ruimte voor fouten, hoe hoger de entropie.

In ons geval was de hoge latentie niet het gevolg van bugs of slechte beslissingen in Kubernetes, KIAM, AWS Java SDK of onze microservice. Het was het resultaat van het combineren van twee onafhankelijke standaardinstellingen: één in KIAM, de andere in de AWS Java SDK. Afzonderlijk genomen zijn beide parameters zinvol: het actieve beleid voor certificaatvernieuwing in de AWS Java SDK en de korte geldigheidsperiode van certificaten in KAIM. Maar als je ze samenvoegt, worden de resultaten onvoorspelbaar. Twee onafhankelijke en logische oplossingen hoeven in combinatie geen zin te hebben.

PS van vertaler

U kunt meer leren over de architectuur van het KIAM-hulpprogramma voor het integreren van AWS IAM met Kubernetes op dit artikel van zijn makers.

Lees ook op onze blog:

Bron: www.habr.com

Voeg een reactie