Ang paglipat ni Tinder sa Kubernetes

Sinulat Ni: Chris O'Brien, Tagapamahala ng Teknolohiya | Chris Thomas, Tagapamahala ng Teknolohiya | Jinyong Lee, Senior Software Engineer | Na-edit Ni: Cooper Jackson, Software Engineer

Bakit

Halos dalawang taon na ang nakalilipas, nagpasya si Tinder na ilipat ang platform nito sa Kubernetes. Kubernetes binigyan kami ng isang pagkakataon upang himukin ang Tinder Engineering patungo sa containerization at low-touch na operasyon sa pamamagitan ng hindi mababago na pag-deploy. Ang pagtatayo ng aplikasyon, paglawak, at imprastraktura ay matutukoy bilang code.

Kami ay naghahanap din upang matugunan ang mga hamon ng scale at katatagan. Kapag naging kritikal ang pag-scale, madalas kaming nagdusa sa maraming minuto ng paghihintay para sa mga bagong pagkakataon sa EC2 na dumating sa online. Ang ideya ng pag-iiskedyul ng mga lalagyan at paghahatid ng trapiko sa loob ng ilang segundo kumpara sa mga minuto ay nakakaakit sa amin.

Hindi ito madali. Sa panahon ng aming paglipat sa unang bahagi ng 2019, nakarating kami sa kritikal na masa sa loob ng aming kumpol ng Kubernetes at nagsimulang makaharap ng iba't ibang mga hamon dahil sa dami ng trapiko, laki ng kumpol, at DNS. Nalutas namin ang mga kagiliw-giliw na mga hamon upang lumipat ng 200 mga serbisyo at magpatakbo ng isang Kubernetes kumpol sa sukat na umabot sa 1,000 node, 15,000 pods, at 48,000 tumatakbo na mga lalagyan.

Paano

Simula Enero 2018, nagtrabaho kami sa iba't ibang yugto ng pagsisikap ng paglipat. Sinimulan namin sa pamamagitan ng paglalagyan ng lahat ng aming mga serbisyo at inilalabas ang mga ito sa isang serye ng mga Kubernetes na naka-host sa mga kapaligiran ng staging. Simula ng Oktubre, sinimulan namin ang paglipat ng lahat ng aming mga serbisyo sa pamana sa Kubernetes. Pagsapit ng Marso sa susunod na taon, natapos namin ang aming paglipat at ang Tinder Platform ay tumatakbo nang eksklusibo sa Kubernetes.

Mga gusali ng Mga Larawan para sa Kubernetes

Mayroong higit sa 30 mapagkukunan code ng repositori para sa microservice na tumatakbo sa Kubernetes cluster. Ang code sa mga repositori na ito ay nakasulat sa iba't ibang mga wika (halimbawa, Node.js, Java, Scala, Go) na may maraming mga kapaligiran ng pag-runtime para sa parehong wika.

Ang sistema ng build ay dinisenyo upang gumana sa isang ganap na napapasadyang "build context" para sa bawat microservice, na karaniwang binubuo ng isang Dockerfile at isang serye ng mga utos ng shell. Habang ang kanilang mga nilalaman ay ganap na napapasadya, ang mga kontekstong bumubuo ay ang lahat ay nakasulat sa pamamagitan ng pagsunod sa isang pamantayang format. Ang standardization ng mga konteksto ng build ay nagbibigay-daan sa isang solong sistema ng pagbuo upang hawakan ang lahat ng mga microservice.

Larawan 1-1 Ang pamantayan sa proseso ng pagbuo sa pamamagitan ng lalagyan ng Tagabuo

Upang makamit ang maximum na pagkakapareho sa pagitan ng mga runtime environment, ang parehong proseso ng pagtatayo ay ginagamit sa yugto ng pag-unlad at pagsubok. Ito ay nagpapataw ng isang natatanging hamon kapag kailangan namin upang lumikha ng isang paraan upang masiguro ang isang pare-pareho na kapaligiran ng build sa buong platform. Bilang isang resulta, ang lahat ng mga proseso ng pagtatayo ay isinasagawa sa loob ng isang espesyal na lalagyan ng "Tagabuo".

Ang pagpapatupad ng lalagyan ng Tagabuo ay nangangailangan ng isang bilang ng mga advanced na diskarte sa pantalan. Ang lalagyan ng Tagabuo na ito ay nagmamana ng mga lokal na ID ng gumagamit at mga lihim (halimbawa, SSH key, mga kredensyal ng AWS, atbp.) Bilang kinakailangan upang ma-access ang Tinder pribadong repositori. Inilalagay nito ang mga lokal na direktoryo na naglalaman ng source code upang magkaroon ng isang natural na paraan upang mag-imbak ng mga artifact. Ang pamamaraang ito ay nagpapabuti sa pagganap, dahil tinanggal nito ang pagkopya ng mga built artifact sa pagitan ng lalagyan ng Tagabuo at ang machine ng host. Ang mga nakaimbak na build artifact ay muling ginamit sa susunod na walang karagdagang pagsasaayos.

Para sa ilang mga serbisyo, kailangan naming lumikha ng isa pang lalagyan sa loob ng Tagabuo upang tumugma sa compile-time na kapaligiran na may run-time na kapaligiran (halimbawa, ang pag-install ng library ng Node.js bcrypt na bumubuo ng platform na tiyak na binuong artifact). Ang mga kinakailangan sa compile-time ay maaaring magkakaiba sa mga serbisyo at ang panghuling Dockerfile ay binubuo sa fly.

Kubernetes Cluster Architecture At Migration

Cluster Sizing

Kami ay nagpasya na gumamit ng kube-aws para sa automated na kumpol na nagbibigay ng kumpol sa mga pagkakataon sa Amazon EC2. Maaga pa, pinapatakbo namin ang lahat sa isang pangkalahatang node pool. Mabilis naming natukoy ang pangangailangang paghiwalayin ang mga karga sa trabaho sa iba't ibang laki at uri ng mga pagkakataon, upang mas mahusay na magamit ang mga mapagkukunan. Ang pangangatwiran ay ang pagpapatakbo ng mas kaunting mga mabibigat na sinulid na mga pods na magkasama ay nagbunga ng mas mahuhulaan na mga resulta ng pagganap para sa amin kaysa sa pagpapaalam sa kanila na magkakasama sa isang mas malaking bilang ng mga solong may sinulid na pods.

Nag-ayos kami sa:

  • m5.4xlarge para sa pagsubaybay (Prometheus)
  • c5.4xlarge para sa Node.js workload (single-threaded workload)
  • c5.2xlarge para sa Java at Go (multi-threaded workload)
  • c5.4xlarge para sa control eroplano (3 node)

Paglilipat

Ang isa sa mga hakbang sa paghahanda para sa paglipat mula sa aming imprastraktura ng legacy patungong Kubernetes ay ang pagbabago ng umiiral na komunikasyon sa serbisyo sa serbisyo upang ituro sa bagong Elastic Load Balancers (ELBs) na nilikha sa isang tiyak na Virtual Private Cloud (VPC) subnet. Ang subnet na ito ay nailipat sa Kubernetes VPC. Pinapayagan kami nitong lumipat ng mga module na walang pagsasaalang-alang sa tiyak na pag-order para sa mga dependency ng serbisyo.

Ang mga endpoint na ito ay nilikha gamit ang may bigat na mga set ng record ng DNS na mayroong CNAME na tumuturo sa bawat bagong ELB. Upang cutover, nagdagdag kami ng isang bagong tala, na tumuturo sa bagong Kubernetes service ELB, na may bigat na 0. Pagkatapos ay itinakda namin ang Time To Live (TTL) sa talaan na itinakda sa 0. Ang luma at bagong mga timbang ay dahan-dahang naayos sa sa huli ay nagtatapos sa 100% sa bagong server. Matapos makumpleto ang cutover, ang TTL ay naitakda sa isang bagay na mas makatwiran.

Pinarangalan ng aming mga module ng Java ang mababang DNS TTL, ngunit hindi nagawa ang aming mga aplikasyon ng Node. Ang isa sa aming mga inhinyero ay muling nagsulat ng bahagi ng code ng koneksyon ng pool upang balutin ito sa isang tagapamahala na mai-refresh ang mga pool tuwing 60s. Nagtrabaho ito nang napakahusay para sa amin na walang naaaprubahang hit sa pagganap.

Mga Pag-aaral

Mga Limitasyon ng Tela sa Network

Sa mga aga aga ng Enero 8, 2019, ang Plataporma ng Tinder ay nagdusa ng isang paulit-ulit na pag-agos. Bilang tugon sa isang hindi magkakaugnay na pagtaas sa latency ng platform mas maaga sa umaga, ang mga bilang ng pod at node ay na-scale sa kumpol. Nagdulot ito ng ARP cache pagkapagod sa lahat ng aming mga node.

Mayroong tatlong mga halaga ng Linux na nauugnay sa ARP cache:

Kredito

gc_thresh3 ay isang hard cap. Kung nakakakuha ka ng "mga talahanayan ng kapitbahay na umaapaw" ng mga entry sa log, ipinapahiwatig nito na kahit na pagkatapos ng isang magkakasabay na koleksyon ng basura (GC) ng ARP cache, walang sapat na silid upang maiimbak ang pagpasok sa kapitbahay. Sa pagkakataong ito, ibinabagsak lamang ng kernel ang buong packet.

Ginagamit namin ang Flannel bilang aming tela ng network sa Kubernetes. Ipasa ang mga packet sa pamamagitan ng VXLAN. Ang VXLAN ay isang scheme ng overlay ng Layer 2 sa isang Layer 3 network. Gumagamit ito ng MAC Address-in-User Datagram Protocol (MAC-in-UDP) encapsulation upang magbigay ng isang paraan upang mapalawak ang mga segment ng network Layer 2. Ang transport protocol sa network ng data ng pisikal na data ay ang IP kasama ang UDP.

Larawan 2-11 diagram ng flannel (credit)

Larawan 2–2 VXLAN Packet (credit)

Ang bawat manggagawa sa Kubernetes ay naka-allocates ng sarili / 24 ng virtual address space sa labas ng isang mas malaki / 9 block. Para sa bawat node, nagreresulta ito sa 1 entry table table, 1 ARP table entry (sa flannel.1 interface), at 1 pagpasa ng database (FDB) entry. Idinagdag ang mga ito kapag unang naglunsad ang node ng manggagawa o bilang bawat bagong node ay natuklasan.

Bilang karagdagan, ang komunikasyon ng node-to-pod (o pod-to-pod) sa huli ay dumadaloy sa interface ng et0 (na inilalarawan sa diagram ng Flannel sa itaas). Ito ay magreresulta sa isang karagdagang pagpasok sa talahanayan ng ARP para sa bawat kaukulang pinagkukunan ng node at patutunguhan ng node.

Sa ating kapaligiran, ang ganitong uri ng komunikasyon ay pangkaraniwan. Para sa aming mga bagay na serbisyo sa Kubernetes, isang ELB ay nilikha at ang Kubernetes ay nagrerehistro sa bawat node kasama ang ELB. Ang ELB ay walang kamalayan at ang napiling node ay maaaring hindi huling destinasyon ng packet. Ito ay dahil kapag natanggap ng node ang packet mula sa ELB, sinusuri nito ang mga panuntunan ng iptable nito para sa serbisyo at sapalarang pumili ng isang pod sa isa pang node.

Sa oras ng pag-agos, mayroong 605 kabuuang mga node sa kumpol. Para sa mga kadahilanang nakabalangkas sa itaas, ito ay sapat na upang mag-eclipse ang default na halaga ng gc_thresh3. Sa sandaling mangyari ito, hindi lamang mga packet ang ibinaba, ngunit ang buong Flannel / 24s ng virtual address space ay nawawala mula sa talahanayan ng ARP. Ang komunikasyon sa node sa pod at mabigo ang mga lookup ng DNS. (Ang DNS ay naka-host sa loob ng kumpol, tulad ng ipapaliwanag sa mas detalyadong paglaon sa artikulong ito.)

Upang malutas, ang mga halaga ng gc_thresh1, gc_thresh2, at gc_thresh3 ay itataas at dapat na ma-restart si Flannel upang muling magrehistro ng mga nawawalang mga network.

Hindi inaasahang Pagpapatakbo ng DNS Sa Scale

Upang mapaunlakan ang aming paglipat, kami ay lubos na ginamit ang DNS upang mapadali ang paghuhulma ng trapiko at pagtaas ng cutover mula sa pamana sa Kubernetes para sa aming mga serbisyo. Itinakda namin ang medyo mababang mga halaga ng TTL sa nauugnay na Route53 RecordSets. Kapag pinatakbo namin ang aming mga imprastraktura ng legacy sa mga pagkakataon ng EC2, ang aming resolver ng pagsasaayos ay itinuro sa DNS ng Amazon. Kinuha namin ito para sa ipinagkaloob at ang gastos ng isang medyo mababang TTL para sa aming mga serbisyo at serbisyo ng Amazon (eg DynamoDB) ay napunta sa kalakhang hindi napansin.

Habang papunta kami ng mas maraming serbisyo sa Kubernetes, natagpuan namin ang aming sarili na nagpapatakbo ng serbisyo ng DNS na sumasagot sa 250,000 mga kahilingan sa bawat segundo. Nakatagpo kami ng mga walang tigil at nakakaapekto sa oras ng paghahanap ng DNS sa loob ng aming mga aplikasyon. Nangyari ito sa kabila ng isang labis na pagsisikap sa pag-tune at isang tagapagbigay ng DNS na lumipat sa isang paglawak ng CoreDNS na sa isang oras ay tumagas sa 1,000 pods na kumukulang ng 120 mga cores.

Habang nagsasaliksik ng iba pang posibleng mga sanhi at solusyon, nakita namin ang isang artikulo na naglalarawan ng isang kondisyon ng lahi na nakakaapekto sa Linux packet filtering framework netfilter. Ang oras ng DNS na nakikita namin, kasama ang isang pagtaas ng insert_failed counter sa interface ng Flannel, na nakahanay sa mga natuklasan ng artikulo.

Ang isyu ay nangyayari sa panahon ng Source and Destination Network Address Translation (SNAT at DNAT) at kasunod na pagpasok sa talahanayan ng conntrack. Ang isang pinagtatrabahuhan na tinalakay sa loob at iminungkahi ng komunidad ay upang ilipat ang DNS sa manggagawa mismo. Sa kasong ito:

  • Hindi kinakailangan ang SNAT, dahil ang trapiko ay nananatiling lokal sa node. Hindi ito kailangang maipadala sa buong interface ng eth0.
  • Hindi kinakailangan ang DNAT dahil ang patutunguhan na IP ay lokal sa node at hindi isang random na napiling pod bawat iptables na mga patakaran.

Napagpasyahan naming sumulong sa pamamaraang ito. Ang CoreDNS ay na-deploy bilang isang DaemonSet sa Kubernetes at iniksyon namin ang lokal na DNS server ng node sa resolv.conf ng bawat pod sa pamamagitan ng pag-configure ng kubelet - cluster-dns command flag. Ang workaround ay epektibo para sa mga oras ng DNS.

Gayunpaman, nakikita pa rin namin ang mga bumagsak na packet at ang pagsingit ng interface ng insert ng Flannel interface. Ito ay magpapatuloy kahit na matapos ang workaround sa itaas dahil iniiwasan lamang namin ang SNAT at / o DNAT para sa trapiko ng DNS. Ang kalagayan ng lahi ay magaganap pa rin para sa iba pang uri ng trapiko. Sa kabutihang palad, ang karamihan sa aming mga packet ay TCP at kapag nangyari ang kondisyon, ang mga packet ay matagumpay na muling maibalik. Ang isang pang-matagalang pag-aayos para sa lahat ng uri ng trapiko ay isang bagay na pinag-uusapan pa rin natin.

Paggamit ng Envoy Upang Makamit ang Mas mahusay na Pagbalanse ng Load

Habang inililipat namin ang aming mga serbisyo ng backend sa Kubernetes, nagsimula kaming maghirap mula sa hindi balanseng pag-load sa buong pods. Natuklasan namin na dahil sa HTTP Keepalive, ang mga koneksyon ng ELB ay natigil sa unang handa na mga pods sa bawat pag-deploy ng pag-deploy, kaya't ang karamihan sa trapiko ay dumaloy sa isang maliit na porsyento ng mga magagamit na mga pods. Ang isa sa mga unang mitigasyon na sinubukan namin ay ang paggamit ng isang 100% MaxSurge sa mga bagong pag-deploy para sa mga pinakamasamang nagkasala. Ito ay epektibo sa marginally at hindi napapanatiling pangmatagalan sa ilan sa mga mas malaking paglawak.

Ang isa pang pag-iwas na ginamit namin ay ang artipisyal na pagbubuhos ng mga kahilingan sa mapagkukunan sa mga kritikal na serbisyo upang ang mga kolektadong pods ay magkakaroon ng higit pang headroom sa tabi ng iba pang mga mabibigat na pods. Hindi rin ito magiging matatag sa katagalan dahil sa pag-aaksaya ng mapagkukunan at ang aming mga aplikasyon ng Node ay nag-iisang sinulid at sa gayon mabisang nakulong sa 1 core. Ang tanging malinaw na solusyon ay upang magamit ang mas mahusay na pagbabalanse ng pag-load.

Panloob kaming naghahanap upang suriin si Envoy. Ito ay nagbigay sa amin ng isang pagkakataon upang maipalawak ito sa isang napaka-limitadong fashion at umani ng agarang benepisyo. Ang Envoy ay isang bukas na mapagkukunan, mataas na pagganap na Layer 7 na proxy na idinisenyo para sa mga malalaking arkitekturang nakatuon sa serbisyo. Nagagawa nitong ipatupad ang mga advanced na diskarte sa pagbabalanse ng pag-load, kabilang ang mga awtomatikong retry, circuit breaking, at global rate na naglilimita.

Ang pagsasaayos na nakamit namin ay ang magkaroon ng isang Envoy sidecar sa tabi ng bawat pod na mayroong isang ruta at kumpol upang pindutin ang lokal na port ng lalagyan. Upang mabawasan ang mga potensyal na pag-cascading at upang mapanatili ang isang maliit na radius ng putok, ginamit namin ang isang fleet ng front-proxy Envoy pods, isang paglawak sa bawat Availability Zone (AZ) para sa bawat serbisyo. Ang mga ito ay tumama sa isang maliit na mekanismo ng pagtuklas ng serbisyo na pinagsama ng isa sa aming mga inhinyero na ibinalik lamang ang isang listahan ng mga pod sa bawat AZ para sa isang naibigay na serbisyo.

Ang service front-Envoys pagkatapos ay ginamit ang mekanismong ito sa mekanismo ng pagtuklas ng serbisyo sa isang upstream na kumpol at ruta. Na-configure namin ang mga makatwirang oras ng pag-timeout, pinalakas ang lahat ng mga setting ng circuit breaker, at pagkatapos ay ilagay sa isang minimal na pagsasaayos ng retry upang matulungan ang mga pansamantalang pagkabigo at makinis na pag-deploy. Inuna namin ang bawat isa sa mga harap na serbisyo ng Envoy na may isang TCP ELB. Kahit na ang pag-iingat mula sa aming pangunahing harap na proxy layer ay naka-pin sa ilang mga Envoy pods, mas mahusay nilang mapanghawakan ang pag-load at na-configure upang balansehin sa pamamagitan ng hindi bababa sa backend.

Para sa mga pag-deploy, ginamit namin ang isang preStop hook sa parehong application at ang sidecar pod. Ang kawit na ito na tinawag na sidecar health check mabigo ang pagtatapos ng admin, kasama ang isang maliit na pagtulog, upang mabigyan ng kaunting oras upang pahintulutan ang mga koneksyon sa inflight na makumpleto at alisan ng tubig.

Ang isang kadahilanan ay mabilis kaming lumipat ay dahil sa mayamang mga sukatan na nagawa naming madaling maisama sa aming normal na pag-setup ng Prometheus. Pinapayagan kaming makita ito nang eksakto kung ano ang nangyayari habang nag-iwas kami sa mga setting ng pagsasaayos at pinutol ang trapiko.

Agad at halata ang mga resulta. Nagsimula kami sa mga pinaka-hindi balanseng serbisyo at sa puntong ito pinapatakbo ito sa harap ng labindalawang labing mahalagang mga serbisyo sa aming kumpol. Sa taong ito pinaplano namin ang paglipat sa isang buong serbisyo ng mesh, na may mas advanced na serbisyo ng pagtuklas, circuit breaking, outlier detection, rate na naglilimita, at pagsubaybay.

Figure 3-1 na pag-iipon ng CPU ng isang serbisyo sa panahon ng paggupit sa envoy

Ang Resulta ng Wakas

Sa pamamagitan ng mga pag-aaral at karagdagang pananaliksik na ito, nakabuo kami ng isang malakas na koponan sa imprastruktura ng bahay na may mahusay na pamilyar sa kung paano magdisenyo, lumawak, at patakbuhin ang mga malalaking kumpol ng Kubernetes. Ang buong samahan ng Tinder ay mayroon na ngayong kaalaman at karanasan sa kung paano ilagyan at isakatuparan ang kanilang mga aplikasyon sa Kubernetes.

Sa aming imprastraktura ng legacy, kung kinakailangan ang karagdagang sukat, madalas kaming nagdusa ng ilang minuto ng paghihintay para sa mga bagong pagkakataon sa EC2 na dumating sa online. Ang mga lalagyan ay naka-iskedyul at naghahatid ng trapiko sa loob ng ilang segundo kumpara sa mga minuto. Ang pag-iskedyul ng maramihang mga lalagyan sa isang solong halimbawa ng EC2 ay nagbibigay din ng pinabuting pahalang na density. Bilang isang resulta, nag-proyekto kami ng malaking pagtitipid sa gastos sa EC2 noong 2019 kumpara sa nakaraang taon.

Tumagal ng halos dalawang taon, ngunit natapos namin ang aming paglipat noong Marso 2019. Ang Tinder Platform ay tumatakbo nang eksklusibo sa isang Kubernetes cluster na binubuo ng 200 mga serbisyo, 1,000 node, 15,000 pods, at 48,000 tumatakbo na mga lalagyan. Ang imprastraktura ay hindi na isang gawain na nakalaan para sa aming mga koponan sa operasyon. Sa halip, ang mga inhinyero sa buong samahan ay nakikibahagi sa responsibilidad na ito at may kontrol sa kung paano binuo ang kanilang mga aplikasyon at ipinadala sa lahat ng bagay bilang code.