Bagaimana arsitektur web toleran-kesalahan diimplementasikan di platform Mail.ru Cloud Solutions



Halo, Habr! Saya Artyom Karamyshev, kepala tim administrasi sistem di Mail.Ru Cloud Solutions (MCS) . Selama setahun terakhir, kami telah meluncurkan banyak produk baru. Kami ingin layanan API untuk skala dengan mudah, toleran terhadap kesalahan, dan siap untuk peningkatan beban pengguna yang cepat. Platform kami diimplementasikan pada OpenStack, dan saya ingin memberi tahu Anda apa masalah toleransi kesalahan komponen yang harus kami tutup untuk mendapatkan sistem toleran kesalahan. Saya pikir ini akan menarik bagi mereka yang juga mengembangkan produk di OpenStack.

Toleransi kesalahan keseluruhan platform terdiri dari stabilitas komponennya. Jadi kita akan secara bertahap melewati semua level di mana kita menemukan risiko dan menutupnya.

Versi video dari cerita ini, sumber aslinya yang merupakan laporan di konferensi Uptime day 4 yang diselenggarakan oleh ITSumma , dapat dilihat di saluran YouTube Uptime Community .

Toleransi kesalahan arsitektur fisik


Bagian publik cloud MCS sekarang berbasis di dua pusat data Tier III, di antaranya ada serat gelapnya sendiri, dicadangkan pada lapisan fisik dengan rute yang berbeda, dengan throughput 200 Gb / s. Tingkat Tier III menyediakan tingkat ketahanan infrastruktur fisik yang diperlukan.

Serat gelap dicadangkan di level fisik dan logis. Proses reservasi saluran berulang, masalah muncul, dan kami terus meningkatkan komunikasi antara pusat data.

Misalnya, belum lama ini, ketika bekerja di sumur di sebelah salah satu pusat data, sebuah excavator meninju pipa, di dalam pipa ini ada kabel optik utama dan cadangan. Saluran komunikasi kami yang toleran terhadap kesalahan dengan pusat data ternyata rentan pada satu titik, di sumur. Karenanya, kami kehilangan beberapa infrastruktur. Kami membuat kesimpulan, mengambil sejumlah tindakan, termasuk meletakkan optik tambahan di sepanjang sumur tetangga.

Di pusat data ada titik-titik keberadaan penyedia komunikasi tempat kami menyiarkan awalan kami melalui BGP. Untuk setiap arah jaringan, metrik terbaik dipilih, yang memungkinkan pelanggan yang berbeda memberikan kualitas koneksi terbaik. Jika komunikasi melalui satu penyedia terputus, kami membangun kembali perutean kami melalui penyedia yang tersedia.

Jika terjadi kegagalan penyedia, kami secara otomatis beralih ke yang berikutnya. Dalam hal kegagalan salah satu pusat data, kami memiliki salinan cermin dari layanan kami di pusat data kedua, yang menanggung semua beban sendiri.


Ketahanan infrastruktur fisik

Apa yang kami gunakan untuk toleransi kesalahan tingkat aplikasi


Layanan kami dibangun di atas sejumlah komponen opensource.

ExaBGP adalah layanan yang mengimplementasikan sejumlah fungsi menggunakan protokol routing dinamis berdasarkan BGP. Kami secara aktif menggunakannya untuk mengumumkan alamat IP putih kami di mana pengguna mendapatkan akses ke API.

HAProxy adalah penyeimbang yang sarat muatan yang memungkinkan Anda mengonfigurasi aturan yang sangat fleksibel untuk menyeimbangkan lalu lintas di berbagai tingkat model OSI. Kami menggunakannya untuk menyeimbangkan semua layanan: database, pialang pesan, layanan API, layanan web, proyek internal kami - semuanya ada di belakang HAProxy.

Aplikasi API - aplikasi web yang ditulis dengan python, yang dengannya pengguna mengontrol infrastrukturnya, layanannya.

Aplikasi pekerja (selanjutnya hanya disebut sebagai pekerja) - dalam layanan OpenStack ini adalah daemon infrastruktur yang memungkinkan Anda untuk menerjemahkan perintah API ke infrastruktur. Misalnya, disk dibuat di pekerja, dan permintaan untuk pembuatan ada di API aplikasi.

Arsitektur Aplikasi OpenStack Standar


Sebagian besar layanan yang dikembangkan untuk OpenStack mencoba mengikuti paradigma tunggal. Layanan biasanya terdiri dari 2 bagian: API dan pekerja (pelaksana backend). Biasanya, API adalah aplikasi WSGI python yang berjalan baik sebagai proses mandiri (daemon) atau menggunakan server web Nginx yang sudah jadi, Apache. API memproses permintaan pengguna dan meneruskan instruksi lebih lanjut ke aplikasi pekerja. Transmisi terjadi menggunakan broker pesan, biasanya RabbitMQ, sisanya kurang didukung. Ketika pesan sampai ke broker, mereka diproses oleh pekerja dan, jika perlu, kembalikan respons.

Paradigma ini menyiratkan titik kegagalan umum yang terisolasi: RabbitMQ dan database. Tetapi RabbitMQ terisolasi dalam satu layanan dan, secara teori, dapat bersifat individual untuk setiap layanan. Jadi kami di MCS membagikan layanan ini sebanyak mungkin, untuk setiap proyek kami membuat basis data terpisah, sebuah RabbitMQ terpisah. Pendekatan ini baik karena jika terjadi kecelakaan di beberapa titik rentan, tidak semua layanan rusak, tetapi hanya sebagian saja.

Jumlah aplikasi pekerja tidak terbatas, sehingga API dapat dengan mudah mengukur secara horizontal di belakang penyeimbang untuk meningkatkan produktivitas dan toleransi kesalahan.

Beberapa layanan memerlukan koordinasi dalam layanan - ketika operasi berurutan yang kompleks terjadi antara API dan pekerja. Dalam hal ini, pusat koordinasi tunggal digunakan, sistem cluster seperti Redis, Memcache, dll, yang memungkinkan satu pekerja untuk memberi tahu pekerja lain bahwa tugas ini diberikan kepadanya ("tolong jangan bawa"). Kami menggunakan etcd. Sebagai aturan, pekerja aktif berkomunikasi dengan database, menulis dan membaca informasi dari sana. Sebagai database, kami menggunakan mariadb, yang kami miliki di cluster multimaster.

Layanan pengguna tunggal klasik semacam itu diatur dengan cara yang diterima secara umum untuk OpenStack. Ini dapat dianggap sebagai sistem tertutup, yang metode penskalaan dan toleransi kesalahan cukup jelas. Misalnya, untuk toleransi kesalahan dari API, cukup menempatkan penyeimbang di depannya. Penskalaan pekerja dicapai dengan meningkatkan jumlah mereka.

Titik lemah dalam keseluruhan skema adalah RabbitMQ dan MariaDB. Arsitektur mereka layak mendapatkan artikel yang terpisah. Dalam artikel ini saya ingin fokus pada toleransi kesalahan API.


Arsitektur Aplikasi Openstack Perimbangan dan ketahanan platform cloud

Membuat HAProxy Balancer Tangguh dengan ExaBGP


Untuk membuat API kami dapat diskalakan, cepat, dan toleran terhadap kesalahan, kami menetapkan penyeimbang di depannya. Kami memilih HAProxy. Menurut pendapat saya, ini memiliki semua karakteristik yang diperlukan untuk tugas kami: menyeimbangkan pada beberapa tingkat OSI, antarmuka manajemen, fleksibilitas dan skalabilitas, sejumlah besar metode penyeimbangan, dukungan untuk tabel sesi.

Masalah pertama yang perlu dipecahkan adalah toleransi kesalahan penyeimbang itu sendiri. Hanya dengan memasang penyeimbang juga menciptakan titik kegagalan: penyeimbang istirahat - layanan turun. Untuk mencegah hal ini, kami menggunakan HAProxy bersama dengan ExaBGP.

ExaBGP memungkinkan Anda menerapkan mekanisme untuk memeriksa status suatu layanan. Kami menggunakan mekanisme ini untuk memeriksa fungsionalitas HAProxy dan jika ada masalah menonaktifkan layanan HAProxy dari BGP.

Skema ExaBGP + HAProxy

  1. Kami menginstal perangkat lunak yang diperlukan pada tiga server, ExaBGP dan HAProxy.
  2. Di setiap server kami membuat antarmuka loopback.
  3. Di ketiga server, kami menetapkan alamat IP putih yang sama untuk antarmuka ini.
  4. Alamat IP putih diumumkan di Internet melalui ExaBGP.

Toleransi kesalahan dicapai dengan mengumumkan alamat IP yang sama dari ketiga server. Dari sudut pandang jaringan, alamat yang sama dapat diakses dari tiga harapan berikutnya yang berbeda. Router melihat tiga rute yang identik, memilih prioritas yang paling tinggi menurut metriknya sendiri (ini biasanya pilihan yang sama), dan lalu lintas hanya menuju ke salah satu server.

Jika terjadi masalah dengan operasi HAProxy atau kegagalan server, ExaBGP berhenti mengumumkan rute, dan lalu lintas dengan lancar beralih ke server lain.

Dengan demikian, kami telah mencapai toleransi kesalahan penyeimbang.


Toleransi kesalahan penyeimbang HAProxy

Skema itu ternyata tidak sempurna: kami belajar cara memesan HAProxy, tetapi tidak belajar bagaimana mendistribusikan beban di dalam layanan. Oleh karena itu, kami sedikit memperluas skema ini: kami beralih ke menyeimbangkan antara beberapa alamat IP putih.

BGP Berbasis Balancing Plus DNS


Masalah keseimbangan beban sebelum HAProxy kami tetap tidak terselesaikan. Namun demikian, itu dapat diselesaikan dengan cukup sederhana, seperti yang kami lakukan di rumah.

Untuk menyeimbangkan ketiga server, Anda akan membutuhkan 3 alamat IP putih dan DNS lama yang baik. Masing-masing alamat ini didefinisikan pada antarmuka loopback masing-masing HAProxy dan diumumkan di Internet.

OpenStack menggunakan katalog layanan untuk mengelola sumber daya, yang menetapkan API titik akhir layanan. Dalam direktori ini kami meresepkan nama domain - public.infra.mail.ru, yang diselesaikan melalui DNS dengan tiga alamat IP yang berbeda. Akibatnya, kami mendapatkan penyeimbangan beban antara tiga alamat melalui DNS.

Tetapi karena ketika mengumumkan alamat IP putih, kami tidak mengontrol prioritas pemilihan server, sejauh ini ini tidak seimbang. Sebagai aturan, hanya satu server akan dipilih dengan didahulukan dari alamat IP, dan dua lainnya akan menganggur, karena tidak ada metrik yang ditentukan dalam BGP.

Kami mulai memberikan rute melalui ExaBGP dengan metrik yang berbeda. Setiap penyeimbang mengumumkan ketiga alamat IP putih, tetapi salah satunya, yang utama untuk penyeimbang ini, diumumkan dengan metrik minimum. Jadi sementara ketiga penyeimbang beroperasi, panggilan ke alamat IP pertama jatuh pada penyeimbang pertama, panggilan ke yang kedua ke yang kedua, ke yang ketiga ke yang ketiga.

Apa yang terjadi ketika salah satu penyeimbang jatuh? Dalam hal kegagalan penyeimbang berdasarkan basisnya, alamat masih diumumkan dari dua penyeimbang lainnya, lalu lintas di antara mereka didistribusikan kembali. Dengan demikian, kami memberikan kepada pengguna melalui DNS beberapa alamat IP sekaligus. Dengan menyeimbangkan DNS dan metrik yang berbeda, kami mendapatkan distribusi beban yang seragam pada ketiga penyeimbang. Dan pada saat yang sama kita tidak kehilangan toleransi kesalahan.


HAProxy Balancing Berdasarkan DNS + BGP

Interaksi antara ExaBGP dan HAProxy


Jadi, kami menerapkan toleransi kesalahan jika server pergi, berdasarkan penghentian pengumuman rute. Tetapi HAProxy juga dapat terputus karena alasan lain selain kegagalan server: kesalahan administrasi, kegagalan layanan. Kami ingin menghapus penyeimbang yang rusak dari bawah beban dan dalam kasus ini, dan kami membutuhkan mekanisme lain.

Oleh karena itu, memperluas skema sebelumnya, kami menerapkan detak jantung antara ExaBGP dan HAProxy. Ini adalah implementasi perangkat lunak dari interaksi antara ExaBGP dan HAProxy, ketika ExaBGP menggunakan skrip khusus untuk memeriksa status aplikasi.

Untuk melakukan ini, dalam konfigurasi ExaBGP, Anda harus mengonfigurasi pemeriksa kesehatan yang dapat memeriksa status HAProxy. Dalam kasus kami, kami mengonfigurasi backend kesehatan di HAProxy, dan dari sisi ExaBGP kami memeriksa dengan permintaan GET sederhana. Jika pengumuman berhenti terjadi, maka HAProxy kemungkinan besar tidak berfungsi, dan tidak perlu untuk mengumumkannya.


Pemeriksaan Kesehatan HAProxy

HAProxy Peers: sinkronisasi sesi


Hal selanjutnya yang harus dilakukan adalah menyinkronkan sesi. Ketika bekerja melalui penyeimbang terdistribusi, sulit untuk mengatur penyimpanan informasi tentang sesi klien. Tetapi HAProxy adalah salah satu dari sedikit penyeimbang yang dapat melakukan ini karena fungsi Peers - kemampuan untuk mentransfer tabel sesi antara berbagai proses HAProxy.

Ada beberapa metode balancing: sederhana, seperti round-robin , dan advanced, ketika sesi klien diingat, dan setiap kali ia sampai ke server yang sama seperti sebelumnya. Kami ingin menerapkan opsi kedua.

HAProxy menggunakan stick-tables untuk menyimpan sesi klien untuk mekanisme ini. Mereka menyimpan sumber alamat IP klien, alamat target yang dipilih (backend) dan beberapa informasi layanan. Biasanya, stick-tables digunakan untuk menyimpan pasangan sumber-IP + tujuan-IP, yang sangat berguna untuk aplikasi yang tidak dapat mentransmisikan konteks sesi pengguna saat beralih ke penyeimbang lain, misalnya, dalam mode penyeimbangan RoundRobin.

Jika stick-table diajarkan untuk berpindah di antara berbagai proses HAProxy yang berbeda (di antaranya terjadi penyeimbangan), balancers kami akan dapat bekerja dengan satu kumpulan stick-tables. Ini akan memungkinkan untuk beralih mulus jaringan klien ketika salah satu penyeimbang jatuh, bekerja dengan sesi klien akan melanjutkan pada backend yang sama yang dipilih sebelumnya.

Untuk operasi yang tepat, alamat IP sumber penyeimbang dari mana sesi didirikan harus diselesaikan. Dalam kasus kami, ini adalah alamat dinamis pada antarmuka loopback.

Operasi rekan yang benar hanya dapat dicapai dalam kondisi tertentu. Artinya, batas waktu TCP harus cukup besar atau sakelar harus cukup cepat sehingga sesi TCP tidak punya waktu untuk istirahat. Namun, ini memungkinkan pergantian yang mulus.

Kami di IaaS memiliki layanan yang dibangun di atas teknologi yang sama. Ini adalah Load Balancer sebagai layanan untuk OpenStack yang disebut Octavia. Ini berdasarkan pada dua proses HAProxy, ini awalnya termasuk dukungan rekan. Mereka telah membuktikan diri dalam layanan ini.

Gambar secara skematis menunjukkan pergerakan tabel rekan antara tiga instance HAProxy, konfigurasi disarankan, bagaimana ini dapat dikonfigurasi:


HAProxy Peers (sinkronisasi sesi)

Jika Anda menerapkan skema yang sama, pekerjaannya harus diuji dengan cermat. Bukan fakta bahwa ini akan bekerja dengan cara yang sama dalam 100% kasus. Tetapi setidaknya Anda tidak akan kehilangan tabel stick ketika Anda perlu mengingat IP sumber klien.

Membatasi jumlah permintaan simultan dari klien yang sama


Setiap layanan yang berada dalam domain publik, termasuk API kami, dapat dikenakan longsoran permintaan. Alasan mereka bisa sangat berbeda, dari kesalahan pengguna, hingga serangan yang ditargetkan. Kami secara berkala DDoS di alamat IP. Klien sering membuat kesalahan dalam skrip mereka, mereka membuat kita mini-DDoS.

Dengan satu atau lain cara, perlindungan tambahan harus disediakan. Solusi yang jelas adalah membatasi jumlah permintaan API dan tidak membuang waktu CPU memproses permintaan jahat.

Untuk menerapkan pembatasan seperti itu, kami menggunakan batas nilai, yang disusun berdasarkan HAProxy, menggunakan stick-tables yang sama. Batas tersebut dikonfigurasi dengan cukup sederhana dan memungkinkan Anda membatasi pengguna dengan jumlah permintaan ke API. Algoritma ini mengingat IP sumber dari mana permintaan dibuat, dan membatasi jumlah permintaan simultan dari satu pengguna. Tentu saja, kami menghitung rata-rata profil pemuatan API untuk setiap layanan dan menetapkan batas ≈ 10 kali dari nilai ini. Sampai sekarang, kami terus memantau situasi dengan seksama, kami terus memantau.

Seperti apa praktiknya? Kami memiliki pelanggan yang terus-menerus menggunakan API skala otomatis kami. Mereka membuat sekitar dua atau tiga ratus mesin virtual lebih dekat ke pagi hari dan menghapusnya lebih dekat ke malam hari. Untuk OpenStack, buat mesin virtual, juga dengan layanan PaaS, setidaknya 1000 permintaan API, karena interaksi antara layanan juga terjadi melalui API.

Pelemparan tugas seperti itu menyebabkan beban yang agak besar. Kami memperkirakan beban ini, mengumpulkan puncak harian, meningkatkannya sepuluh kali lipat, dan ini menjadi batas kurs kami. Kami menjaga jari kami pada denyut nadi. Kita sering melihat bot, scanner, yang mencoba melihat kita, apakah kita memiliki skrip CGA yang dapat dijalankan, kita secara aktif memotongnya.

Cara memperbarui basis kode secara diam-diam untuk pengguna


Kami juga menerapkan toleransi kesalahan pada tingkat proses penyebaran kode. Ada crash saat peluncuran, tetapi dampaknya pada ketersediaan layanan dapat diminimalkan.

Kami terus memperbarui layanan kami dan harus memastikan proses memperbarui basis kode tanpa efek bagi pengguna. Kami berhasil memecahkan masalah ini menggunakan kemampuan manajemen HAProxy dan penerapan Shutdown Graceful dalam layanan kami.

Untuk mengatasi masalah ini, perlu untuk menyediakan kontrol penyeimbang dan penutupan layanan yang "benar":

  • Dalam kasus HAProxy, kontrol dilakukan melalui file statistik, yang pada dasarnya adalah sebuah soket dan didefinisikan dalam konfigurasi HAProxy. Anda dapat mengirim perintah kepadanya melalui stdio. Tetapi alat kontrol konfigurasi utama kami adalah memungkinkan, sehingga memiliki modul bawaan untuk mengelola HAProxy. Yang kami gunakan secara aktif.
  • Sebagian besar layanan API dan Mesin kami mendukung teknologi shutdown yang anggun: setelah shutdown, mereka menunggu tugas saat ini untuk diselesaikan, baik itu permintaan http atau semacam tugas utilitas. Hal yang sama terjadi pada pekerja. Dia tahu semua tugas yang dia lakukan, dan berakhir ketika dia telah berhasil menyelesaikan semuanya.

Berkat dua poin ini, algoritma aman penerapan kami adalah sebagai berikut.

  1. Pengembang membuat paket kode baru (kami memiliki RPM), tes di lingkungan dev, tes di panggung, dan meninggalkannya di repositori panggung.
  2. Pengembang menempatkan tugas pada penyebaran dengan deskripsi paling rinci dari "artefak": versi paket baru, deskripsi fungsi baru dan detail lainnya tentang penyebaran, jika perlu.
  3. Administrator sistem memulai peningkatan. Meluncurkan playbook Ansible, yang pada gilirannya melakukan hal berikut:
    • Dibutuhkan paket dari repositori panggung, memperbarui versi paket dalam repositori produk dengannya.
    • Membuat daftar backend dari layanan yang diperbarui.
    • Mematikan layanan yang pertama diperbarui di HAProxy dan menunggu akhir dari prosesnya. Berkat shutdown anggun, kami yakin bahwa semua permintaan klien saat ini akan selesai dengan sukses.
    • Setelah API, pekerja, dan HAProxy benar-benar dihentikan, kode diperbarui.
    • Layanan peluncuran yang dimungkinkan.
    • Untuk setiap layanan, ia menarik "pena" tertentu yang melakukan pengujian unit untuk sejumlah tes kunci yang telah ditentukan. Pemeriksaan dasar dari kode baru terjadi.
    • Jika tidak ada kesalahan yang ditemukan pada langkah sebelumnya, backend diaktifkan.
    • Pergi ke backend berikutnya.
  4. Setelah memperbarui semua backend, tes fungsional diluncurkan. Jika tidak cukup, maka pengembang akan melihat fungsionalitas baru yang dia lakukan.

Pada penyebaran ini selesai.


Siklus pembaruan layanan

Skema ini tidak akan berfungsi jika kami tidak memiliki satu aturan. Kami mendukung versi lama dan baru dalam pertempuran. Di muka, pada tahap pengembangan perangkat lunak, dinyatakan bahwa bahkan jika ada perubahan dalam database layanan, mereka tidak akan merusak kode sebelumnya. Akibatnya, basis kode diperbarui secara bertahap.

Kesimpulan


Berbagi pemikiran saya sendiri tentang arsitektur WEB yang toleran terhadap kesalahan, saya ingin sekali lagi mencatat poin-poin utamanya:

  • toleransi kesalahan fisik;
  • toleransi kesalahan jaringan (penyeimbang, BGP);
  • .

uptime!

Source: https://habr.com/ru/post/id474180/


All Articles