Iš gyvenimo su Kubernetes: kaip HTTP serveris nebuvo palankus ispanams

Iš gyvenimo su Kubernetes: kaip HTTP serveris nebuvo palankus ispanams

Mūsų kliento, kurio programų krūva yra „Microsoft“ debesyje (Azure), atstovas išsprendė problemą: neseniai kai kurios užklausos iš kai kurių klientų iš Europos pradėjo baigtis klaidos 400 (Bloga užklausa). Visos programos parašytos .NET, įdiegtos Kubernetes...

Viena iš programų yra API, per kurią galiausiai patenka visas srautas. Šio srauto klausosi HTTP serveris vėgėlė, sukonfigūruotas .NET kliento ir priglobtas podelyje. Derinant mums pasisekė ta prasme, kad buvo konkretus vartotojas, kuris nuolat kartojo problemą. Tačiau viską apsunkino eismo grandinė:

Iš gyvenimo su Kubernetes: kaip HTTP serveris nebuvo palankus ispanams

Ingress klaida atrodė taip:

{
   "number_fields":{
      "status":400,
      "request_time":0.001,
      "bytes_sent":465,
      "upstream_response_time":0,
      "upstream_retries":0,
      "bytes_received":2328
   },
   "stream":"stdout",
   "string_fields":{
      "ingress":"app",
      "protocol":"HTTP/1.1",
      "request_id":"f9ab8540407208a119463975afda90bc",
      "path":"/api/sign-in",
      "nginx_upstream_status":"400",
      "service":"app",
      "namespace":"production",
      "location":"/front",
      "scheme":"https",
      "method":"POST",
      "nginx_upstream_response_time":"0.000",
      "nginx_upstream_bytes_received":"120",
      "vhost":"api.app.example.com",
      "host":"api.app.example.com",
      "user":"",
      "address":"83.41.81.250",
      "nginx_upstream_addr":"10.240.0.110:80",
      "referrer":"https://api.app.example.com/auth/login?long_encrypted_header",
      "service_port":"http",
      "user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
      "time":"2019-03-06T18:29:16+00:00",
      "content_kind":"cache-headers-not-present",
      "request_query":""
   },
   "timestamp":"2019-03-06 18:29:16",
   "labels":{
      "app":"nginx",
      "pod-template-generation":"6",
      "controller-revision-hash":"1682636041"
   },
   "namespace":"kube-nginx-ingress",
   "nsec":6726612,
   "source":"kubernetes",
   "host":"k8s-node-55555-0",
   "pod_name":"nginx-v2hcb",
   "container_name":"nginx",
   "boolean_fields":{}
}

Tuo pačiu metu Kestrel davė:

HTTP/1.1 400 Bad Request
Connection: close
Date: Wed, 06 Mar 2019 12:34:20 GMT
Server: Kestrel
Content-Length: 0

Net ir esant maksimaliai daugžodžiams, Kestrel klaida buvo labai didelė mažai naudingos informacijos:

{
   "number_fields":{"ThreadId":76},
   "stream":"stdout",
   "string_fields":{
      "EventId":"{"Id"=>17, "Name"=>"ConnectionBadRequest"}",
      "SourceContext":"Microsoft.AspNetCore.Server.Kestrel",
      "ConnectionId":"0HLL2VJSST5KV",
      "@mt":"Connection id "{ConnectionId}" bad request data: "{message}"",
      "@t":"2019-03-07T13:06:48.1449083Z",
      "@x":"Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Malformed request: invalid headers.n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection.TryParseRequest(ReadResult result, Boolean& endConnection)n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequestsAsync>d__185`1.MoveNext()",
      "message":"Malformed request: invalid headers."
   },
   "timestamp":"2019-03-07 13:06:48",
   "labels":{
      "pod-template-hash":"2368795483",
      "service":"app"
   },
   "namespace":"production",
   "nsec":145341848,
   "source":"kubernetes",
   "host":"k8s-node-55555-1",
   "pod_name":"app-67bdcf98d7-mhktx",
   "container_name":"app",
   "boolean_fields":{}
}

Atrodytų, kad tik tcpdump padės išspręsti šią problemą... bet pasikartosiu apie srauto grandinę:

Iš gyvenimo su Kubernetes: kaip HTTP serveris nebuvo palankus ispanams

tyrimas

Akivaizdu, kad geriau klausytis eismo tame konkrečiame mazge, kur Kubernetes dislokavo podą: sąvartyno tūris bus toks, kad bus galima gana greitai rasti bent ką nors. Ir iš tiesų, nagrinėjant jį, buvo pastebėtas toks kadras:

GET /back/user HTTP/1.1
Host: api.app.example.com
X-Request-ID: 27ceb14972da8c21a8f92904b3eff1e5
X-Real-IP: 83.41.81.250
X-Forwarded-For: 83.41.81.250
X-Forwarded-Host: api.app.example.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Original-URI: /front/back/user
X-Scheme: https
X-Original-Forwarded-For: 83.41.81.250
X-Nginx-Geo-Client-Country: Spain
X-Nginx-Geo-Client-City: M.laga
Accept-Encoding: gzip
CF-IPCountry: ES
CF-RAY: 4b345cfd1c4ac691-MAD
CF-Visitor: {"scheme":"https"}
pragma: no-cache
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36
referer: https://app.example.com/auth/login
accept-language: en-US,en;q=0.9,en-GB;q=0.8,pl;q=0.7
cookie: many_encrypted_cookies; .AspNetCore.Identity.Application=something_encrypted; 
CF-Connecting-IP: 83.41.81.250
True-Client-IP: 83.41.81.250
CDN-Loop: cloudflare

HTTP/1.1 400 Bad Request
Connection: close
Date: Wed, 06 Mar 2019 12:34:20 GMT
Server: Kestrel
Content-Length: 0

Atidžiau apžiūrėjus sąvartyną, šis žodis buvo pastebėtas M.laga. Nesunku atspėti, kad Ispanijoje nėra M.lagos miesto (bet yra Malaga). Pasinaudoję šia idėja, pažiūrėjome į Ingress konfigūracijas, kur pamatėme prieš mėnesį įdėtą (kliento pageidavimu) "nekenksmingas" fragmentas:

    ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header X-Nginx-Geo-Client-Country $geoip_country_name;
      proxy_set_header X-Nginx-Geo-Client-City $geoip_city;

Išjungus šių antraščių persiuntimą viskas susitvarkė! (Netrukus paaiškėjo, kad pačiai programai šių antraščių nebereikia.)

Dabar pažvelkime į problemą Plačiau. Jį galima lengvai atkurti programoje, pateikus Telnet užklausą localhost:80:

GET /back/user HTTP/1.1
Host: api.app.example.com
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
Cookie: test=Desiree

... grįžta 401 Unauthorized, kaip tikėtasi. Kas atsitiks, jei atliksime:

GET /back/user HTTP/1.1
Host: api.app.example.com
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
Cookie: test=Désirée

?

Grįš 400 Bad request — programos žurnale gausime mums jau pažįstamą klaidą:

{
   "@t":"2019-03-31T12:59:54.3746446Z",
   "@mt":"Connection id "{ConnectionId}" bad request data: "{message}"",
   "@x":"Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Malformed request: invalid headers.n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection.TryParseRequest(ReadResult result, Boolean& endConnection)n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequestsAsync>d__185`1.MoveNext()",
   "ConnectionId":"0HLLLR1J974L9",
   "message":"Malformed request: invalid headers.",
   "EventId":{
      "Id":17,
      "Name":"ConnectionBadRequest"
   },
   "SourceContext":"Microsoft.AspNetCore.Server.Kestrel",
   "ThreadId":71
}

rezultatai

Tiksliau Kestrel negaliu teisingai apdoroti HTTP antraštes su tinkamais UTF-8 simboliais, kurie yra gana daugelio miestų pavadinimuose.

Papildomas veiksnys mūsų atveju yra tai, kad klientas šiuo metu neplanuoja keisti Kestrel diegimo programoje. Tačiau problemos pačioje AspNetCore (№ 4318, № 7707) jie sako, kad tai nepadės...

Apibendrinant: pastaba jau ne apie konkrečias Kestrel ar UTF-8 problemas (2019 m.?!), o apie tai, kad sąmoningumas ir nuoseklus tyrimas Kiekvienas žingsnis, kurį žengsite ieškant problemų, anksčiau ar vėliau duos vaisių. Sėkmės!

PS

Taip pat skaitykite mūsų tinklaraštyje:

Šaltinis: www.habr.com

Добавить комментарий