Tinder transition sa Kubernetes

Tandaan. transl.: Ang mga empleyado ng sikat na serbisyo sa Tinder sa buong mundo ay nagbahagi kamakailan ng ilang teknikal na detalye ng paglipat ng kanilang imprastraktura sa Kubernetes. Ang proseso ay tumagal ng halos dalawang taon at nagresulta sa paglulunsad ng isang napakalaking platform sa K8s, na binubuo ng 200 mga serbisyo na naka-host sa 48 libong mga lalagyan. Anong mga kawili-wiling paghihirap ang naranasan ng mga inhinyero ng Tinder at anong mga resulta ang kanilang narating? Basahin ang pagsasaling ito.

Tinder transition sa Kubernetes

Bakit?

Halos dalawang taon na ang nakalipas, nagpasya ang Tinder na ilipat ang platform nito sa Kubernetes. Pahihintulutan ng Kubernetes ang koponan ng Tinder na maglagay ng lalagyan at lumipat sa produksyon na may kaunting pagsisikap sa pamamagitan ng hindi nababagong deployment (hindi nababagong deployment). Sa kasong ito, ang pagpupulong ng mga application, ang kanilang deployment, at ang mismong imprastraktura ay tiyak na tutukuyin ng code.

Naghahanap din kami ng solusyon sa problema ng scalability at stability. Kapag naging kritikal ang pag-scale, madalas kaming maghintay ng ilang minuto para umikot ang mga bagong instance ng EC2. Ang ideya ng paglulunsad ng mga lalagyan at pagsisimula ng paghahatid ng trapiko sa mga segundo sa halip na mga minuto ay naging lubhang kaakit-akit sa amin.

Mahirap pala ang proseso. Sa aming paglipat sa unang bahagi ng 2019, ang Kubernetes cluster ay umabot sa kritikal na masa at nagsimula kaming makaharap ng iba't ibang problema dahil sa dami ng trapiko, laki ng cluster, at DNS. Kasabay nito, nalutas namin ang maraming kawili-wiling problema na nauugnay sa paglipat ng 200 serbisyo at pagpapanatili ng isang Kubernetes cluster na binubuo ng 1000 node, 15000 pod at 48000 tumatakbong container.

Paano?

Mula noong Enero 2018, dumaan na tayo sa iba't ibang yugto ng migrasyon. Nagsimula kami sa pamamagitan ng pag-container ng lahat ng aming mga serbisyo at pag-deploy ng mga ito sa mga pagsubok sa cloud environment ng Kubernetes. Simula noong Oktubre, sinimulan naming i-migrate ang lahat ng kasalukuyang serbisyo sa Kubernetes. Pagsapit ng Marso ng sumunod na taon, natapos namin ang paglipat at ngayon ang platform ng Tinder ay tumatakbo nang eksklusibo sa Kubernetes.

Pagbuo ng mga larawan para sa Kubernetes

Mayroon kaming mahigit 30 source code repository para sa mga microservice na tumatakbo sa isang Kubernetes cluster. Ang code sa mga repositoryong ito ay nakasulat sa iba't ibang wika (halimbawa, Node.js, Java, Scala, Go) na may maraming runtime na kapaligiran para sa parehong wika.

Ang build system ay idinisenyo upang magbigay ng ganap na nako-customize na "build context" para sa bawat microservice. Karaniwan itong binubuo ng isang Dockerfile at isang listahan ng mga shell command. Ang kanilang nilalaman ay ganap na nako-customize, at kasabay nito, ang lahat ng mga build context na ito ay nakasulat ayon sa isang standardized na format. Ang pag-standardize ng mga konteksto ng build ay nagbibigay-daan sa isang solong build system na pangasiwaan ang lahat ng mga microservice.

Tinder transition sa Kubernetes
Larawan 1-1. Standardized na proseso ng build sa pamamagitan ng Builder container

Upang makamit ang maximum na pagkakapare-pareho sa pagitan ng mga runtime (mga runtime na kapaligiran) ang parehong proseso ng pagbuo ay ginagamit sa panahon ng pagbuo at pagsubok. Hinarap namin ang isang napaka-kagiliw-giliw na hamon: kailangan naming bumuo ng isang paraan upang matiyak ang pagkakapare-pareho ng build environment sa buong platform. Upang makamit ito, ang lahat ng mga proseso ng pagpupulong ay isinasagawa sa loob ng isang espesyal na lalagyan. Ang nagpapagawa.

Ang kanyang pagpapatupad ng container ay nangangailangan ng mga advanced na diskarte sa Docker. Namana ng Builder ang lokal na user ID at mga sikreto (tulad ng SSH key, mga kredensyal ng AWS, atbp.) na kinakailangan upang ma-access ang mga pribadong imbakan ng Tinder. Nag-mount ito ng mga lokal na direktoryo na naglalaman ng mga mapagkukunan upang natural na mag-imbak ng mga artifact ng build. Pinapabuti ng diskarteng ito ang pagganap dahil inaalis nito ang pangangailangang kumopya ng mga artifact ng build sa pagitan ng container ng Builder at ng host. Ang mga nakaimbak na artifact ng build ay maaaring magamit muli nang walang karagdagang configuration.

Para sa ilang serbisyo, kinailangan naming gumawa ng isa pang lalagyan upang imapa ang compilation environment sa runtime environment (halimbawa, ang Node.js bcrypt library ay bumubuo ng mga binary artifact na partikular sa platform habang nag-i-install). Sa panahon ng proseso ng compilation, maaaring mag-iba ang mga kinakailangan sa pagitan ng mga serbisyo, at ang panghuling Dockerfile ay naipon sa mabilisang.

Kubernetes cluster architecture at migration

Pamamahala ng laki ng kumpol

Nagpasya kaming gamitin kube-aws para sa awtomatikong pag-deploy ng cluster sa mga instance ng Amazon EC2. Sa pinakadulo simula, lahat ay gumana sa isang karaniwang pool ng mga node. Mabilis naming napagtanto ang pangangailangang paghiwalayin ang mga workload ayon sa laki at uri ng instance para mas mahusay na gumamit ng mga mapagkukunan. Ang lohika ay ang pagpapatakbo ng ilang load na multi-threaded pod ay naging mas predictable sa mga tuntunin ng performance kaysa sa kanilang coexistence sa isang malaking bilang ng single-threaded pods.

Sa huli, nagkasundo kami sa:

  • m5.4x malaki - para sa pagsubaybay (Prometheus);
  • c5.4xmalaki - para sa Node.js workload (single-threaded workload);
  • c5.2xmalaki - para sa Java at Go (multithreaded workload);
  • c5.4xmalaki β€” para sa control panel (3 node).

Paglilipat

Isa sa mga hakbang sa paghahanda para sa paglipat mula sa lumang imprastraktura patungo sa Kubernetes ay ang pag-redirect ng kasalukuyang direktang komunikasyon sa pagitan ng mga serbisyo sa mga bagong load balancer (Elastic Load Balancers (ELB). Nilikha ang mga ito sa isang partikular na subnet ng isang virtual private cloud (VPC). Ang subnet na ito ay konektado sa isang Kubernetes VPC. Nagbigay-daan ito sa amin na mag-migrate ng mga module nang unti-unti, nang hindi isinasaalang-alang ang partikular na pagkakasunud-sunod ng mga dependency ng serbisyo.

Ang mga endpoint na ito ay ginawa gamit ang mga weighted set ng DNS record na may mga CNAME na tumuturo sa bawat bagong ELB. Upang lumipat, nagdagdag kami ng bagong entry na tumuturo sa bagong ELB ng serbisyo ng Kubernetes na may timbang na 0. Pagkatapos ay itinakda namin ang Time To Live (TTL) ng entry na nakatakda sa 0. Pagkatapos nito, ang luma at bagong mga timbang ay dahan-dahang inayos, at kalaunan ay naipadala ang 100% ng load sa isang bagong server. Matapos makumpleto ang paglipat, ang halaga ng TTL ay bumalik sa isang mas sapat na antas.

Ang mga Java module na mayroon kami ay maaaring makayanan ang mababang TTL DNS, ngunit ang mga Node application ay hindi makayanan. Isinulat muli ng isa sa mga inhinyero ang bahagi ng code ng koneksyon sa pool at ibinalot ito sa isang manager na nag-a-update sa mga pool bawat 60 segundo. Ang napiling diskarte ay gumana nang mahusay at walang anumang kapansin-pansing pagkasira ng pagganap.

Ang mga aralin

Ang Mga Limitasyon ng Tela ng Network

Sa madaling araw ng Enero 8, 2019, ang platform ng Tinder ay hindi inaasahang bumagsak. Bilang tugon sa hindi nauugnay na pagtaas sa latency ng platform kaninang umaga, tumaas ang bilang ng mga pod at node sa cluster. Nagdulot ito ng pagkaubos ng cache ng ARP sa lahat ng aming mga node.

Mayroong tatlong mga opsyon sa Linux na nauugnay sa ARP cache:

Tinder transition sa Kubernetes
(pinagmulan)

gc_thresh3 - ito ay isang mahirap na limitasyon. Nangangahulugan ang paglitaw ng mga entry ng "neghbor table overflow" sa log na kahit na pagkatapos ng sabay-sabay na koleksyon ng basura (GC), walang sapat na espasyo sa cache ng ARP upang iimbak ang kalapit na entry. Sa kasong ito, ganap na itinapon ng kernel ang packet.

Gumagamit kami pranela bilang isang network fabric sa Kubernetes. Ang mga packet ay ipinapadala sa VXLAN. Ang VXLAN ay isang L2 tunnel na nakataas sa ibabaw ng isang L3 network. Ang teknolohiya ay gumagamit ng MAC-in-UDP (MAC Address-in-User Datagram Protocol) encapsulation at nagbibigay-daan sa pagpapalawak ng Layer 2 na mga segment ng network. Ang transport protocol sa pisikal na network ng data center ay IP plus UDP.

Tinder transition sa Kubernetes
Larawan 2–1. diagram ng flannel (pinagmulan)

Tinder transition sa Kubernetes
Larawan 2–2. VXLAN package (pinagmulan)

Ang bawat Kubernetes worker node ay naglalaan ng virtual address space na may /24 mask mula sa mas malaking /9 block. Para sa bawat node ito ay paraan isang entry sa routing table, isang entry sa ARP table (sa flannel.1 interface), at isang entry sa switching table (FDB). Idinaragdag ang mga ito sa unang pagkakataon na magsimula ang isang worker node o sa tuwing may natuklasang bagong node.

Bilang karagdagan, ang komunikasyon ng node-pod (o pod-pod) sa huli ay dumadaan sa interface eth0 (tulad ng ipinapakita sa diagram ng Flannel sa itaas). Nagreresulta ito sa karagdagang entry sa talahanayan ng ARP para sa bawat kaukulang source at destination host.

Sa ating kapaligiran, ang ganitong uri ng komunikasyon ay karaniwan. Para sa mga service object sa Kubernetes, isang ELB ang ginawa at nirerehistro ng Kubernetes ang bawat node sa ELB. Walang alam ang ELB tungkol sa mga pod at ang napiling node ay maaaring hindi ang huling destinasyon ng packet. Ang punto ay kapag ang isang node ay nakatanggap ng isang packet mula sa ELB, isinasaalang-alang nito ang pagsasaalang-alang sa mga patakaran iptables para sa isang partikular na serbisyo at random na pumipili ng pod sa isa pang node.

Sa oras ng pagkabigo, mayroong 605 node sa cluster. Para sa mga kadahilanang nabanggit sa itaas, ito ay sapat na upang mapagtagumpayan ang kahalagahan gc_thresh3, na siyang default. Kapag nangyari ito, hindi lang magsisimulang i-drop ang mga packet, ngunit ang buong Flannel na virtual address space na may /24 mask ay mawawala sa ARP table. Ang komunikasyon sa node-pod at mga query sa DNS ay naantala (Ang DNS ay naka-host sa isang cluster; basahin sa ibang pagkakataon sa artikulong ito para sa mga detalye).

Upang malutas ang problemang ito, kailangan mong dagdagan ang mga halaga gc_thresh1, gc_thresh2 ΠΈ gc_thresh3 at i-restart ang Flannel upang muling irehistro ang mga nawawalang network.

Hindi inaasahang DNS scaling

Sa panahon ng proseso ng paglipat, aktibong ginamit namin ang DNS upang pamahalaan ang trapiko at unti-unting ilipat ang mga serbisyo mula sa lumang imprastraktura patungo sa Kubernetes. Nagtakda kami ng medyo mababang halaga ng TTL para sa mga nauugnay na RecordSet sa Route53. Noong tumatakbo ang lumang imprastraktura sa mga instance ng EC2, itinuro ng aming configuration ng solver ang Amazon DNS. Isinasaalang-alang namin ito at ang epekto ng mababang TTL sa aming mga serbisyo at serbisyo ng Amazon (gaya ng DynamoDB) ay hindi napansin.

Habang nag-migrate kami ng mga serbisyo sa Kubernetes, nalaman namin na pinoproseso ng DNS ang 250 libong kahilingan kada segundo. Bilang resulta, nagsimulang makaranas ang mga application ng pare-pareho at seryosong pag-timeout para sa mga query sa DNS. Nangyari ito sa kabila ng hindi kapani-paniwalang pagsisikap na i-optimize at ilipat ang DNS provider sa CoreDNS (na sa peak load ay umabot sa 1000 pod na tumatakbo sa 120 core).

Habang nagsasaliksik ng iba pang posibleng dahilan at solusyon, natuklasan namin isang artikulo, na naglalarawan sa mga kondisyon ng lahi na nakakaapekto sa packet filtering framework netfilter sa Linux. Ang mga timeout na naobserbahan namin, kasama ng pagtaas ng counter insert_failed sa interface ng Flannel ay naaayon sa mga natuklasan ng artikulo.

Ang problema ay nangyayari sa yugto ng Source at Destination Network Address Translation (SNAT at DNAT) at kasunod na pagpasok sa talahanayan conntrack. Isa sa mga workaround na tinalakay sa loob at iminungkahi ng komunidad ay ang paglipat ng DNS sa worker node mismo. Sa kasong ito:

  • Hindi kailangan ang SNAT dahil nananatili ang trapiko sa loob ng node. Hindi ito kailangang i-ruta sa interface eth0.
  • Hindi kailangan ang DNAT dahil ang destination IP ay lokal sa node, at hindi random na napiling pod ayon sa mga panuntunan iptables.

Nagpasya kaming manatili sa diskarteng ito. Ang CoreDNS ay na-deploy bilang isang DaemonSet sa Kubernetes at nagpatupad kami ng isang lokal na node DNS server sa lutasin.conf bawat pod sa pamamagitan ng pagtatakda ng bandila --cluster-dns mga koponan kubeletβ€Š. Ang solusyon na ito ay naging epektibo para sa mga DNS timeout.

Gayunpaman, nakita pa rin namin ang pagkawala ng packet at pagtaas sa counter insert_failed sa interface ng Flannel. Nagpatuloy ito pagkatapos maipatupad ang workaround dahil nagawa naming alisin ang SNAT at/o DNAT para sa trapiko ng DNS lamang. Ang mga kondisyon ng karera ay napanatili para sa iba pang mga uri ng trapiko. Sa kabutihang palad, karamihan sa aming mga packet ay TCP, at kung magkaroon ng problema ay ipinapadala lamang ang mga ito. Sinusubukan pa rin naming maghanap ng angkop na solusyon para sa lahat ng uri ng trapiko.

Paggamit ng Envoy para sa Mas Mabuting Pagbalanse ng Load

Habang nag-migrate kami ng mga serbisyo ng backend sa Kubernetes, nagsimula kaming dumanas ng hindi balanseng pag-load sa pagitan ng mga pod. Nalaman namin na ang HTTP Keepalive ay nagdulot ng mga koneksyon sa ELB na mag-hang sa mga unang handa na pod ng bawat deployment na inilunsad. Kaya, ang karamihan ng trapiko ay dumaan sa maliit na porsyento ng mga available na pod. Ang unang solusyon na sinubukan namin ay ang pagtatakda ng MaxSurge sa 100% sa mga bagong deployment para sa pinakamasamang sitwasyon. Ang epekto ay naging hindi gaanong mahalaga at hindi kapani-paniwala sa mga tuntunin ng mas malalaking pag-deploy.

Ang isa pang solusyon na ginamit namin ay ang artipisyal na pagtaas ng mga kahilingan sa mapagkukunan para sa mga kritikal na serbisyo. Sa kasong ito, ang mga pod na nakalagay sa malapit ay magkakaroon ng mas maraming puwang upang maniobrahin kumpara sa iba pang mabibigat na pod. Hindi rin ito gagana sa katagalan dahil ito ay isang pag-aaksaya ng mga mapagkukunan. Bilang karagdagan, ang aming mga application ng Node ay single-threaded at, nang naaayon, maaari lamang gumamit ng isang core. Ang tanging tunay na solusyon ay ang paggamit ng mas mahusay na pagbabalanse ng pagkarga.

Matagal na nating gustong pahalagahan nang lubusan Sugo. Ang kasalukuyang sitwasyon ay nagpapahintulot sa amin na i-deploy ito sa napakalimitadong paraan at makakuha ng agarang resulta. Ang Envoy ay isang high-performance, open-source, layer-XNUMX proxy na idinisenyo para sa malalaking SOA application. Maaari itong magpatupad ng mga advanced na diskarte sa pagbalanse ng load, kabilang ang mga awtomatikong muling pagsubok, mga circuit breaker, at pandaigdigang paglilimita sa rate. (Tandaan. transl.: Maaari mong basahin ang higit pa tungkol dito sa artikulong ito tungkol kay Istio, na batay sa Envoy.)

Nakabuo kami ng sumusunod na configuration: magkaroon ng Envoy sidecar para sa bawat pod at isang ruta, at ikonekta ang cluster sa container nang lokal sa pamamagitan ng port. Para mabawasan ang potensyal na cascading at mapanatili ang maliit na hit radius, gumamit kami ng fleet ng Envoy front-proxy pod, isa sa bawat Availability Zone (AZ) para sa bawat serbisyo. Umasa sila sa isang simpleng service discovery engine na isinulat ng isa sa aming mga engineer na nagbalik lang ng listahan ng mga pod sa bawat AZ para sa isang partikular na serbisyo.

Pagkatapos ay ginamit ng Service front-Envoys ang mekanismo ng pagtuklas ng serbisyo na ito na may isang upstream cluster at ruta. Nagtakda kami ng sapat na mga timeout, pinataas ang lahat ng mga setting ng circuit breaker, at nagdagdag ng kaunting retry configuration upang makatulong sa mga solong pagkabigo at matiyak ang maayos na pag-deploy. Naglagay kami ng TCP ELB sa harap ng bawat isa sa mga front-Envoy ng serbisyo. Kahit na ang keepalive mula sa aming pangunahing proxy layer ay na-stuck sa ilang Envoy pod, nakaya pa rin nila ang pag-load nang mas mahusay at na-configure upang balansehin sa pamamagitan ng least_request sa backend.

Para sa pag-deploy, ginamit namin ang preStop hook sa parehong mga application pod at sidecar pod. Ang hook ay nag-trigger ng error sa pagsuri sa status ng admin endpoint na matatagpuan sa sidecar container at natulog nang ilang sandali upang payagan ang mga aktibong koneksyon na wakasan.

Isa sa mga dahilan kung bakit kami nakagalaw nang napakabilis ay dahil sa mga detalyadong sukatan na madali naming naisama sa isang karaniwang pag-install ng Prometheus. Nagbigay-daan ito sa amin na makita nang eksakto kung ano ang nangyayari habang inaayos namin ang mga parameter ng configuration at muling ipinamahagi ang trapiko.

Ang mga resulta ay agaran at halata. Nagsimula kami sa pinaka-hindi balanseng mga serbisyo, at sa sandaling ito ay tumatakbo sa harap ng 12 pinakamahalagang serbisyo sa cluster. Sa taong ito, nagpaplano kami ng paglipat sa isang buong service mesh na may mas advanced na pagtuklas ng serbisyo, circuit breaking, outlier detection, paglilimita sa rate at pagsubaybay.

Tinder transition sa Kubernetes
Larawan 3–1. CPU convergence ng isang serbisyo sa panahon ng paglipat sa Envoy

Tinder transition sa Kubernetes

Tinder transition sa Kubernetes

Pangwakas na resulta

Sa pamamagitan ng karanasang ito at karagdagang pananaliksik, nakabuo kami ng isang malakas na team ng imprastraktura na may malalakas na kasanayan sa pagdidisenyo, pag-deploy, at pagpapatakbo ng malalaking Kubernetes cluster. Ang lahat ng mga inhinyero ng Tinder ay mayroon na ngayong kaalaman at karanasan upang mag-package ng mga container at mag-deploy ng mga application sa Kubernetes.

Nang lumitaw ang pangangailangan para sa karagdagang kapasidad sa lumang imprastraktura, kinailangan naming maghintay ng ilang minuto para maglunsad ang mga bagong EC2 instance. Ngayon ang mga container ay nagsimulang tumakbo at magsisimulang magproseso ng trapiko sa loob ng ilang segundo sa halip na mga minuto. Ang pag-iskedyul ng maraming lalagyan sa iisang EC2 instance ay nagbibigay din ng pinahusay na pahalang na konsentrasyon. Bilang resulta, hinuhulaan namin ang isang makabuluhang pagbawas sa mga gastos sa EC2019 sa 2 kumpara sa nakaraang taon.

Ang paglipat ay tumagal ng halos dalawang taon, ngunit natapos namin ito noong Marso 2019. Sa kasalukuyan, ang platform ng Tinder ay eksklusibong tumatakbo sa isang Kubernetes cluster na binubuo ng 200 serbisyo, 1000 node, 15 pod at 000 tumatakbong container. Ang imprastraktura ay hindi na ang tanging domain ng mga operations team. Ibinabahagi ng lahat ng aming mga inhinyero ang responsibilidad na ito at kinokontrol ang proseso ng pagbuo at pag-deploy ng kanilang mga aplikasyon gamit lamang ang code.

PS mula sa tagasalin

Basahin din ang isang serye ng mga artikulo sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento