
Halo, Habr! Saya Artem Karamyshev, kepala tim administrasi sistem . Kami telah meluncurkan banyak produk baru selama setahun terakhir. Kami ingin memastikan bahwa layanan API dapat diskalakan dengan mudah, toleran terhadap kesalahan, dan siap menghadapi pertumbuhan pesat dalam beban pengguna. Platform kami diimplementasikan pada OpenStack, dan saya ingin memberi tahu Anda masalah toleransi kesalahan komponen apa yang harus kami selesaikan untuk mendapatkan sistem yang toleran terhadap kesalahan. Saya rasa ini akan menarik bagi mereka yang juga mengembangkan produk di OpenStack.
Toleransi kesalahan keseluruhan suatu platform terdiri dari ketahanan komponen-komponennya. Jadi kami akan secara bertahap melewati semua level di mana kami mengidentifikasi risiko dan menutupnya.
Versi video dari cerita ini, sumber utamanya adalah laporan pada konferensi Uptime hari ke-4, yang diselenggarakan oleh , kamu bisa lihat .
Ketahanan arsitektur fisik
Bagian publik dari cloud MCS sekarang berbasis di dua pusat data Tingkat III, di antara keduanya terdapat serat gelapnya sendiri, yang dicadangkan pada tingkat fisik melalui rute berbeda, dengan throughput 200 Gbit/dtk. Tier III memberikan tingkat toleransi kesalahan yang diperlukan untuk infrastruktur fisik.
Serat gelap dicadangkan pada tingkat fisik dan logis. Proses reservasi saluran bersifat berulang, masalah muncul, dan kami terus meningkatkan komunikasi antar pusat data.
Misalnya, belum lama ini, saat bekerja di sumur dekat salah satu pusat data, sebuah ekskavator memecahkan sebuah pipa, dan di dalam pipa tersebut terdapat kabel optik utama dan kabel cadangan. Saluran komunikasi kami yang toleran terhadap kesalahan dengan pusat data ternyata rentan pada satu titik, di dalam sumur. Oleh karena itu, kami telah kehilangan sebagian infrastruktur. Kami menarik kesimpulan dan mengambil sejumlah tindakan, termasuk memasang optik tambahan di sumur yang berdekatan.
Di pusat data terdapat titik kehadiran penyedia komunikasi kepada siapa kami menyiarkan awalan kami melalui BGP. Untuk setiap arah jaringan, metrik terbaik dipilih, yang memungkinkan klien berbeda diberikan kualitas koneksi terbaik. Jika komunikasi melalui satu penyedia terputus, kami membangun kembali perutean kami melalui penyedia yang tersedia.
Jika salah satu penyedia gagal, kami secara otomatis beralih ke penyedia berikutnya. Jika terjadi kegagalan pada salah satu pusat data, kami memiliki salinan cermin layanan kami di pusat data kedua, yang mengambil alih seluruh beban.

Ketahanan infrastruktur fisik
Apa yang kami gunakan untuk toleransi kesalahan tingkat aplikasi
Layanan kami dibangun di atas sejumlah komponen sumber terbuka.
ExaBGP adalah layanan yang mengimplementasikan sejumlah fungsi menggunakan protokol perutean dinamis berbasis BGP. Kami secara aktif menggunakannya untuk mengiklankan alamat IP kami yang masuk daftar putih yang digunakan pengguna untuk mengakses API.
HAProxy adalah penyeimbang beban tinggi yang memungkinkan Anda mengonfigurasi aturan penyeimbangan lalu lintas yang sangat fleksibel di berbagai tingkat model OSI. Kami menggunakannya untuk menyeimbangkan semua layanan: database, perantara pesan, layanan API, layanan web, proyek internal kami - semuanya ada di belakang HAProxy.
aplikasi API — aplikasi web yang ditulis dengan python, yang dengannya pengguna mengelola infrastruktur dan layanannya.
Aplikasi pekerja (selanjutnya disebut pekerja) - dalam layanan OpenStack, ini adalah daemon infrastruktur yang memungkinkan Anda menyiarkan perintah API ke infrastruktur. Misalnya, pembuatan disk terjadi di pekerja, dan permintaan pembuatan terjadi di API aplikasi.
Arsitektur Aplikasi OpenStack Standar
Kebanyakan layanan yang dikembangkan untuk OpenStack mencoba mengikuti paradigma tunggal. Sebuah layanan biasanya terdiri dari 2 bagian: API dan pekerja (pelaksana backend). Biasanya, API adalah aplikasi WSGI dengan python, yang diluncurkan baik sebagai proses independen (daemon), atau menggunakan server web Nginx atau Apache yang sudah jadi. API memproses permintaan pengguna dan meneruskan instruksi lebih lanjut ke aplikasi pekerja untuk dieksekusi. Transfer terjadi menggunakan broker pesan, biasanya RabbitMQ, yang lain tidak didukung dengan baik. Ketika pesan sampai ke broker, pesan tersebut diproses oleh pekerja dan, jika perlu, memberikan respons.
Paradigma ini melibatkan titik kegagalan umum yang terisolasi: RabbitMQ dan database. Namun RabbitMQ diisolasi dalam satu layanan dan, secara teori, dapat bersifat individual untuk setiap layanan. Jadi di MCS kami memisahkan layanan ini sebanyak mungkin; untuk setiap proyek kami membuat database terpisah, RabbitMQ terpisah. Pendekatan ini baik karena jika terjadi kecelakaan di beberapa titik rawan, tidak seluruh layanan terganggu, melainkan hanya sebagian saja.
Jumlah aplikasi pekerja tidak terbatas, sehingga API dapat dengan mudah melakukan penskalaan secara horizontal di belakang penyeimbang untuk meningkatkan kinerja dan toleransi kesalahan.
Beberapa layanan memerlukan koordinasi dalam layanan ketika terjadi operasi berurutan yang kompleks antara API dan pekerja. Dalam hal ini, satu pusat koordinasi digunakan, sistem cluster seperti Redis, Memcache, dll, yang memungkinkan satu pekerja memberi tahu pekerja lain bahwa tugas ini diberikan kepadanya (“tolong jangan ambil itu”). Kami menggunakan dll. Biasanya, pekerja secara aktif berkomunikasi dengan database, menulis dan membaca informasi dari sana. Kami menggunakan mariadb sebagai database, yang terletak di cluster multimaster.
Layanan tunggal klasik ini diatur dengan cara yang diterima secara umum untuk OpenStack. Ini dapat dianggap sebagai sistem tertutup, yang metode penskalaannya dan toleransi kesalahannya cukup jelas. Misalnya, untuk toleransi kesalahan API, cukup dengan menempatkan penyeimbang di depannya. Penskalaan pekerja dicapai dengan meningkatkan jumlah mereka.
Titik lemah dalam keseluruhan skema adalah RabbitMQ dan MariaDB. Arsitektur mereka layak mendapat artikel terpisah. Pada artikel ini saya ingin fokus pada toleransi kesalahan API.

Arsitektur Aplikasi Openstack. Penyeimbangan dan toleransi kesalahan pada platform cloud
Membuat penyeimbang HAProxy toleran terhadap kesalahan menggunakan ExaBGP
Untuk menjadikan API kami skalabel, cepat, dan toleran terhadap kesalahan, kami menempatkan penyeimbang beban di depannya. Kami memilih HAProxy. Menurut pendapat saya, ia memiliki semua karakteristik yang diperlukan untuk tugas kita: penyeimbangan di beberapa level OSI, antarmuka manajemen, fleksibilitas dan skalabilitas, sejumlah besar metode penyeimbangan, dukungan untuk tabel sesi.
Masalah pertama yang perlu dipecahkan adalah toleransi kesalahan dari penyeimbang itu sendiri. Memasang penyeimbang saja juga menciptakan titik kegagalan: penyeimbang rusak dan layanan terhenti. Untuk mencegah hal ini terjadi, kami menggunakan HAProxy bersama dengan ExaBGP.
ExaBGP memungkinkan Anda menerapkan mekanisme untuk memeriksa status layanan. Kami menggunakan mekanisme ini untuk memeriksa fungsionalitas HAProxy dan, jika terjadi masalah, menonaktifkan layanan HAProxy dari BGP.
Skema ExaBGP+HAProxy
- Kami menginstal perangkat lunak yang diperlukan, ExaBGP dan HAProxy, di tiga server.
- Kami membuat antarmuka loopback di setiap server.
- Di ketiga server kami menetapkan alamat IP putih yang sama ke antarmuka ini.
- Alamat IP putih diiklankan ke Internet melalui ExaBGP.
Toleransi kesalahan dicapai dengan mengiklankan alamat IP yang sama dari ketiga server. Dari sudut pandang jaringan, alamat yang sama dapat diakses dari tiga hop berikutnya yang berbeda. Router melihat tiga rute yang identik, memilih prioritas tertinggi berdasarkan metriknya sendiri (biasanya ini merupakan opsi yang sama), dan lalu lintas hanya menuju ke salah satu server.
Jika terjadi masalah dengan pengoperasian HAProxy atau kegagalan server, ExaBGP berhenti mengumumkan rute, dan lalu lintas dengan lancar beralih ke server lain.
Dengan demikian, kami mencapai toleransi kesalahan penyeimbang.

Toleransi kesalahan penyeimbang HAProxy
Skema tersebut ternyata tidak sempurna: kami mempelajari cara mencadangkan HAProxy, tetapi tidak mempelajari cara mendistribusikan beban dalam layanan. Oleh karena itu, kami sedikit memperluas skema ini: kami beralih ke penyeimbangan antara beberapa alamat IP putih.
Penyeimbangan berdasarkan DNS plus BGP
Masalah penyeimbangan beban untuk HAProxy kami masih belum terselesaikan. Namun, hal ini dapat diselesaikan dengan cukup sederhana, seperti yang kami lakukan di sini.
Untuk menyeimbangkan tiga server Anda memerlukan 3 alamat IP putih dan DNS lama yang bagus. Masing-masing alamat ini ditentukan pada antarmuka loopback setiap HAProxy dan diiklankan ke Internet.
Di OpenStack, untuk mengelola sumber daya, direktori layanan digunakan, yang menentukan API titik akhir dari layanan tertentu. Di direktori ini kami mendaftarkan nama domain - public.infra.mail.ru, yang diselesaikan melalui DNS dengan tiga alamat IP berbeda. Hasilnya, kami mendapatkan distribusi beban antara tiga alamat melalui DNS.
Namun karena saat mengumumkan alamat IP putih kami tidak mengontrol prioritas pemilihan server, hal ini belum seimbang. Biasanya, hanya satu server yang akan dipilih berdasarkan senioritas alamat IP, dan dua lainnya akan menganggur karena tidak ada metrik yang ditentukan dalam BGP.
Kami mulai mengirimkan rute melalui ExaBGP dengan metrik yang berbeda. Setiap penyeimbang mengiklankan ketiga alamat IP putih, namun salah satunya, yang utama untuk penyeimbang ini, diiklankan dengan metrik minimum. Jadi ketika ketiga penyeimbang sedang beroperasi, panggilan ke alamat IP pertama menuju ke penyeimbang pertama, panggilan ke penyeimbang kedua ke penyeimbang kedua, dan panggilan ke penyeimbang ketiga ke penyeimbang ketiga.
Apa yang terjadi jika salah satu penyeimbang jatuh? Jika salah satu penyeimbang gagal, alamat utamanya masih diiklankan dari dua penyeimbang lainnya, dan lalu lintas didistribusikan kembali di antara keduanya. Jadi, kami memberikan pengguna beberapa alamat IP sekaligus melalui DNS. Dengan menyeimbangkan berdasarkan DNS dan metrik yang berbeda, kami mendapatkan distribusi beban yang merata di ketiga penyeimbang. Dan pada saat yang sama kami tidak kehilangan toleransi kesalahan.

Menyeimbangkan HAProxy berdasarkan DNS + BGP
Interaksi antara ExaBGP dan HAProxy
Jadi, kami menerapkan toleransi kesalahan jika server keluar, berdasarkan penghentian pengumuman rute. Tetapi HAProxy dapat dimatikan karena alasan lain selain kegagalan server: kesalahan administrasi, kegagalan dalam layanan. Kami ingin menghilangkan penyeimbang yang rusak dari bawah beban dalam kasus ini juga, dan kami memerlukan mekanisme yang berbeda.
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, Anda perlu mengonfigurasi pemeriksa kesehatan di konfigurasi ExaBGP, yang dapat memeriksa status HAProxy. Dalam kasus kami, kami mengonfigurasi backend kesehatan di HAProxy, dan dari sisi ExaBGP kami memeriksanya dengan permintaan GET sederhana. Jika pengumuman berhenti terjadi, kemungkinan besar HAProxy tidak berfungsi dan tidak perlu diiklankan.

Pemeriksaan Kesehatan HAProxy
HAProxy Peers: sinkronisasi sesi
Hal berikutnya yang harus dilakukan adalah menyinkronkan sesi. Saat bekerja melalui penyeimbang terdistribusi, sulit untuk mengatur penyimpanan informasi tentang sesi klien. Namun HAProxy adalah salah satu dari sedikit penyeimbang yang dapat melakukan ini karena fungsi Peers - kemampuan untuk mentransfer tabel sesi antara proses HAProxy yang berbeda.
Ada beberapa metode penyeimbangan yang berbeda: yang sederhana seperti , dan diperpanjang, ketika sesi klien diingat, dan setiap kali dia berakhir di server yang sama seperti sebelumnya. Kami ingin menerapkan opsi kedua.
HAProxy menggunakan tabel stick untuk menyimpan sesi klien dari mekanisme ini. Mereka menyimpan alamat IP asli klien, alamat target yang dipilih (backend) dan beberapa informasi layanan. Biasanya, tabel stick digunakan untuk menyimpan pasangan IP sumber + IP tujuan, yang sangat berguna untuk aplikasi yang tidak dapat mentransfer konteks sesi pengguna saat beralih ke penyeimbang lain, misalnya, dalam mode penyeimbangan RoundRobin.
Jika tabel stick diajarkan untuk berpindah di antara proses HAProxy yang berbeda (di antara proses penyeimbangan yang terjadi), penyeimbang kami akan dapat bekerja dengan satu kumpulan tabel stick. Hal ini akan memungkinkan peralihan jaringan klien dengan lancar jika salah satu penyeimbang gagal; bekerja dengan sesi klien akan dilanjutkan pada backend yang sama yang dipilih sebelumnya.
Untuk pengoperasian yang benar, masalah alamat IP sumber penyeimbang tempat sesi dibuat harus diselesaikan. Dalam kasus kami, ini adalah alamat dinamis pada antarmuka loopback.
Pekerjaan rekan yang benar hanya dapat dicapai dalam kondisi tertentu. Artinya, batas waktu TCP harus cukup besar atau peralihan harus cukup cepat sehingga sesi TCP tidak mempunyai waktu untuk dihentikan. Namun, ini memungkinkan peralihan yang mulus.
Di IaaS kami memiliki layanan yang dibangun menggunakan teknologi yang sama. Ini , yang disebut Oktavia. Hal ini didasarkan pada dua proses HAProxy dan pada awalnya mencakup dukungan untuk rekan-rekan. Mereka telah membuktikan diri mereka unggul dalam layanan ini.
Gambar secara skematis menunjukkan pergerakan tabel rekan antara tiga instance HAProxy, sebuah konfigurasi diusulkan tentang bagaimana hal ini dapat dikonfigurasi:

HAProxy Peers (sinkronisasi sesi)
Jika Anda menerapkan skema yang sama, pengoperasiannya harus diuji dengan cermat. Bukan fakta bahwa ini akan bekerja dengan cara yang sama 100% setiap saat. Tapi setidaknya Anda tidak akan kehilangan tabel stick ketika Anda perlu mengingat IP sumber klien.
Membatasi jumlah permintaan simultan dari klien yang sama
Layanan apa pun yang tersedia untuk umum, termasuk API kami, dapat mengalami banyak sekali permintaan. Alasannya bisa sangat berbeda, mulai dari kesalahan pengguna hingga serangan yang ditargetkan. Kami secara berkala DDoS berdasarkan alamat IP. Klien sering membuat kesalahan dalam skrip mereka dan memberi kami mini-DDoS.
Bagaimanapun, perlindungan tambahan harus diberikan. Solusi yang jelas adalah membatasi jumlah permintaan API dan tidak membuang waktu CPU untuk memproses permintaan berbahaya.
Untuk menerapkan pembatasan tersebut, kami menggunakan batasan tarif, yang disusun berdasarkan HAProxy, menggunakan tabel stick yang sama. Menyiapkan batasan cukup sederhana dan memungkinkan Anda membatasi pengguna berdasarkan 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 profil beban API rata-rata untuk setiap layanan dan menetapkan batas ≈ 10 kali nilai ini. Kami terus memantau situasi dengan cermat dan terus memantau perkembangannya.
Seperti apa praktiknya? Kami memiliki pelanggan yang menggunakan API penskalaan otomatis kami sepanjang waktu. Mereka membuat sekitar dua hingga tiga ratus mesin virtual di pagi hari dan menghapusnya di malam hari. Untuk OpenStack, pembuatan mesin virtual, juga dengan layanan PaaS, memerlukan setidaknya 1000 permintaan API, karena interaksi antar layanan juga terjadi melalui API.
Pemindahan tugas yang demikian menimbulkan beban yang cukup besar. Kami menilai beban ini, mengumpulkan puncak harian, meningkatkannya sepuluh kali lipat, dan ini menjadi batas kecepatan kami. Kami terus memantau perkembangannya. Kami sering melihat bot dan pemindai yang mencoba melihat kami untuk melihat apakah kami memiliki skrip CGA yang dapat dijalankan, kami secara aktif memotongnya.
Cara memperbarui basis kode Anda tanpa disadari pengguna
Kami juga menerapkan toleransi kesalahan pada tingkat proses penerapan kode. Mungkin ada gangguan selama peluncuran, namun dampaknya terhadap ketersediaan layanan dapat diminimalkan.
Kami terus memperbarui layanan kami dan harus memastikan bahwa basis kode diperbarui tanpa memengaruhi pengguna. Kami berhasil mengatasi masalah ini dengan menggunakan kemampuan manajemen HAProxy dan penerapan Graceful Shutdown di layanan kami.
Untuk mengatasi masalah ini, penting untuk memastikan kontrol penyeimbang dan penutupan layanan yang “benar”:
- Dalam kasus HAProxy, kontrol dilakukan melalui file statistik, yang pada dasarnya adalah soket dan ditentukan dalam konfigurasi HAProxy. Anda dapat mengirim perintah melalui stdio. Namun alat kontrol konfigurasi utama kami dimungkinkan, sehingga memiliki modul bawaan untuk mengelola HAProxy. Yang kami gunakan secara aktif.
- Sebagian besar layanan API dan Mesin kami mendukung teknologi pematian yang baik: saat dimatikan, mereka menunggu hingga tugas saat ini selesai, baik itu permintaan http atau tugas layanan apa pun. Hal yang sama juga terjadi pada pekerja. Ia mengetahui semua tugas yang dilakukannya dan berakhir ketika semuanya telah berhasil diselesaikan.
Berkat dua poin ini, algoritme aman untuk penerapan kami terlihat seperti ini.
- Pengembang merakit paket kode baru (bagi kami ini adalah RPM), mengujinya di lingkungan pengembang, mengujinya di panggung, dan meninggalkannya di repositori panggung.
- Pengembang menetapkan tugas penerapan dengan deskripsi "artefak" paling detail: versi paket baru, deskripsi fungsi baru, dan detail penerapan lainnya jika diperlukan.
- Administrator sistem memulai pembaruan. Meluncurkan buku pedoman Ansible, yang selanjutnya melakukan hal berikut:
- Mengambil paket dari repositori panggung dan menggunakannya untuk memperbarui versi paket di repositori produk.
- Mengompilasi daftar backend layanan yang diperbarui.
- Mematikan layanan pertama yang diperbarui di HAProxy dan menunggu prosesnya selesai berjalan. Berkat penutupan yang baik, kami yakin bahwa semua permintaan klien saat ini akan berhasil diselesaikan.
- Setelah API dan pekerja dihentikan sepenuhnya, dan HAProxy dimatikan, kode diperbarui.
- Kemungkinan menjalankan layanan.
- Untuk setiap layanan, “pegangan” tertentu ditarik, yang melakukan pengujian unit pada sejumlah pengujian kunci yang telah ditentukan sebelumnya. Pemeriksaan dasar terhadap kode baru dilakukan.
- Jika tidak ditemukan kesalahan pada langkah sebelumnya, backend diaktifkan.
- Mari beralih ke backend berikutnya.
- Setelah semua backend diperbarui, pengujian fungsional diluncurkan. Jika tidak ada, maka pengembang akan melihat fungsi baru yang dia buat.
Ini menyelesaikan penerapan.

Siklus pembaruan layanan
Skema ini tidak akan berhasil jika kita tidak memiliki satu aturan. Kami mendukung versi lama dan baru dalam pertempuran. Sebelumnya, pada tahap pengembangan perangkat lunak, telah ditetapkan bahwa meskipun ada perubahan pada database layanan, perubahan tersebut tidak akan merusak kode sebelumnya. Hasilnya, basis kode diperbarui secara bertahap.
Kesimpulan
Berbagi pemikiran saya tentang arsitektur WEB yang toleran terhadap kesalahan, saya ingin sekali lagi mencatat poin-poin utamanya:
- toleransi kesalahan fisik;
- toleransi kesalahan jaringan (penyeimbang, BGP);
- toleransi kesalahan perangkat lunak yang digunakan dan dikembangkan.
Waktu aktif yang stabil semuanya!
Sumber: www.habr.com
