Transisi Tinder ke Kubernetes

Catatan. terjemahan: Karyawan layanan Tinder yang terkenal di dunia baru-baru ini membagikan beberapa detail teknis tentang migrasi infrastruktur mereka ke Kubernetes. Prosesnya memakan waktu hampir dua tahun dan menghasilkan peluncuran platform berskala sangat besar di K8, yang terdiri dari 200 layanan yang dihosting di 48 ribu kontainer. Kesulitan menarik apa yang dihadapi para insinyur Tinder dan apa hasil yang mereka peroleh? Baca terjemahan ini.

Transisi Tinder ke Kubernetes

Kenapa?

Hampir dua tahun lalu, Tinder memutuskan untuk memindahkan platformnya ke Kubernetes. Kubernetes akan memungkinkan tim Tinder untuk melakukan container dan beralih ke produksi dengan upaya minimal melalui penerapan yang tidak dapat diubah (penyebaran yang tidak dapat diubah). Dalam hal ini, perakitan aplikasi, penerapannya, dan infrastruktur itu sendiri akan ditentukan secara unik oleh kode.

Kami juga mencari solusi untuk masalah skalabilitas dan stabilitas. Ketika penskalaan menjadi penting, kami sering kali harus menunggu beberapa menit agar instans EC2 baru dapat dijalankan. Gagasan untuk meluncurkan kontainer dan mulai melayani lalu lintas dalam hitungan detik, bukan menit, menjadi sangat menarik bagi kami.

Ternyata prosesnya sulit. Selama migrasi kami di awal tahun 2019, cluster Kubernetes mencapai masa kritis dan kami mulai menghadapi berbagai masalah karena volume lalu lintas, ukuran cluster, dan DNS. Dalam prosesnya, kami memecahkan banyak masalah menarik terkait migrasi 200 layanan dan pemeliharaan cluster Kubernetes yang terdiri dari 1000 node, 15000 pod, dan 48000 container yang sedang berjalan.

Bagaimana?

Sejak Januari 2018, kami telah melalui berbagai tahapan migrasi. Kami memulai dengan memasukkan semua layanan kami ke dalam container dan menerapkannya ke lingkungan cloud pengujian Kubernetes. Mulai bulan Oktober, kami mulai memigrasikan semua layanan yang ada ke Kubernetes secara metodis. Pada bulan Maret tahun berikutnya, kami menyelesaikan migrasi dan sekarang platform Tinder berjalan secara eksklusif di Kubernetes.

Membangun image untuk Kubernetes

Kami memiliki lebih dari 30 repositori kode sumber untuk layanan mikro yang berjalan di cluster Kubernetes. Kode dalam repositori ini ditulis dalam bahasa berbeda (misalnya, Node.js, Java, Scala, Go) dengan beberapa lingkungan runtime untuk bahasa yang sama.

Sistem pembangunan dirancang untuk menyediakan “konteks pembangunan” yang sepenuhnya dapat disesuaikan untuk setiap layanan mikro. Biasanya terdiri dari Dockerfile dan daftar perintah shell. Konten mereka sepenuhnya dapat disesuaikan, dan pada saat yang sama, semua konteks bangunan ini ditulis berdasarkan format standar. Standarisasi konteks build memungkinkan satu sistem build untuk menangani semua layanan mikro.

Transisi Tinder ke Kubernetes
Gambar 1-1. Proses pembangunan standar melalui wadah Builder

Untuk mencapai konsistensi maksimum antar runtime (lingkungan waktu proses) proses pembangunan yang sama digunakan selama pengembangan dan pengujian. Kami menghadapi tantangan yang sangat menarik: kami harus mengembangkan cara untuk memastikan konsistensi lingkungan pembangunan di seluruh platform. Untuk mencapai hal tersebut, semua proses perakitan dilakukan di dalam wadah khusus. Pembangun.

Implementasi containernya memerlukan teknik Docker tingkat lanjut. Builder mewarisi ID pengguna lokal dan rahasia (seperti kunci SSH, kredensial AWS, dll.) yang diperlukan untuk mengakses repositori Tinder pribadi. Itu memasang direktori lokal yang berisi sumber untuk menyimpan artefak bangunan secara alami. Pendekatan ini meningkatkan kinerja karena menghilangkan kebutuhan untuk menyalin artefak build antara kontainer Builder dan host. Artefak bangunan yang disimpan dapat digunakan kembali tanpa konfigurasi tambahan.

Untuk beberapa layanan, kami harus membuat wadah lain untuk memetakan lingkungan kompilasi ke lingkungan runtime (misalnya, perpustakaan bcrypt Node.js menghasilkan artefak biner khusus platform selama instalasi). Selama proses kompilasi, persyaratan dapat bervariasi antar layanan, dan Dockerfile akhir dikompilasi dengan cepat.

Arsitektur dan migrasi cluster Kubernetes

Manajemen ukuran cluster

Kami memutuskan untuk menggunakan kube-aws untuk penerapan klaster otomatis pada instans Amazon EC2. Pada awalnya, semuanya bekerja dalam satu kumpulan node yang sama. Kami segera menyadari perlunya memisahkan beban kerja berdasarkan ukuran dan jenis instans untuk membuat penggunaan sumber daya lebih efisien. Logikanya adalah menjalankan beberapa pod multi-thread yang dimuat ternyata lebih dapat diprediksi dalam hal performa dibandingkan menjalankannya secara berdampingan dengan sejumlah besar pod single-threaded.

Pada akhirnya kami memutuskan:

  • m5.4xbesar — untuk pemantauan (Prometheus);
  • c5.4xbesar - untuk beban kerja Node.js (beban kerja single-thread);
  • c5.2xbesar - untuk Java dan Go (beban kerja multithread);
  • c5.4xbesar — untuk panel kontrol (3 node).

Migrasi

Salah satu langkah persiapan untuk migrasi dari infrastruktur lama ke Kubernetes adalah dengan mengalihkan komunikasi langsung yang ada antar layanan ke penyeimbang beban baru (Elastic Load Balancer (ELB). Mereka dibuat pada subnet tertentu dari virtual private cloud (VPC). Subnet ini terhubung ke VPC Kubernetes. Hal ini memungkinkan kami untuk memigrasikan modul secara bertahap, tanpa mempertimbangkan urutan spesifik ketergantungan layanan.

Titik akhir ini dibuat menggunakan kumpulan data DNS berbobot yang memiliki CNAME yang menunjuk ke setiap ELB baru. Untuk beralih, kami menambahkan entri baru yang menunjuk ke ELB baru dari layanan Kubernetes dengan bobot 0. Kami kemudian menetapkan Time To Live (TTL) dari entri tersebut ke 0. Setelah ini, bobot lama dan baru adalah disesuaikan secara perlahan, dan akhirnya 100% beban dikirim ke server baru. Setelah peralihan selesai, nilai TTL kembali ke tingkat yang lebih memadai.

Modul Java yang kami miliki dapat mengatasi DNS TTL rendah, tetapi aplikasi Node tidak dapat melakukannya. Salah satu insinyur menulis ulang bagian dari kode kumpulan koneksi dan membungkusnya dalam manajer yang memperbarui kumpulan setiap 60 detik. Pendekatan yang dipilih bekerja dengan sangat baik dan tanpa penurunan kinerja yang nyata.

Pelajaran

Batasan Jaringan Jaringan

Dini hari tanggal 8 Januari 2019, platform Tinder tiba-tiba mogok. Menanggapi peningkatan latensi platform yang tidak terkait pada pagi hari itu, jumlah pod dan node di cluster meningkat. Hal ini menyebabkan cache ARP habis di semua node kami.

Ada tiga opsi Linux yang terkait dengan cache ARP:

Transisi Tinder ke Kubernetes
(sumber)

gc_thresh3 - ini adalah batas yang sulit. Munculnya entri “tabel tetangga overflow” di log berarti bahwa bahkan setelah pengumpulan sampah sinkron (GC), tidak ada cukup ruang di cache ARP untuk menyimpan entri tetangga. Dalam hal ini, kernel membuang paket tersebut sepenuhnya.

Kita gunakan Flanel sebagai struktur jaringan di Kubernetes. Paket ditransmisikan melalui VXLAN. VXLAN adalah terowongan L2 yang dibangun di atas jaringan L3. Teknologi ini menggunakan enkapsulasi MAC-in-UDP (MAC Address-in-User Datagram Protocol) dan memungkinkan perluasan segmen jaringan Layer 2. Protokol transport pada jaringan pusat data fisik adalah IP plus UDP.

Transisi Tinder ke Kubernetes
Gambar 2–1. Diagram flanel (sumber)

Transisi Tinder ke Kubernetes
Gambar 2-2. Paket VXLAN (sumber)

Setiap node pekerja Kubernetes mengalokasikan ruang alamat virtual dengan mask /24 dari blok /9 yang lebih besar. Untuk setiap node ini adalah sarana satu entri di tabel routing, satu entri di tabel ARP (pada antarmuka flanel.1), dan satu entri di tabel switching (FDB). Mereka ditambahkan saat pertama kali node pekerja dimulai atau setiap kali node baru ditemukan.

Selain itu, komunikasi node-pod (atau pod-pod) pada akhirnya melewati antarmuka eth0 (seperti yang ditunjukkan pada diagram Flanel di atas). Hal ini menghasilkan entri tambahan dalam tabel ARP untuk setiap host sumber dan tujuan yang sesuai.

Di lingkungan kita, komunikasi seperti ini sangat umum terjadi. Untuk objek layanan di Kubernetes, ELB dibuat dan Kubernetes mendaftarkan setiap node ke ELB. ELB tidak mengetahui apa pun tentang pod dan node yang dipilih mungkin bukan tujuan akhir paket. Intinya adalah ketika sebuah node menerima paket dari ELB, ia mempertimbangkannya dengan mempertimbangkan aturan iptables untuk layanan tertentu dan secara acak memilih pod di node lain.

Pada saat kegagalan terjadi, ada 605 node di cluster. Karena alasan-alasan yang disebutkan di atas, hal ini sudah cukup untuk mengatasi signifikansinya gc_thresh3, yang merupakan defaultnya. Ketika hal ini terjadi, tidak hanya paket yang mulai dibuang, namun seluruh ruang alamat virtual Flannel dengan mask /24 menghilang dari tabel ARP. Komunikasi node-pod dan kueri DNS terganggu (DNS dihosting di sebuah cluster; baca nanti di artikel ini untuk detailnya).

Untuk mengatasi masalah ini, Anda perlu meningkatkan nilainya gc_thresh1, gc_thresh2 и gc_thresh3 dan mulai ulang Flanel untuk mendaftarkan ulang jaringan yang hilang.

Penskalaan DNS yang tidak terduga

Selama proses migrasi, kami secara aktif menggunakan DNS untuk mengatur lalu lintas dan secara bertahap mentransfer layanan dari infrastruktur lama ke Kubernetes. Kami menetapkan nilai TTL yang relatif rendah untuk RecordSet terkait di Route53. Ketika infrastruktur lama berjalan pada instans EC2, konfigurasi penyelesai kami mengarah ke Amazon DNS. Kami menganggap hal ini sebagai hal yang wajar dan dampak TTL yang rendah pada layanan kami dan layanan Amazon (seperti DynamoDB) sebagian besar tidak diperhatikan.

Saat kami memigrasikan layanan ke Kubernetes, kami menemukan bahwa DNS memproses 250 ribu permintaan per detik. Akibatnya, aplikasi mulai mengalami waktu tunggu yang konstan dan serius untuk kueri DNS. Hal ini terjadi meskipun ada upaya luar biasa untuk mengoptimalkan dan mengalihkan penyedia DNS ke CoreDNS (yang pada beban puncak mencapai 1000 pod yang berjalan pada 120 core).

Saat meneliti kemungkinan penyebab dan solusi lain, kami menemukan sebuah artikel, menjelaskan kondisi balapan yang memengaruhi kerangka pemfilteran paket netfilter di Linux. Batas waktu yang kami amati, ditambah dengan penghitung yang semakin meningkat masukkan_gagal di antarmuka Flanel konsisten dengan temuan artikel.

Masalah terjadi pada tahap Penerjemahan Alamat Jaringan Sumber dan Tujuan (SNAT dan DNAT) dan selanjutnya masuk ke dalam tabel conntrack. Salah satu solusi yang dibahas secara internal dan disarankan oleh komunitas adalah memindahkan DNS ke node pekerja itu sendiri. Pada kasus ini:

  • SNAT tidak diperlukan karena lalu lintas tetap berada di dalam node. Itu tidak perlu dirutekan melalui antarmuka eth0.
  • DNAT tidak diperlukan karena IP tujuan bersifat lokal pada node, dan bukan pod yang dipilih secara acak sesuai aturan iptables.

Kami memutuskan untuk tetap menggunakan pendekatan ini. CoreDNS diterapkan sebagai DaemonSet di Kubernetes dan kami mengimplementasikan server DNS node lokal di dalamnya penyelesaian.conf setiap pod dengan menyetel bendera --cluster-dns tim kubus . Solusi ini ternyata efektif untuk waktu tunggu DNS.

Namun, kami masih melihat kehilangan paket dan peningkatan penghitung masukkan_gagal di antarmuka Flanel. Hal ini berlanjut setelah solusi diterapkan karena kami dapat menghilangkan SNAT dan/atau DNAT hanya untuk lalu lintas DNS. Kondisi balapan dipertahankan untuk jenis lalu lintas lainnya. Untungnya, sebagian besar paket kami adalah TCP, dan jika terjadi masalah, paket tersebut akan dikirim ulang. Kami masih berusaha mencari solusi yang cocok untuk semua jenis lalu lintas.

Menggunakan Utusan untuk Penyeimbangan Beban yang Lebih Baik

Saat kami memigrasikan layanan backend ke Kubernetes, kami mulai mengalami beban yang tidak seimbang antar pod. Kami menemukan bahwa HTTP Keepalive menyebabkan koneksi ELB terhenti pada pod siap pakai pertama dari setiap penerapan yang diluncurkan. Oleh karena itu, sebagian besar lalu lintas melewati sebagian kecil pod yang tersedia. Solusi pertama yang kami uji adalah menyetel MaxSurge ke 100% pada penerapan baru untuk skenario terburuk. Dampaknya ternyata tidak signifikan dan tidak menjanjikan dalam hal penerapan yang lebih besar.

Solusi lain yang kami gunakan adalah meningkatkan permintaan sumber daya untuk layanan penting secara artifisial. Dalam hal ini, pod yang ditempatkan di dekatnya akan memiliki lebih banyak ruang untuk bermanuver dibandingkan dengan pod berat lainnya. Hal ini juga tidak akan berhasil dalam jangka panjang karena akan membuang-buang sumber daya. Selain itu, aplikasi Node kami berulir tunggal dan, karenanya, hanya dapat menggunakan satu inti. Satu-satunya solusi nyata adalah menggunakan penyeimbangan beban yang lebih baik.

Kami sudah lama ingin mengapresiasi sepenuhnya Utusan. Situasi saat ini memungkinkan kami menerapkannya dengan cara yang sangat terbatas dan mendapatkan hasil langsung. Envoy adalah proksi lapisan-XNUMX sumber terbuka dan berkinerja tinggi yang dirancang untuk aplikasi SOA besar. Ini dapat menerapkan teknik penyeimbangan beban tingkat lanjut, termasuk percobaan ulang otomatis, pemutus sirkuit, dan pembatasan kecepatan global. (Catatan. terjemahan: Anda dapat membaca lebih lanjut tentang ini di Artikel ini tentang Istio, yang didasarkan pada Envoy.)

Kami membuat konfigurasi berikut: memiliki sespan Envoy untuk setiap pod dan satu rute, dan menghubungkan cluster ke container secara lokal melalui port. Untuk meminimalkan potensi cascading dan mempertahankan radius serangan yang kecil, kami menggunakan armada pod proxy depan Envoy, satu per Availability Zone (AZ) untuk setiap layanan. Mereka mengandalkan mesin penemuan layanan sederhana yang ditulis oleh salah satu teknisi kami yang hanya mengembalikan daftar pod di setiap AZ untuk layanan tertentu.

Utusan depan layanan kemudian menggunakan mekanisme penemuan layanan ini dengan satu cluster dan rute hulu. Kami menetapkan batas waktu yang memadai, meningkatkan semua pengaturan pemutus sirkuit, dan menambahkan konfigurasi percobaan ulang minimal untuk membantu mengatasi kegagalan tunggal dan memastikan penerapan lancar. Kami menempatkan TCP ELB di depan masing-masing Utusan depan layanan ini. Bahkan jika keepalive dari lapisan proxy utama kita tertahan di beberapa pod Envoy, mereka masih mampu menangani beban dengan lebih baik dan dikonfigurasi untuk menyeimbangkan melalui less_request di backend.

Untuk penerapan, kami menggunakan hook preStop pada pod aplikasi dan pod sidecar. Pengait memicu kesalahan dalam memeriksa status titik akhir admin yang terletak di wadah sespan dan tertidur selama beberapa saat untuk memungkinkan koneksi aktif dihentikan.

Salah satu alasan kami dapat bergerak begitu cepat adalah karena metrik terperinci yang dapat kami integrasikan dengan mudah ke dalam instalasi Prometheus pada umumnya. Hal ini memungkinkan kami melihat apa yang sebenarnya terjadi saat kami menyesuaikan parameter konfigurasi dan mendistribusikan ulang lalu lintas.

Hasilnya langsung terlihat dan nyata. Kami memulai dengan layanan yang paling tidak seimbang, dan saat ini layanan tersebut beroperasi di depan 12 layanan terpenting dalam cluster. Tahun ini kami merencanakan transisi ke layanan penuh dengan penemuan layanan yang lebih canggih, pemutusan sirkuit, deteksi outlier, pembatasan laju, dan penelusuran.

Transisi Tinder ke Kubernetes
Gambar 3–1. Konvergensi CPU dari satu layanan selama transisi ke Envoy

Transisi Tinder ke Kubernetes

Transisi Tinder ke Kubernetes

Hasil akhir

Melalui pengalaman dan penelitian tambahan ini, kami telah membangun tim infrastruktur yang kuat dengan keterampilan yang kuat dalam merancang, menerapkan, dan mengoperasikan cluster Kubernetes yang besar. Semua teknisi Tinder kini memiliki pengetahuan dan pengalaman untuk mengemas container dan menerapkan aplikasi ke Kubernetes.

Ketika kebutuhan akan kapasitas tambahan muncul pada infrastruktur lama, kami harus menunggu beberapa menit hingga instans EC2 baru diluncurkan. Kini kontainer mulai berjalan dan mulai memproses lalu lintas dalam hitungan detik, bukan menit. Menjadwalkan beberapa kontainer pada satu instans EC2 juga memberikan peningkatan konsentrasi horizontal. Hasilnya, kami memperkirakan adanya penurunan biaya EC2019 yang signifikan pada tahun 2 dibandingkan tahun lalu.

Migrasi ini memakan waktu hampir dua tahun, namun kami menyelesaikannya pada Maret 2019. Saat ini, platform Tinder berjalan secara eksklusif pada cluster Kubernetes yang terdiri dari 200 layanan, 1000 node, 15 pod, dan 000 container yang sedang berjalan. Infrastruktur tidak lagi menjadi satu-satunya domain tim operasi. Semua teknisi kami berbagi tanggung jawab ini dan mengontrol proses pembuatan dan penerapan aplikasi mereka hanya dengan menggunakan kode.

PS dari penerjemah

Baca juga rangkaian artikel di blog kami:

Sumber: www.habr.com

Tambah komentar