Pergerakan Tinder ke Kubernetes

Ditulis oleh: Chris O'Brien, Pengurus Kejuruteraan | Chris Thomas, Pengurus Kejuruteraan | Jinyong Lee, Jurutera Perisian Kanan | Disunting Oleh: Cooper Jackson, Jurutera Perisian

Mengapa

Hampir dua tahun yang lalu, Tinder memutuskan untuk memindahkan platformnya ke Kubernetes. Kubernetes memberi kita peluang untuk mendorong Tinder Engineering ke arah pengangkatan dan operasi sentuhan rendah melalui penyebaran yang tidak berubah. Pembuatan aplikasi, penyebaran, dan infrastruktur akan didefinisikan sebagai kod.

Kami juga berusaha menangani cabaran skala dan kestabilan. Apabila penskalaan menjadi kritikal, kami sering mengalami beberapa minit menunggu kemasukan EC2 baru dalam talian. Idea mengenai penjadualan kontena dan melayani lalu lintas dalam beberapa saat berbanding minit menarik bagi kami.

Ia tidak mudah. Semasa penghijrahan kami pada awal tahun 2019, kami mencapai jumlah kritikal dalam kelompok Kubernetes kami dan mula menghadapi pelbagai cabaran kerana jumlah lalu lintas, ukuran kluster, dan DNS. Kami menyelesaikan cabaran menarik untuk memindahkan 200 perkhidmatan dan menjalankan kluster Kubernetes pada skala berjumlah 1,000 nod, 15,000 pod, dan 48,000 kontena yang berjalan.

Bagaimana

Mulai Januari 2018, kami telah melalui pelbagai peringkat usaha penghijrahan. Kami bermula dengan menggunakan semua perkhidmatan kami dan menyebarkannya ke rangkaian persekitaran pementasan yang dihoskan oleh Kubernetes. Mulai Oktober, kami mula memindahkan semua perkhidmatan warisan kami secara berkala ke Kubernetes. Menjelang Mac tahun berikutnya, kami menyelesaikan penghijrahan kami dan Tinder Platform kini dijalankan secara eksklusif di Kubernetes.

Membina Imej untuk Kubernetes

Terdapat lebih daripada 30 repositori kod sumber untuk perkhidmatan mikro yang berjalan di kluster Kubernetes. Kod di repositori ini ditulis dalam bahasa yang berlainan (contohnya, Node.js, Java, Scala, Go) dengan pelbagai persekitaran runtime untuk bahasa yang sama.

Sistem build dirancang untuk beroperasi pada "konteks membangun" yang dapat disesuaikan sepenuhnya untuk setiap layanan mikro, yang biasanya terdiri dari Dockerfile dan serangkaian perintah shell. Walaupun kandungannya dapat disesuaikan sepenuhnya, konteks binaan ini semuanya ditulis dengan mengikuti format standard. Penyeragaman konteks binaan membolehkan sistem binaan tunggal mengendalikan semua perkhidmatan mikro.

Rajah 1–1 Proses binaan standard melalui wadah Builder

Untuk mencapai konsistensi maksimum antara lingkungan runtime, proses build yang sama digunakan selama fasa pengembangan dan pengujian. Ini memberikan cabaran yang unik ketika kita perlu mencari jalan untuk menjamin persekitaran pembinaan yang konsisten di seluruh platform. Akibatnya, semua proses binaan dilaksanakan di dalam wadah "Builder" khas.

Pelaksanaan wadah Builder memerlukan sejumlah teknik Docker yang maju. Wadah Builder ini mewarisi ID dan rahsia pengguna tempatan (contohnya, kunci SSH, kelayakan AWS, dll.) Seperti yang diperlukan untuk mengakses repositori peribadi Tinder. Ini memasang direktori tempatan yang mengandungi kod sumber untuk mempunyai cara semula jadi untuk menyimpan artifak binaan. Pendekatan ini meningkatkan prestasi, kerana menghilangkan penyalinan artifak yang dibina antara wadah Builder dan mesin induk. Artifak binaan yang disimpan akan digunakan semula pada masa akan datang tanpa konfigurasi lebih lanjut.

Untuk perkhidmatan tertentu, kami perlu membuat wadah lain di dalam Builder untuk memadankan persekitaran waktu kompilasi dengan persekitaran waktu larian (contohnya, memasang perpustakaan Node.js bcrypt menghasilkan artifak binari khusus platform). Keperluan masa kompilasi mungkin berbeza antara perkhidmatan dan Dockerfile terakhir disusun dengan cepat.

Senibina dan Penghijrahan Kluster Kubernetes

Ukuran Kluster

Kami memutuskan untuk menggunakan kube-aws untuk penyediaan kluster automatik pada kejadian EC2 Amazon. Sejak awal, kami menjalankan semuanya dalam satu kumpulan simpul umum. Kami dengan cepat mengenal pasti keperluan untuk memisahkan beban kerja ke dalam berbagai ukuran dan jenis keadaan, untuk menggunakan sumber yang lebih baik. Alasannya adalah bahawa menjalankan lebih banyak pod berulir bersama menghasilkan hasil prestasi yang lebih dapat diramalkan bagi kita daripada membiarkannya wujud bersama dengan bilangan pod single-thread yang lebih besar.

Kami menetap di:

  • m5.4xbesar untuk pemantauan (Prometheus)
  • c5.4xbesar untuk beban kerja Node.js (beban kerja utas tunggal)
  • c5.2xlarge untuk Java dan Go (beban kerja multi-utas)
  • c5.4xlengkap untuk satah kawalan (3 nod)

Penghijrahan

Salah satu langkah persiapan untuk penghijrahan dari infrastruktur lama kami ke Kubernetes adalah mengubah komunikasi perkhidmatan-ke-perkhidmatan yang ada untuk menunjuk ke Elastic Load Balancers (ELB) baru yang dibuat dalam subnet Virtual Private Cloud (VPC) tertentu. Subnet ini dikupas ke Kubernetes VPC. Ini membolehkan kami memindahkan modul secara terperinci tanpa mengambil kira pesanan khusus untuk kebergantungan perkhidmatan.

Titik akhir ini dibuat menggunakan set rekod DNS berwajaran yang mempunyai CNAME yang menunjuk ke setiap ELB baru. Untuk memotong, kami menambah rekod baru, menunjuk ELB perkhidmatan Kubernetes baru, dengan berat 0. Kami kemudian menetapkan Time To Live (TTL) pada rekod ditetapkan ke 0. Berat lama dan baru kemudian perlahan-lahan disesuaikan ke akhirnya berakhir dengan 100% pada pelayan baru. Setelah pemotongan selesai, TTL ditetapkan menjadi sesuatu yang lebih munasabah.

Modul Java kami menghormati TTL DNS rendah, tetapi aplikasi Node kami tidak. Salah seorang jurutera kami menulis semula sebahagian daripada kod kumpulan sambungan untuk memasukkannya ke dalam pengurus yang akan menyegarkan kolam setiap 60-an. Ini berfungsi dengan baik untuk kami tanpa prestasi yang cukup tinggi.

Pembelajaran

Had Fabrik Rangkaian

Pada awal pagi 8 Januari 2019, Tinder Platform mengalami gangguan berterusan. Sebagai tindak balas kepada peningkatan latensi platform yang tidak berkaitan pada awal pagi itu, jumlah pod dan nod dikira pada kluster. Ini mengakibatkan keletihan cache ARP di semua nod kami.

Terdapat tiga nilai Linux yang berkaitan dengan cache ARP:

Kredit

gc_thresh3 adalah topi keras. Sekiranya anda mendapat entri log "jadual luapan tetangga", ini menunjukkan bahawa walaupun selepas pengumpulan sampah (GC) sinkron ARP cache, tidak ada ruang yang cukup untuk menyimpan entri jiran. Dalam kes ini, kernel hanya menjatuhkan paket sepenuhnya.

Kami menggunakan Flannel sebagai kain rangkaian kami di Kubernetes. Paket dihantar melalui VXLAN. VXLAN adalah skema overlay Layer 2 melalui rangkaian Layer 3. Ia menggunakan enkapsulasi MAC Address-in-User Datagram Protocol (MAC-in-UDP) untuk menyediakan kaedah untuk memperluas segmen rangkaian Layer 2. Protokol pengangkutan melalui rangkaian pusat data fizikal adalah IP plus UDP.

Gambar 2-1 Gambar rajah flanel (kredit)

Gambar 2-2 Paket VXLAN (kredit)

Setiap node pekerja Kubernetes memperuntukkan ruang alamat maya / 24 sendiri daripada blok / 9 yang lebih besar. Untuk setiap simpul, ini menghasilkan 1 entri jadual laluan, 1 entri jadual ARP (pada antara muka flannel.1), dan 1 entri pangkalan data penerusan (FDB). Ini ditambahkan semasa simpul pekerja dilancarkan pertama kali atau ketika setiap nod baru ditemui.

Sebagai tambahan, komunikasi node-to-pod (atau pod-to-pod) akhirnya mengalir melalui antara muka eth0 (digambarkan dalam rajah Flannel di atas). Ini akan menghasilkan entri tambahan dalam jadual ARP untuk setiap sumber nod dan tujuan nod yang sesuai.

Di persekitaran kita, komunikasi jenis ini sangat biasa. Untuk objek perkhidmatan Kubernetes kami, ELB dibuat dan Kubernetes mendaftarkan setiap nod dengan ELB. ELB tidak sedar pod dan simpul yang dipilih mungkin bukan tujuan akhir paket. Ini kerana apabila node menerima paket dari ELB, ia menilai peraturan iptables untuk perkhidmatan dan memilih secara rawak pod pada nod lain.

Pada masa pemadaman, terdapat 605 jumlah nod dalam kelompok. Atas sebab-sebab yang dinyatakan di atas, ini cukup untuk menghilangkan nilai gc_thresh3 lalai. Sebaik sahaja ini berlaku, bukan sahaja paket yang dijatuhkan, tetapi keseluruhan ruang alamat maya Flannel / 24s hilang dari jadual ARP. Node ke pod komunikasi dan carian DNS gagal. (DNS dihoskan dalam kluster, seperti yang akan dijelaskan dengan lebih terperinci nanti dalam artikel ini.)

Untuk menyelesaikannya, nilai gc_thresh1, gc_thresh2, dan gc_thresh3 dinaikkan dan Flannel mesti dimulakan semula untuk mendaftarkan semula rangkaian yang hilang.

Menjalankan DNS Tanpa Skala

Untuk memenuhi penghijrahan kami, kami memanfaatkan DNS dengan banyak untuk memudahkan pembentukan lalu lintas dan pemotongan tambahan dari warisan ke Kubernetes untuk perkhidmatan kami. Kami menetapkan nilai TTL yang agak rendah pada Route53 RecordSets yang berkaitan. Semasa kami menjalankan infrastruktur lama kami pada contoh EC2, konfigurasi penyelesai kami menunjukkan DNS Amazon. Kami menganggap ini sebagai perkara biasa dan kos TTL yang agak rendah untuk perkhidmatan kami dan perkhidmatan Amazon (misalnya DynamoDB) sebahagian besarnya tidak disedari.

Ketika kami menaiki perkhidmatan semakin banyak ke Kubernetes, kami mendapati kami menjalankan perkhidmatan DNS yang menjawab 250,000 permintaan sesaat. Kami menghadapi masa tunggu DNS yang sekejap-sekejap dan berdampak dalam aplikasi kami. Ini terjadi walaupun ada upaya penalaan yang menyeluruh dan penyedia DNS beralih ke penyebaran CoreDNS yang pada satu masa memuncak pada 1.000 buah yang memakan 120 teras.

Semasa meneliti kemungkinan penyebab dan penyelesaian lain, kami menemui sebuah artikel yang menggambarkan keadaan perlumbaan yang mempengaruhi netfilter rangka kerja penyaringan paket Linux. Waktu tunggu DNS yang kami lihat, bersama dengan penghitung insert_failed yang bertambah pada antara muka Flannel, sejajar dengan penemuan artikel.

Masalahnya berlaku semasa Penterjemahan Alamat Rangkaian Sumber dan Destinasi (SNAT dan DNAT) dan penyisipan seterusnya ke dalam jadual sambung. Satu penyelesaian yang dibincangkan secara dalaman dan dicadangkan oleh masyarakat adalah memindahkan DNS ke simpul pekerja itu sendiri. Dalam kes ini:

  • SNAT tidak diperlukan, kerana trafik berada di simpul secara tempatan. Ia tidak perlu dihantar ke antara muka eth0.
  • DNAT tidak diperlukan kerana IP tujuan adalah lokal ke nod dan bukan peraturan pod per iptables yang dipilih secara rawak.

Kami memutuskan untuk terus maju dengan pendekatan ini. CoreDNS dikerahkan sebagai DaemonSet di Kubernetes dan kami menyuntik pelayan DNS tempatan node ke dalam resolv.conf setiap pod dengan mengkonfigurasi bendera perintah kubelet - cluster-dns. Penyelesaiannya berkesan untuk tamat masa DNS.

Walau bagaimanapun, kami masih melihat paket yang dijatuhkan dan kenaikan kaunter insert_failed antara muka Flannel. Ini akan berterusan walaupun selepas penyelesaian di atas kerana kami hanya mengelakkan SNAT dan / atau DNAT untuk trafik DNS. Keadaan perlumbaan masih akan berlaku untuk jenis lalu lintas yang lain. Nasib baik, kebanyakan paket kami adalah TCP dan apabila keadaan berlaku, paket akan berjaya dihantar semula. Pembaikan jangka panjang untuk semua jenis lalu lintas adalah perkara yang masih kita bincangkan.

Menggunakan Utusan Untuk Mencapai Pengimbangan Beban Yang Lebih Baik

Semasa kami memindahkan perkhidmatan backend kami ke Kubernetes, kami mula mengalami beban yang tidak seimbang di seluruh kumpulan. Kami mendapati bahawa kerana HTTP Keepalive, sambungan ELB tersekat pada pod siap pertama dari setiap penggelaran, sehingga kebanyakan lalu lintas mengalir melalui peratusan kecil dari pod yang ada. Salah satu mitigasi pertama yang kami cuba adalah menggunakan MaxSurge 100% pada penyebaran baru untuk pelaku yang paling teruk. Ini adalah jangka panjang yang efektif dan tidak berkesinambungan dengan beberapa penyebaran yang lebih besar.

Mitigasi lain yang kami gunakan adalah untuk meningkatkan permintaan sumber secara artifisial pada perkhidmatan kritikal sehingga pod colocated akan mempunyai lebih banyak ruang kepala bersama dengan pod berat lain. Ini juga tidak akan dapat dilaksanakan dalam jangka masa panjang kerana pembaziran sumber dan aplikasi Node kami adalah satu utas dan dengan demikian dibatasi pada 1 teras. Satu-satunya penyelesaian yang jelas adalah menggunakan pengimbangan beban yang lebih baik.

Kami secara dalaman telah berusaha untuk menilai Envoy. Ini memberi kami peluang untuk menyebarkannya dengan cara yang sangat terhad dan memperoleh faedah segera. Envoy adalah sumber terbuka, proksi Layer 7 berprestasi tinggi yang direka untuk seni bina berorientasikan perkhidmatan yang besar. Ia dapat menerapkan teknik pengimbangan beban lanjutan, termasuk percubaan semula automatik, pemutus litar, dan pembatasan kadar global.

Konfigurasi yang kami buat adalah dengan mempunyai sidecar Envoy di samping setiap pod yang mempunyai satu laluan dan kluster untuk memukul port kontena tempatan. Untuk meminimumkan potensi lata dan untuk menjaga radius ledakan kecil, kami menggunakan armada Envoy proxy depan, satu penyebaran di setiap Zona Ketersediaan (AZ) untuk setiap perkhidmatan. Ini memenuhi mekanisme penemuan perkhidmatan kecil yang disusun oleh salah seorang jurutera kami yang hanya mengembalikan senarai pod di setiap AZ untuk perkhidmatan tertentu.

Utusan depan perkhidmatan kemudian menggunakan mekanisme penemuan perkhidmatan ini dengan satu kelompok dan laluan hulu. Kami mengatur masa tamat yang munasabah, meningkatkan semua tetapan pemutus litar, dan kemudian memasukkan konfigurasi percubaan minimum untuk membantu kegagalan sementara dan penyebaran lancar. Kami mengetuai setiap perkhidmatan Envoy depan ini dengan TCP ELB. Walaupun penyimpan dari lapisan proksi utama utama kami disematkan pada pod Envoy tertentu, mereka lebih mampu menangani beban dan dikonfigurasi untuk mengimbangkan melalui sekurang-kurangnya permintaan ke bahagian belakang.

Untuk penyebaran, kami menggunakan kait praStop pada aplikasi dan pod sidecar. Cangkuk ini yang disebut pemeriksaan kesihatan sidecar gagal titik akhir admin, bersama dengan tidur kecil, untuk memberi sedikit masa untuk membolehkan sambungan penerbangan selesai dan menguras.

Salah satu sebab kami dapat bergerak begitu cepat adalah kerana metrik yang kaya sehingga kami dapat menyatu dengan mudah dengan persediaan Prometheus biasa kami. Ini membolehkan kami melihat dengan tepat apa yang sedang berlaku semasa kami mengulangi tetapan konfigurasi dan mengurangkan lalu lintas.

Hasilnya segera dan jelas. Kami bermula dengan perkhidmatan yang paling tidak seimbang dan pada masa ini ia beroperasi di hadapan dua belas perkhidmatan yang paling penting dalam kluster kami. Tahun ini kami merancang untuk beralih ke jaringan perkhidmatan penuh, dengan penemuan perkhidmatan yang lebih maju, pemecahan litar, pengesanan outlier, pembatasan kadar, dan penelusuran.

Gambar 3-1 Penumpuan CPU satu perkhidmatan semasa pemotongan ke utusan

Hasil Akhir

Melalui pembelajaran dan penyelidikan tambahan ini, kami telah mengembangkan pasukan infrastruktur dalaman yang kuat dan sangat memahami cara merancang, menggunakan, dan mengendalikan kelompok Kubernetes yang besar. Keseluruhan organisasi kejuruteraan Tinder kini mempunyai pengetahuan dan pengalaman tentang cara mengemas dan menggunakan aplikasi mereka di Kubernetes.

Pada infrastruktur lama kami, apabila diperlukan skala tambahan, kami sering mengalami beberapa minit menunggu kemasukan EC2 baru dalam talian. Kontena kini menjadualkan dan melayani lalu lintas dalam beberapa saat berbanding minit. Penjadualan beberapa bekas pada satu contoh EC2 juga memberikan kepadatan mendatar yang lebih baik. Hasilnya, kami memproyeksikan penjimatan kos yang besar pada EC2 pada tahun 2019 berbanding tahun sebelumnya.

Ia mengambil masa hampir dua tahun, tetapi kami menyelesaikan penghijrahan kami pada bulan Mac 2019. Platform Tinder berjalan secara eksklusif pada kluster Kubernetes yang terdiri daripada 200 perkhidmatan, 1,000 nod, 15,000 pod, dan 48,000 kontena yang berjalan. Infrastruktur bukan lagi tugas yang dikhaskan untuk pasukan operasi kami. Sebaliknya, jurutera di seluruh organisasi berkongsi tanggungjawab ini dan mempunyai kawalan terhadap bagaimana aplikasi mereka dibina dan digunakan dengan semua kod.