No dzīves ar Kubernetes: kā HTTP serveris nedeva priekšroku spāņiem

No dzīves ar Kubernetes: kā HTTP serveris nedeva priekšroku spāņiem

Mūsu klienta pārstāvis, kura lietojumprogrammu steks atrodas Microsoft mākonī (Azure), novērsa problēmu: nesen daži pieprasījumi no dažiem klientiem no Eiropas sāka beigties ar kļūdu 400 (Slikts pieprasījums). Visas lietojumprogrammas ir rakstītas .NET, izvietotas Kubernetes...

Viena no lietojumprogrammām ir API, caur kuru galu galā tiek nodrošināta visa trafika. Šo trafiku noklausās HTTP serveris lauka piekūns, ko konfigurējis .NET klients un mitināts podā. Ar atkļūdošanu mums paveicās tādā ziņā, ka bija konkrēts lietotājs, kurš konsekventi atkārtoja problēmu. Tomēr visu sarežģīja satiksmes ķēde:

No dzīves ar Kubernetes: kā HTTP serveris nedeva priekšroku spāņiem

Ingress kļūda izskatījās šādi:

{
   "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":{}
}

Tajā pašā laikā Kestrels deva:

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

Pat ar maksimālu runīgumu Kestrel kļūda saturēja ārkārtīgi daudz maz noderīgas informācijas:

{
   "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":{}
}

Šķiet, ka tikai tcpdump palīdzēs atrisināt šo problēmu ... bet es atkārtošu par satiksmes ķēdi:

No dzīves ar Kubernetes: kā HTTP serveris nedeva priekšroku spāņiem

Izmeklēšana

Acīmredzot labāk ir klausīties satiksmi šajā konkrētajā mezglā, kur Kubernetes ir izvietojis podiņu: izgāztuves apjoms būs tāds, ka varēs diezgan ātri atrast vismaz kaut ko. Un patiešām, to pārbaudot, tika pamanīts šāds kadrs:

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

Uzmanīgāk apskatot izgāztuvi, vārds tika pamanīts M.laga. Ir viegli uzminēt, ka Spānijā nav M.lagas pilsētas (bet ir Malaga). Izmantojot šo ideju, apskatījām Ingress konfigurācijas, kur redzējām pirms mēneša ievietoto (pēc klienta pieprasījuma) "nekaitīgs" fragments:

    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;

Pēc šo virsrakstu pārsūtīšanas atspējošanas viss kļuva kārtībā! (Drīz kļuva skaidrs, ka pašai lietojumprogrammai šīs galvenes vairs nav vajadzīgas.)

Tagad apskatīsim problēmu vispārīgāk. To var viegli reproducēt lietojumprogrammā, veicot Telnet pieprasījumu 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

... atgriežas 401 Unauthorized, kā gaidīts. Kas notiek, ja mēs rīkojamies:

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

?

Atgriezīsies 400 Bad request — lietojumprogrammu žurnālā mēs saņemsim kļūdu, kas mums jau ir pazīstama:

{
   "@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
}

Rezultāti

Konkrēti Kestrel nevar pareizi apstrādāt HTTP galvenes ar pareizajām rakstzīmēm UTF-8, kuras ir ietvertas diezgan daudzu pilsētu nosaukumos.

Papildu faktors mūsu gadījumā ir tas, ka klients šobrīd neplāno mainīt Kestrel ieviešanu aplikācijā. Tomēr problēmas pašā AspNetCore (№ 4318, № 7707) viņi saka, ka tas nepalīdzēs...

Rezumējot: piezīme vairs nav par Kestrel vai UTF-8 specifiskajām problēmām (2019. gadā?!), bet gan par to, ka uzmanīgums un konsekventa izpēte Katrs solis, ko spersi, meklējot problēmas, agri vai vēlu nesīs augļus. Veiksmi!

PS

Lasi arī mūsu emuārā:

Avots: www.habr.com

Pievieno komentāru