One-cloud - OS tingkat pusat data di Odnoklassniki

One-cloud - OS tingkat pusat data di Odnoklassniki

Aloha, semuanya! Nama saya Oleg Anastasyev, saya bekerja di Odnoklassniki di tim Platform. Dan selain saya, ada banyak perangkat keras yang berfungsi di Odnoklassniki. Kami memiliki empat pusat data dengan sekitar 500 rak dengan lebih dari 8 ribu server. Pada titik tertentu, kami menyadari bahwa pengenalan sistem manajemen baru akan memungkinkan kami memuat peralatan dengan lebih efisien, memfasilitasi manajemen akses, mengotomatisasi (re)distribusi sumber daya komputasi, mempercepat peluncuran layanan baru, dan mempercepat respons. terhadap kecelakaan berskala besar.

Apa hasilnya?

Selain saya dan sejumlah perangkat keras, ada juga orang yang bekerja dengan perangkat keras ini: insinyur yang berlokasi langsung di pusat data; penggiat jejaring yang menyiapkan perangkat lunak jaringan; administrator, atau SRE, yang menyediakan ketahanan infrastruktur; dan tim pengembangan, masing-masing bertanggung jawab atas sebagian fungsi portal. Perangkat lunak yang mereka buat berfungsi seperti ini:

One-cloud - OS tingkat pusat data di Odnoklassniki

Permintaan pengguna diterima di bagian depan portal utama www.ok.ru, dan lainnya, misalnya di bagian depan API musik. Untuk memproses logika bisnis, mereka memanggil server aplikasi, yang, ketika memproses permintaan, memanggil layanan mikro khusus yang diperlukan - satu grafik (grafik koneksi sosial), cache pengguna (cache profil pengguna), dll.

Masing-masing layanan ini diterapkan pada banyak mesin, dan masing-masing layanan tersebut memiliki pengembang yang bertanggung jawab atas fungsi modul, pengoperasiannya, dan pengembangan teknologinya. Semua layanan ini berjalan di server perangkat keras, dan hingga saat ini kami meluncurkan tepat satu tugas per server, yaitu khusus untuk tugas tertentu.

Mengapa demikian? Pendekatan ini memiliki beberapa keuntungan:

  • Lega manajemen massa. Katakanlah suatu tugas memerlukan beberapa perpustakaan, beberapa pengaturan. Dan kemudian server ditugaskan ke satu grup tertentu, kebijakan cfengine untuk grup ini dijelaskan (atau sudah dijelaskan), dan konfigurasi ini diluncurkan secara terpusat dan otomatis ke semua server di grup ini.
  • Sederhana diagnostik. Katakanlah Anda melihat peningkatan beban pada prosesor pusat dan menyadari bahwa beban ini hanya dapat dihasilkan oleh tugas yang berjalan pada prosesor perangkat keras ini. Pencarian seseorang untuk disalahkan berakhir dengan sangat cepat.
  • Sederhana pemantauan. Jika ada yang salah dengan server, monitor akan melaporkannya, dan Anda tahu persis siapa yang harus disalahkan.

Layanan yang terdiri dari beberapa replika dialokasikan beberapa server - satu untuk masing-masing server. Kemudian sumber daya komputasi untuk layanan tersebut dialokasikan dengan sangat sederhana: jumlah server yang dimiliki layanan, jumlah maksimum sumber daya yang dapat dikonsumsi. β€œMudah” di sini bukan berarti mudah digunakan, namun dalam arti alokasi sumber daya dilakukan secara manual.

Pendekatan ini juga memungkinkan kami melakukan hal tersebut konfigurasi besi khusus untuk tugas yang berjalan di server ini. Jika tugas menyimpan data dalam jumlah besar, maka kami menggunakan server 4U dengan sasis dengan 38 disk. Jika tugasnya murni komputasi, maka kita bisa membeli server 1U yang lebih murah. Ini efisien secara komputasi. Antara lain, pendekatan ini memungkinkan kita menggunakan mesin empat kali lebih sedikit dengan beban yang sebanding dengan satu jejaring sosial ramah.

Efisiensi dalam penggunaan sumber daya komputasi juga harus menjamin efisiensi ekonomi, jika kita berangkat dari premis bahwa yang paling mahal adalah server. Untuk waktu yang lama, perangkat keras adalah yang paling mahal, dan kami berupaya keras untuk menurunkan harga perangkat keras, menghasilkan algoritme toleransi kesalahan untuk mengurangi persyaratan keandalan perangkat keras. Dan hari ini kita telah mencapai tahap di mana harga server tidak lagi menjadi penentu. Jika Anda tidak mempertimbangkan eksotik terbaru, maka konfigurasi spesifik server di rak tidak menjadi masalah. Sekarang kita mempunyai masalah lain - harga ruang yang ditempati oleh server di pusat data, yaitu ruang di rak.

Menyadari hal ini, kami memutuskan untuk menghitung seberapa efektif kami menggunakan rak.
Kami mengambil harga server yang paling kuat dari yang dapat dibenarkan secara ekonomi, menghitung berapa banyak server yang dapat kami tempatkan di rak, berapa banyak tugas yang akan kami jalankan berdasarkan model lama β€œsatu server = satu tugas” dan berapa banyak server tersebut tugas dapat memanfaatkan peralatan tersebut. Mereka menghitung dan menitikkan air mata. Ternyata efisiensi kami dalam menggunakan rak sekitar 11%. Kesimpulannya jelas: kita perlu meningkatkan efisiensi penggunaan pusat data. Tampaknya solusinya jelas: Anda perlu menjalankan beberapa tugas di satu server sekaligus. Namun di sinilah kesulitannya dimulai.

Konfigurasi massal menjadi jauh lebih rumit - sekarang tidak mungkin untuk menetapkan satu grup ke server. Memang, sekarang beberapa tugas dari perintah yang berbeda dapat dijalankan di satu server. Selain itu, konfigurasi mungkin bertentangan untuk aplikasi yang berbeda. Diagnosis juga menjadi lebih rumit: jika Anda melihat peningkatan konsumsi CPU atau disk di server, Anda tidak tahu tugas mana yang menyebabkan masalah.

Namun yang terpenting adalah tidak ada isolasi antara tugas-tugas yang berjalan di mesin yang sama. Di sini, misalnya, adalah grafik waktu respons rata-rata tugas server sebelum dan sesudah aplikasi komputasi lain diluncurkan di server yang sama, yang sama sekali tidak terkait dengan yang pertama - waktu respons tugas utama telah meningkat secara signifikan.

One-cloud - OS tingkat pusat data di Odnoklassniki

Jelasnya, Anda perlu menjalankan tugas baik di container atau di mesin virtual. Karena hampir semua tugas kita berjalan di bawah satu OS (Linux) atau diadaptasi untuk itu, kita tidak perlu mendukung banyak sistem operasi yang berbeda. Oleh karena itu, virtualisasi tidak diperlukan; karena adanya tambahan overhead, maka akan kurang efisien dibandingkan containerisasi.

Sebagai implementasi container untuk menjalankan tugas secara langsung di server, Docker adalah kandidat yang baik: image sistem file memecahkan masalah dengan konfigurasi yang bertentangan dengan baik. Fakta bahwa gambar dapat terdiri dari beberapa lapisan memungkinkan kami mengurangi secara signifikan jumlah data yang diperlukan untuk menyebarkannya pada infrastruktur, memisahkan bagian-bagian umum menjadi lapisan dasar yang terpisah. Kemudian lapisan dasar (dan yang paling banyak jumlahnya) akan di-cache dengan cukup cepat di seluruh infrastruktur, dan untuk menghadirkan berbagai jenis aplikasi dan versi, hanya lapisan kecil yang perlu ditransfer.

Selain itu, registri siap pakai dan penandaan gambar di Docker memberi kita primitif siap pakai untuk membuat versi dan mengirimkan kode ke produksi.

Docker, seperti teknologi serupa lainnya, memberi kita tingkat isolasi container tertentu. Misalnya, isolasi memori - setiap container diberi batasan penggunaan memori mesin, yang melebihi batas tersebut tidak akan dikonsumsi. Anda juga dapat mengisolasi container berdasarkan penggunaan CPU. Namun bagi kami, insulasi standar saja tidak cukup. Namun lebih lanjut tentang itu di bawah.

Menjalankan container secara langsung di server hanyalah sebagian dari masalah. Bagian lainnya terkait dengan hosting container di server. Anda perlu memahami container mana yang bisa ditempatkan di server mana. Ini bukanlah tugas yang mudah, karena container harus ditempatkan di server sepadat mungkin tanpa mengurangi kecepatannya. Penempatan seperti itu juga sulit dari sudut pandang toleransi kesalahan. Seringkali kita ingin menempatkan replika layanan yang sama di rak yang berbeda atau bahkan di ruangan berbeda di pusat data, sehingga jika ada rak atau ruangan yang rusak, kita tidak langsung kehilangan semua replika layanan tersebut.

Mendistribusikan container secara manual bukanlah suatu pilihan ketika Anda memiliki 8 ribu server dan 8-16 ribu container.

Selain itu, kami ingin memberi pengembang lebih banyak kebebasan dalam alokasi sumber daya sehingga mereka dapat meng-host sendiri layanan mereka dalam produksi, tanpa bantuan administrator. Pada saat yang sama, kami ingin mempertahankan kendali sehingga beberapa layanan kecil tidak menghabiskan seluruh sumber daya pusat data kami.

Jelasnya, kita memerlukan lapisan kontrol yang dapat melakukan hal ini secara otomatis.

Jadi kita sampai pada gambaran sederhana dan mudah dipahami yang disukai semua arsitek: tiga kotak.

One-cloud - OS tingkat pusat data di Odnoklassniki

one-cloud master adalah kluster failover yang bertanggung jawab atas orkestrasi cloud. Pengembang mengirimkan manifes ke master, yang berisi semua informasi yang diperlukan untuk menghosting layanan. Berdasarkan itu, master memberikan perintah kepada minion yang dipilih (mesin yang dirancang untuk menjalankan container). Minion memiliki agen kami, yang menerima perintah, mengeluarkan perintahnya ke Docker, dan Docker mengonfigurasi kernel linux untuk meluncurkan container yang sesuai. Selain menjalankan perintah, agen terus-menerus melaporkan kepada master tentang perubahan status mesin minion dan container yang berjalan di dalamnya.

Alokasi sumber daya

Sekarang mari kita lihat masalah alokasi sumber daya yang lebih kompleks untuk banyak minion.

Sumber daya komputasi dalam satu cloud adalah:

  • Jumlah daya prosesor yang dikonsumsi oleh tugas tertentu.
  • Jumlah memori yang tersedia untuk tugas tersebut.
  • Lalu lintas jaringan. Masing-masing minion memiliki antarmuka jaringan tertentu dengan bandwidth terbatas, sehingga tidak mungkin mendistribusikan tugas tanpa memperhitungkan jumlah data yang mereka kirimkan melalui jaringan.
  • Disk. Selain ruang untuk tugas-tugas ini, kami juga mengalokasikan jenis disk: HDD atau SSD. Disk dapat melayani sejumlah permintaan per detik - IOPS. Oleh karena itu, untuk tugas yang menghasilkan lebih banyak IOPS daripada yang dapat ditangani oleh satu disk, kami juga mengalokasikan β€œspindel” - yaitu, perangkat disk yang harus dicadangkan secara eksklusif untuk tugas tersebut.

Kemudian untuk beberapa layanan, misalnya untuk cache pengguna, kita dapat mencatat sumber daya yang dikonsumsi dengan cara ini: 400 inti prosesor, memori 2,5 TB, lalu lintas 50 Gbit/s di kedua arah, ruang HDD 6 TB yang terletak di 100 spindel. Atau dalam bentuk yang lebih familiar seperti ini:

alloc:
    cpu: 400
    mem: 2500
    lan_in: 50g
    lan_out: 50g
    hdd:100x6T

Sumber daya layanan cache pengguna hanya menggunakan sebagian dari seluruh sumber daya yang tersedia di infrastruktur produksi. Oleh karena itu, saya ingin memastikan bahwa tiba-tiba, karena kesalahan operator atau tidak, cache pengguna tidak menghabiskan lebih banyak sumber daya daripada yang dialokasikan padanya. Artinya, kita harus membatasi sumber daya. Tapi kuotanya bisa diikat ke mana?

Mari kita kembali ke diagram interaksi komponen yang sangat disederhanakan dan menggambar ulang dengan lebih detail - seperti ini:

One-cloud - OS tingkat pusat data di Odnoklassniki

Apa yang menarik perhatian:

  • Frontend web dan musik menggunakan cluster terisolasi dari server aplikasi yang sama.
  • Kita dapat membedakan lapisan logis yang menjadi milik cluster ini: front, cache, penyimpanan data, dan lapisan manajemen.
  • Frontendnya heterogen; terdiri dari subsistem fungsional yang berbeda.
  • Cache juga dapat tersebar di seluruh subsistem yang datanya di-cache.

Mari kita menggambar ulang gambarnya lagi:

One-cloud - OS tingkat pusat data di Odnoklassniki

Bah! Ya, kami melihat hierarki! Ini berarti Anda dapat mendistribusikan sumber daya dalam bagian yang lebih besar: menugaskan pengembang yang bertanggung jawab ke simpul hierarki ini yang sesuai dengan subsistem fungsional (seperti β€œmusik” dalam gambar), dan melampirkan kuota ke tingkat hierarki yang sama. Hirarki ini juga memungkinkan kami mengatur layanan dengan lebih fleksibel untuk kemudahan pengelolaan. Misalnya, kita membagi seluruh web, karena ini adalah pengelompokan server yang sangat besar, menjadi beberapa kelompok yang lebih kecil, yang ditunjukkan pada gambar sebagai grup1, grup2.

Dengan menghapus garis tambahan, kita dapat menulis setiap simpul gambar kita dalam bentuk yang lebih datar: grup1.web.depan, api.musik.depan, pengguna-cache.cache.

Inilah bagaimana kita sampai pada konsep β€œantrian hierarkis”. Ini memiliki nama seperti "group1.web.front". Kuota untuk sumber daya dan hak pengguna diberikan padanya. Kami akan memberikan orang dari DevOps hak untuk mengirim layanan ke antrean, dan karyawan tersebut dapat meluncurkan sesuatu dalam antrean, dan orang dari OpsDev akan memiliki hak admin, dan sekarang dia dapat mengelola antrean, menugaskan orang di sana, memberikan hak kepada orang-orang ini, dll. Layanan yang berjalan pada antrean ini akan berjalan sesuai kuota antrean. Jika kuota komputasi antrian tidak cukup untuk mengeksekusi semua layanan sekaligus, maka layanan tersebut akan dieksekusi secara berurutan, sehingga membentuk antrian itu sendiri.

Mari kita lihat lebih dekat layanannya. Suatu layanan memiliki nama yang sepenuhnya memenuhi syarat, yang selalu menyertakan nama antrian. Kemudian layanan web depan akan memiliki nama ok-web.group1.web.depan. Dan layanan server aplikasi yang diaksesnya akan dipanggil ok-app.group1.web.depan. Setiap layanan memiliki manifes, yang menentukan semua informasi yang diperlukan untuk penempatan pada mesin tertentu: berapa banyak sumber daya yang digunakan tugas ini, konfigurasi apa yang diperlukan untuk itu, berapa banyak replika yang harus ada, properti untuk menangani kegagalan layanan ini. Dan setelah layanan ditempatkan langsung pada mesin, instance-nya muncul. Mereka juga diberi nama dengan jelas - seperti nomor instans dan nama layanan: 1.ok-web.group1.web.depan, 2.ok-web.group1.web.depan, …

Ini sangat mudah: hanya dengan melihat nama container yang sedang berjalan, kita dapat langsung mengetahui banyak hal.

Sekarang mari kita lihat lebih dekat apa yang sebenarnya dilakukan oleh instance ini: tugas.

Kelas Isolasi Tugas

Semua tugas di OK (dan, mungkin, di mana pun) dapat dibagi menjadi beberapa kelompok:

  • Tugas Latensi Pendek - prod. Untuk tugas dan layanan seperti itu, penundaan respons (latensi) sangat penting, seberapa cepat setiap permintaan akan diproses oleh sistem. Contoh tugas: front web, cache, server aplikasi, penyimpanan OLTP, dll.
  • Soal perhitungan - batch. Di sini, kecepatan pemrosesan setiap permintaan spesifik tidak penting. Bagi mereka, penting berapa banyak perhitungan yang akan dilakukan tugas ini dalam jangka waktu (throughput) tertentu (panjang). Ini akan berupa tugas MapReduce, Hadoop, pembelajaran mesin, statistik.
  • Tugas latar belakang - menganggur. Untuk tugas-tugas seperti itu, baik latensi maupun throughput tidak terlalu penting. Ini mencakup berbagai pengujian, migrasi, penghitungan ulang, dan konversi data dari satu format ke format lainnya. Di satu sisi, hal tersebut mirip dengan hal yang diperhitungkan, di sisi lain, tidak terlalu menjadi masalah bagi kami seberapa cepat hal tersebut diselesaikan.

Mari kita lihat bagaimana tugas-tugas tersebut menghabiskan sumber daya, misalnya, prosesor pusat.

Tugas penundaan singkat. Tugas seperti itu akan memiliki pola konsumsi CPU yang serupa dengan ini:

One-cloud - OS tingkat pusat data di Odnoklassniki

Permintaan dari pengguna diterima untuk diproses, tugas mulai menggunakan semua inti CPU yang tersedia, memprosesnya, mengembalikan respons, menunggu permintaan berikutnya dan berhenti. Permintaan berikutnya tiba - sekali lagi kami memilih semua yang ada, menghitungnya, dan menunggu yang berikutnya.

Untuk menjamin latensi minimum untuk tugas semacam itu, kita harus mengambil sumber daya maksimum yang dikonsumsi dan mencadangkan jumlah inti yang diperlukan pada minion (mesin yang akan menjalankan tugas tersebut). Maka rumus reservasi untuk masalah kita adalah sebagai berikut:

alloc: cpu = 4 (max)

dan jika kita memiliki mesin minion dengan 16 inti, maka tepat empat tugas tersebut dapat ditempatkan di dalamnya. Kami secara khusus mencatat bahwa konsumsi rata-rata prosesor untuk tugas-tugas semacam itu seringkali sangat rendah - hal ini jelas terlihat, karena sebagian besar waktu tugas tersebut menunggu permintaan dan tidak melakukan apa pun.

Tugas perhitungan. Polanya akan sedikit berbeda:

One-cloud - OS tingkat pusat data di Odnoklassniki

Konsumsi sumber daya CPU rata-rata untuk tugas-tugas tersebut cukup tinggi. Seringkali kita ingin suatu tugas penghitungan diselesaikan dalam jangka waktu tertentu, sehingga kita perlu mencadangkan jumlah minimum prosesor yang diperlukan agar seluruh penghitungan diselesaikan dalam waktu yang dapat diterima. Rumus reservasinya akan terlihat seperti ini:

alloc: cpu = [1,*)

β€œTolong letakkan di minion yang memiliki setidaknya satu inti bebas, dan sebanyak yang ada, itu akan melahap semuanya.”

Di sini efisiensi penggunaan sudah jauh lebih baik daripada tugas dengan penundaan singkat. Namun keuntungannya akan jauh lebih besar jika Anda menggabungkan kedua jenis tugas tersebut dalam satu mesin minion dan mendistribusikan sumber dayanya saat bepergian. Ketika suatu tugas dengan penundaan singkat memerlukan prosesor, ia segera menerimanya, dan ketika sumber daya tidak lagi diperlukan, sumber daya tersebut ditransfer ke tugas komputasi, yaitu seperti ini:

One-cloud - OS tingkat pusat data di Odnoklassniki

Tetapi bagaimana cara melakukannya?

Pertama, mari kita lihat prod dan alokasinya: cpu = 4. Kita perlu mencadangkan empat core. Dalam menjalankan Docker, hal ini dapat dilakukan dengan dua cara:

  • Menggunakan opsi --cpuset=1-4, yaitu mengalokasikan empat inti spesifik pada mesin untuk tugas tersebut.
  • Menggunakan --cpuquota=400_000 --cpuperiod=100_000, tetapkan kuota untuk waktu prosesor, yaitu menunjukkan bahwa setiap 100 ms waktu nyata, tugas tersebut menghabiskan tidak lebih dari 400 ms waktu prosesor. Empat inti yang sama diperoleh.

Namun metode manakah yang cocok?

CPUset terlihat cukup menarik. Tugas ini memiliki empat inti khusus, yang berarti cache prosesor akan bekerja seefisien mungkin. Hal ini juga memiliki sisi negatifnya: kita harus melakukan tugas mendistribusikan perhitungan ke seluruh inti mesin yang tidak dimuat, bukan ke OS, dan ini adalah tugas yang tidak sepele, terutama jika kita mencoba menempatkan tugas batch pada sistem seperti itu. mesin. Pengujian telah menunjukkan bahwa opsi dengan kuota lebih cocok di sini: dengan cara ini sistem operasi memiliki lebih banyak kebebasan dalam memilih inti untuk melakukan tugas saat ini dan waktu prosesor didistribusikan dengan lebih efisien.

Mari kita cari tahu cara membuat reservasi di Docker berdasarkan jumlah minimum core. Kuota untuk tugas batch sudah tidak berlaku lagi, karena tidak perlu dibatasi maksimalnya, cukup menjamin minimalnya saja. Dan di sini pilihannya cocok docker run --cpushares.

Kami sepakat bahwa jika suatu batch memerlukan jaminan untuk setidaknya satu inti, maka kami menunjukkannya --cpushares=1024, dan jika setidaknya ada dua inti, maka kami tunjukkan --cpushares=2048. Pembagian CPU tidak mengganggu distribusi waktu prosesor dengan cara apa pun selama tersedia dalam jumlah yang cukup. Jadi, jika prod saat ini tidak menggunakan keempat intinya, tidak ada yang membatasi tugas batch, dan mereka dapat menggunakan waktu prosesor tambahan. Namun dalam situasi di mana terdapat kekurangan prosesor, jika prod telah menggunakan keempat core-nya dan telah mencapai kuota, sisa waktu prosesor akan dibagi secara proporsional ke cpushares, yaitu dalam situasi tiga core bebas, satu akan menjadi diberikan pada tugas dengan 1024 cpushares, dan dua sisanya akan diberikan pada tugas dengan 2048 cpushares.

Namun menggunakan kuota dan share saja tidak cukup. Kita perlu memastikan bahwa tugas dengan penundaan singkat mendapat prioritas dibandingkan tugas batch ketika mengalokasikan waktu prosesor. Tanpa prioritas seperti itu, tugas batch akan menghabiskan seluruh waktu prosesor pada saat dibutuhkan oleh produk. Tidak ada opsi prioritas kontainer dalam menjalankan Docker, tetapi kebijakan penjadwal CPU Linux sangat berguna. Anda dapat membacanya secara detail di sini, dan dalam kerangka artikel ini kita akan membahasnya secara singkat:

  • SCHED_OTHER
    Secara default, semua proses pengguna normal pada mesin Linux menerima.
  • SCHED_BATCH
    Dirancang untuk proses intensif sumber daya. Saat menempatkan tugas pada prosesor, apa yang disebut penalti aktivasi diperkenalkan: tugas seperti itu cenderung tidak menerima sumber daya prosesor jika saat ini sedang digunakan oleh tugas dengan SCHED_OTHER
  • SCHED_IDLE
    Proses latar belakang dengan prioritas sangat rendah, bahkan lebih rendah dari -19 yang bagus. Kami menggunakan perpustakaan sumber terbuka kami satu-nio, untuk menetapkan kebijakan yang diperlukan saat memulai penampung dengan menelepon

one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )

Namun meskipun Anda tidak memprogram dalam Java, hal yang sama dapat dilakukan dengan menggunakan perintah chrt:

chrt -i 0 $pid

Mari kita rangkum semua tingkat isolasi kita ke dalam satu tabel untuk kejelasan:

Kelas isolasi
Alokasikan contoh
Opsi menjalankan Docker
sched_setscheduler chrt*

Pecutan
prosesor = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

Sekumpulan
CPU = [1, *)
--cpushares=1024
SCHED_BATCH

Siaga
CPU= [2, *)
--cpushares=2048
SCHED_IDLE

*Jika Anda melakukan chrt dari dalam sebuah container, Anda mungkin memerlukan kemampuan sys_nice, karena secara default Docker menghapus kemampuan ini saat memulai container.

Namun tugas tidak hanya menghabiskan prosesor, tetapi juga lalu lintas, yang lebih memengaruhi latensi tugas jaringan daripada alokasi sumber daya prosesor yang salah. Oleh karena itu, tentu saja kami ingin mendapatkan gambaran lalu lintas yang persis sama. Artinya, ketika tugas prod mengirimkan beberapa paket ke jaringan, kami membatasi kecepatan maksimum (rumus alokasi: lan=[*,500mbps) ), yang dengannya prod dapat melakukan ini. Dan untuk batch kami hanya menjamin throughput minimal saja, namun tidak membatasi maksimal (rumus alokasi: lan=[10Mbps,*) ) Dalam hal ini, lalu lintas produk harus mendapat prioritas di atas tugas batch.
Di sini Docker tidak memiliki primitif apa pun yang dapat kita gunakan. Tapi itu membantu kami Kontrol Lalu Lintas Linux. Kami dapat mencapai hasil yang diinginkan dengan bantuan disiplin Kurva Pelayanan Adil Hierarki. Dengan bantuannya, kami membedakan dua kelas lalu lintas: produk berprioritas tinggi dan batch/idle berprioritas rendah. Hasilnya, konfigurasi trafik keluar adalah seperti ini:

One-cloud - OS tingkat pusat data di Odnoklassniki

di sini 1:0 adalah "root qdisc" dari disiplin hsfc; 1:1 - kelas anak hsfc dengan total batas bandwidth 8 Gbit/s, di mana kelas anak dari semua container ditempatkan; 1:2 - kelas anak hsfc umum untuk semua tugas batch dan idle dengan batas "dinamis", yang akan dibahas di bawah. Kelas turunan hsfc yang tersisa adalah kelas khusus untuk container prod yang sedang berjalan dengan batasan sesuai dengan manifesnya - 450 dan 400 Mbit/s. Setiap kelas hsfc diberi antrian qdisc fq atau fq_codel, bergantung pada versi kernel Linux, untuk menghindari kehilangan paket selama lonjakan lalu lintas.

Biasanya, disiplin tc berfungsi untuk memprioritaskan lalu lintas keluar saja. Namun kami juga ingin memprioritaskan lalu lintas masuk - lagipula, beberapa tugas batch dapat dengan mudah memilih seluruh saluran masuk, menerima, misalnya, sejumlah besar data masukan untuk peta&kurangi. Untuk ini kami menggunakan modul jikab, yang membuat antarmuka virtual ifbX untuk setiap antarmuka jaringan dan mengalihkan lalu lintas masuk dari antarmuka ke lalu lintas keluar di ifbX. Selanjutnya, untuk ifbX, semua disiplin ilmu yang sama berfungsi untuk mengontrol lalu lintas keluar, yang konfigurasi hsfcnya akan sangat mirip:

One-cloud - OS tingkat pusat data di Odnoklassniki

Selama percobaan, kami menemukan bahwa hsfc menunjukkan hasil terbaik ketika lalu lintas batch/idle kelas 1:2 non-prioritas dibatasi pada mesin minion hingga tidak lebih dari jalur bebas tertentu. Jika tidak, lalu lintas non-prioritas akan berdampak terlalu besar pada latensi tugas produksi. miniond menentukan jumlah bandwidth gratis saat ini setiap detik, mengukur konsumsi lalu lintas rata-rata dari semua tugas prod dari minion tertentu One-cloud - OS tingkat pusat data di Odnoklassniki dan menguranginya dari bandwidth antarmuka jaringan One-cloud - OS tingkat pusat data di Odnoklassniki dengan margin kecil, mis.

One-cloud - OS tingkat pusat data di Odnoklassniki

Pita ditentukan secara independen untuk lalu lintas masuk dan keluar. Dan sesuai dengan nilai baru, miniond mengkonfigurasi ulang batas kelas non-prioritas 1:2.

Jadi, kami mengimplementasikan ketiga kelas isolasi: prod, batch, dan idle. Kelas-kelas ini sangat mempengaruhi karakteristik kinerja tugas. Oleh karena itu, kami memutuskan untuk menempatkan atribut ini di bagian atas hierarki, sehingga ketika melihat nama antrian hierarki, akan segera terlihat jelas apa yang sedang kita hadapi:

One-cloud - OS tingkat pusat data di Odnoklassniki

Semua teman kita jaringan ΠΈ musik bagian depan kemudian ditempatkan dalam hierarki di bawah prod. Misalnya, di bawah batch, mari kita tempatkan layanannya katalog musik, yang secara berkala menyusun katalog lagu dari kumpulan file mp3 yang diunggah ke Odnoklassniki. Contoh layanan dalam kondisi idle adalah transformator musik, yang menormalkan tingkat volume musik.

Dengan menghapus baris tambahan lagi, kita dapat menulis nama layanan kita lebih datar dengan menambahkan kelas isolasi tugas di akhir nama layanan lengkap: web.depan.prod, katalog.musik.batch, transformator.musik.idle.

Dan sekarang, melihat nama layanannya, kita memahami tidak hanya fungsi apa yang dijalankannya, tetapi juga kelas isolasinya, yang berarti kekritisannya, dll.

Semuanya bagus, tapi ada satu kenyataan pahit. Tidak mungkin untuk sepenuhnya mengisolasi tugas-tugas yang berjalan pada satu mesin.

Apa yang berhasil kami capai: jika konsumsi secara batch intensif hanya sumber daya CPU, maka penjadwal CPU Linux bawaan melakukan tugasnya dengan sangat baik, dan praktis tidak ada dampak pada tugas prod. Tetapi jika tugas batch ini mulai aktif bekerja dengan memori, maka pengaruh timbal balik sudah muncul. Hal ini terjadi karena tugas prod β€œdihapus” dari cache memori prosesor - akibatnya, cache yang hilang meningkat, dan prosesor memproses tugas prod lebih lambat. Tugas batch seperti itu dapat meningkatkan latensi kontainer produk kami sebesar 10%.

Mengisolasi lalu lintas bahkan lebih sulit karena fakta bahwa kartu jaringan modern memiliki antrian paket internal. Jika paket dari tugas batch sampai di sana terlebih dahulu, maka paket tersebut akan menjadi yang pertama ditransmisikan melalui kabel, dan tidak ada yang dapat dilakukan untuk mengatasinya.

Selain itu, sejauh ini kami hanya berhasil memecahkan masalah prioritas lalu lintas TCP: pendekatan hsfc tidak berfungsi untuk UDP. Dan bahkan dalam kasus lalu lintas TCP, jika tugas batch menghasilkan banyak lalu lintas, hal ini juga memberikan peningkatan sekitar 10% dalam penundaan tugas prod.

toleransi kesalahan

Salah satu tujuan pengembangan one-cloud adalah untuk meningkatkan toleransi kesalahan Odnoklassniki. Oleh karena itu, selanjutnya saya ingin mempertimbangkan secara lebih rinci kemungkinan skenario kegagalan dan kecelakaan. Mari kita mulai dengan skenario sederhana - kegagalan container.

Wadah itu sendiri bisa gagal dalam beberapa cara. Ini bisa berupa eksperimen, bug, atau kesalahan dalam manifes, yang menyebabkan tugas prod mulai menghabiskan lebih banyak sumber daya daripada yang ditunjukkan dalam manifes. Kami mempunyai sebuah kasus: seorang pengembang mengimplementasikan sebuah algoritma yang kompleks, mengerjakannya ulang berkali-kali, terlalu memikirkan dirinya sendiri dan menjadi sangat bingung sehingga akhirnya masalah tersebut terulang kembali dengan cara yang sangat tidak sepele. Dan karena tugas prod memiliki prioritas lebih tinggi daripada tugas lainnya pada minion yang sama, tugas tersebut mulai menghabiskan semua sumber daya prosesor yang tersedia. Dalam situasi ini, isolasi, atau lebih tepatnya kuota waktu CPU, menyelamatkan situasi. Jika suatu tugas diberi kuota, tugas tersebut tidak akan memakan lebih banyak. Oleh karena itu, tugas batch dan produk lainnya yang dijalankan pada mesin yang sama tidak memperhatikan apa pun.

Kemungkinan masalah kedua adalah jatuhnya kontainer. Dan di sini kebijakan restart menyelamatkan kita, semua orang mengetahuinya, Docker sendiri melakukan tugasnya dengan baik. Hampir semua tugas prod memiliki kebijakan selalu restart. Terkadang kami menggunakan on_failure untuk tugas batch atau untuk men-debug container produk.

Apa yang dapat Anda lakukan jika seluruh minion tidak tersedia?

Tentunya, jalankan container di komputer lain. Bagian yang menarik di sini adalah apa yang terjadi pada alamat IP yang ditetapkan ke container.

Kami dapat menetapkan alamat IP yang sama pada container dengan mesin minion yang menjalankan container ini. Kemudian, ketika kontainer diluncurkan di komputer lain, alamat IP-nya berubah, dan semua klien harus memahami bahwa kontainer telah dipindahkan, dan sekarang mereka harus pergi ke alamat lain, yang memerlukan layanan Service Discovery terpisah.

Penemuan Layanan itu nyaman. Ada banyak solusi di pasar dengan berbagai tingkat toleransi kesalahan untuk mengatur registri layanan. Seringkali solusi seperti itu mengimplementasikan logika penyeimbang beban, menyimpan konfigurasi tambahan dalam bentuk penyimpanan KV, dll.
Namun, kami ingin menghindari kebutuhan untuk menerapkan registri terpisah, karena ini berarti memperkenalkan sistem penting yang digunakan oleh semua layanan dalam produksi. Ini berarti bahwa ini adalah titik kegagalan potensial, dan Anda perlu memilih atau mengembangkan solusi yang sangat toleran terhadap kesalahan, yang jelas sangat sulit, memakan waktu dan mahal.

Dan satu lagi kelemahan besar: agar infrastruktur lama kami dapat berfungsi dengan infrastruktur baru, kami harus benar-benar menulis ulang semua tugas untuk menggunakan semacam sistem Service Discovery. Ada BANYAK pekerjaan yang harus dilakukan, dan di beberapa tempat hal ini hampir tidak mungkin dilakukan jika menyangkut perangkat tingkat rendah yang bekerja pada tingkat kernel OS atau langsung dengan perangkat keras. Implementasi fungsi ini menggunakan pola solusi yang sudah ada, seperti sespan di beberapa tempat berarti beban tambahan, di tempat lain - komplikasi operasi dan skenario kegagalan tambahan. Kami tidak ingin memperumit masalah, jadi kami memutuskan untuk menjadikan penggunaan Service Discovery sebagai opsional.

Dalam satu cloud, IP mengikuti container, yaitu setiap instance tugas memiliki alamat IP sendiri. Alamat ini bersifat β€œstatis”: alamat ini ditetapkan ke setiap instance saat layanan pertama kali dikirim ke cloud. Jika suatu layanan memiliki jumlah instance yang berbeda selama masa pakainya, maka pada akhirnya layanan tersebut akan diberi alamat IP sebanyak jumlah maksimum instance.

Selanjutnya, alamat-alamat ini tidak berubah: alamat-alamat ini ditetapkan satu kali dan terus ada sepanjang umur layanan dalam produksi. Alamat IP mengikuti kontainer di seluruh jaringan. Jika container dipindahkan ke minion lain, maka alamatnya akan mengikutinya.

Oleh karena itu, pemetaan nama layanan ke daftar alamat IP-nya sangat jarang berubah. Jika Anda melihat kembali nama-nama service instance yang kami sebutkan di awal artikel (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), kita akan melihat bahwa mereka menyerupai FQDN yang digunakan dalam DNS. Benar sekali, untuk memetakan nama instance layanan ke alamat IP-nya, kami menggunakan protokol DNS. Selain itu, DNS ini mengembalikan semua alamat IP yang dicadangkan dari semua kontainer - baik yang berjalan maupun yang dihentikan (misalkan tiga replika digunakan, dan kita memiliki lima alamat yang dicadangkan di sana - kelima alamat tersebut akan dikembalikan). Klien, setelah menerima informasi ini, akan mencoba membuat koneksi dengan kelima replika - dan dengan demikian menentukan replika mana yang berfungsi. Opsi untuk menentukan ketersediaan ini jauh lebih dapat diandalkan; tidak melibatkan DNS atau Service Discovery, yang berarti tidak ada masalah sulit untuk dipecahkan dalam memastikan relevansi informasi dan toleransi kesalahan sistem ini. Selain itu, dalam layanan penting yang menjadi sandaran pengoperasian seluruh portal, kita tidak dapat menggunakan DNS sama sekali, tetapi cukup memasukkan alamat IP ke dalam konfigurasi.

Menerapkan transfer IP di belakang container bukanlah hal yang mudah - dan kita akan melihat cara kerjanya dengan contoh berikut:

One-cloud - OS tingkat pusat data di Odnoklassniki

Katakanlah one-cloud master memberikan perintah kepada minion M1 untuk dijalankan 1.ok-web.group1.web.front.prod dengan alamat 1.1.1.1. Bekerja pada antek BURUNG, yang mengiklankan alamat ini ke server khusus reflektor rute. Yang terakhir memiliki sesi BGP dengan perangkat keras jaringan, di mana rute alamat 1.1.1.1 pada M1 diterjemahkan. M1 merutekan paket di dalam container menggunakan Linux. Ada tiga server reflektor rute, karena ini adalah bagian yang sangat penting dari infrastruktur satu cloud - tanpa server tersebut, jaringan dalam satu cloud tidak akan berfungsi. Kami menempatkannya di rak yang berbeda, jika mungkin ditempatkan di ruangan berbeda di pusat data, untuk mengurangi kemungkinan ketiga kegagalan pada saat yang bersamaan.

Sekarang mari kita asumsikan bahwa koneksi antara one-cloud master dan minion M1 terputus. Master satu cloud sekarang akan bertindak dengan asumsi bahwa M1 telah gagal total. Artinya, ia akan memberikan perintah kepada minion M2 untuk meluncurkannya web.grup1.web.depan.prod dengan alamat yang sama 1.1.1.1. Sekarang kami memiliki dua rute yang bertentangan di jaringan untuk 1.1.1.1: di M1 dan di M2. Untuk menyelesaikan konflik tersebut, kami menggunakan Multi Exit Discriminator, yang ditentukan dalam pengumuman BGP. Ini adalah angka yang menunjukkan bobot rute yang diiklankan. Di antara rute-rute yang bertentangan, rute dengan nilai MED yang lebih rendah akan dipilih. Master satu cloud mendukung MED sebagai bagian integral dari alamat IP kontainer. Untuk pertama kalinya, alamat ditulis dengan MED = 1 yang cukup besar. Dalam situasi transfer kontainer darurat seperti itu, master mengurangi MED, dan M000 sudah menerima perintah untuk mengiklankan alamat 000 dengan MED = 2. Contoh yang berjalan pada M1.1.1.1 akan tetap pada kasus ini tidak ada koneksi, dan nasib selanjutnya tidak terlalu menarik bagi kita sampai koneksi dengan master dipulihkan, ketika dia akan dihentikan seperti pengambilan lama.

Kecelakaan

Semua sistem manajemen pusat data selalu menangani kegagalan kecil dengan baik. Luapan kontainer adalah hal yang biasa terjadi hampir di semua tempat.

Mari kita lihat bagaimana kita menangani keadaan darurat, seperti pemadaman listrik di satu atau lebih ruangan di pusat data.

Apa arti kecelakaan bagi sistem manajemen pusat data? Pertama-tama, ini adalah kegagalan besar-besaran yang terjadi satu kali pada banyak mesin, dan sistem kontrol perlu memigrasikan banyak container secara bersamaan. Namun jika bencananya berskala sangat besar, maka bisa saja seluruh tugas tidak dapat dialokasikan kembali ke minion lain, karena kapasitas sumber daya pusat data turun di bawah 100% beban.

Seringkali kecelakaan disertai dengan kegagalan lapisan kendali. Hal ini dapat terjadi karena kegagalan peralatannya, namun lebih sering karena kecelakaan tidak diuji, dan lapisan kontrol itu sendiri jatuh karena meningkatnya beban.

Apa yang dapat Anda lakukan terhadap semua ini?

Migrasi massal berarti adanya sejumlah besar aktivitas, migrasi, dan penerapan yang terjadi di infrastruktur. Setiap migrasi mungkin memerlukan beberapa waktu untuk mengirimkan dan membongkar image container ke minion, meluncurkan dan menginisialisasi container, dll. Oleh karena itu, sebaiknya tugas yang lebih penting diluncurkan sebelum tugas yang kurang penting.

Mari kita lihat kembali hierarki layanan yang kita kenal dan coba putuskan tugas mana yang ingin kita jalankan terlebih dahulu.

One-cloud - OS tingkat pusat data di Odnoklassniki

Tentu saja, ini adalah proses yang terlibat langsung dalam pemrosesan permintaan pengguna, yaitu prod. Kami menunjukkan ini dengan prioritas penempatan β€” nomor yang dapat ditetapkan ke antrian. Jika antrian mempunyai prioritas lebih tinggi, layanannya ditempatkan terlebih dahulu.

Pada produk kami menetapkan prioritas yang lebih tinggi, 0; secara batch - sedikit lebih rendah, 100; saat idle - bahkan lebih rendah lagi, 200. Prioritas diterapkan secara hierarki. Semua tugas yang lebih rendah dalam hierarki akan memiliki prioritas yang sesuai. Jika kita ingin cache di dalam prod diluncurkan sebelum frontend, maka kita tetapkan prioritas ke cache = 0 dan ke subqueue depan = 1. Jika, misalnya, kita ingin portal utama diluncurkan dari depan terlebih dahulu, dan musik depan saja lalu, kita dapat menetapkan prioritas yang lebih rendah untuk yang terakhir - 10.

Masalah berikutnya adalah kurangnya sumber daya. Jadi, sejumlah besar peralatan, seluruh ruang pusat data, gagal, dan kami meluncurkan kembali begitu banyak layanan sehingga sekarang sumber daya tidak cukup untuk semua orang. Anda perlu memutuskan tugas mana yang harus dikorbankan agar layanan penting utama tetap berjalan.

One-cloud - OS tingkat pusat data di Odnoklassniki

Tidak seperti prioritas penempatan, kami tidak dapat mengorbankan semua tugas batch tanpa pandang bulu; beberapa di antaranya penting untuk pengoperasian portal. Oleh karena itu, kami telah menyoroti secara terpisah prioritas pendahuluan tugas. Saat ditempatkan, tugas dengan prioritas lebih tinggi dapat mendahului, yaitu menghentikan, tugas dengan prioritas lebih rendah jika tidak ada lagi minion gratis. Dalam hal ini, tugas dengan prioritas rendah mungkin akan tetap tidak ditempatkan, yaitu tidak akan ada lagi minion yang cocok untuk tugas tersebut dengan sumber daya gratis yang cukup.

Dalam hierarki kami, sangat mudah untuk menentukan prioritas preemption sehingga tugas prod dan batch mendahului atau menghentikan tugas-tugas yang menganggur, tetapi tidak satu sama lain, dengan menentukan prioritas untuk idle sama dengan 200. Sama seperti dalam kasus prioritas penempatan, kami dapat menggunakan hierarki kami untuk menjelaskan aturan yang lebih kompleks. Sebagai contoh, mari kita tunjukkan bahwa kita mengorbankan fungsi musik jika kita tidak memiliki sumber daya yang cukup untuk portal web utama, dengan menetapkan prioritas untuk node terkait lebih rendah: 10.

Seluruh kecelakaan DC

Mengapa seluruh pusat data bisa gagal? Elemen. Adalah postingan yang bagus badai mempengaruhi pekerjaan pusat data. Unsur-unsur tersebut dapat dianggap sebagai tunawisma yang pernah membakar optik di manifold, dan pusat data benar-benar kehilangan kontak dengan situs lain. Penyebab kegagalan juga bisa menjadi faktor manusia: operator akan mengeluarkan perintah sedemikian rupa sehingga seluruh pusat data akan tumbang. Hal ini bisa saja terjadi karena adanya bug yang besar. Secara umum, keruntuhan pusat data bukanlah hal yang jarang terjadi. Ini terjadi pada kami setiap beberapa bulan sekali.

Dan inilah yang kami lakukan untuk mencegah siapa pun men-tweet #alive.

Strategi pertama adalah isolasi. Setiap instance satu cloud diisolasi dan hanya dapat mengelola mesin di satu pusat data. Artinya, hilangnya cloud karena bug atau perintah operator yang salah berarti hilangnya satu pusat data saja. Kami siap untuk ini: kami memiliki kebijakan redundansi di mana replika aplikasi dan data ditempatkan di semua pusat data. Kami menggunakan database yang toleran terhadap kesalahan dan secara berkala menguji kegagalan.
Karena saat ini kami memiliki empat pusat data, itu berarti empat contoh one-cloud yang terpisah dan sepenuhnya terisolasi.

Pendekatan ini tidak hanya melindungi terhadap kegagalan fisik, namun juga dapat melindungi terhadap kesalahan operator.

Apa lagi yang bisa dilakukan dengan faktor manusia? Ketika seorang operator memberikan perintah aneh atau berpotensi berbahaya pada cloud, dia mungkin tiba-tiba diminta untuk memecahkan masalah kecil untuk melihat seberapa baik pemikirannya. Misalnya, jika ini adalah semacam penghentian massal banyak replika atau hanya perintah aneh - mengurangi jumlah replika atau mengubah nama gambar, dan bukan hanya nomor versi di manifes baru.

One-cloud - OS tingkat pusat data di Odnoklassniki

Hasil

Fitur khas dari satu cloud:

  • Skema penamaan hierarkis dan visual untuk layanan dan kontainer, yang memungkinkan Anda dengan cepat mengetahui apa tugasnya, apa hubungannya dan bagaimana cara kerjanya, serta siapa yang bertanggung jawab atasnya.
  • Kami menerapkan milik kami teknik menggabungkan produk dan batch-tugas pada minion untuk meningkatkan efisiensi berbagi mesin. Alih-alih cpuset kami menggunakan kuota CPU, pembagian, kebijakan penjadwal CPU, dan QoS Linux.
  • Tidak mungkin untuk sepenuhnya mengisolasi kontainer yang berjalan pada mesin yang sama, namun pengaruh timbal baliknya tetap dalam 20%.
  • Mengorganisasikan layanan ke dalam hierarki membantu penggunaan pemulihan bencana otomatis prioritas penempatan dan preemption.

Faq

Mengapa kita tidak mengambil solusi yang sudah jadi?

  • Kelas isolasi tugas yang berbeda memerlukan logika yang berbeda ketika ditempatkan pada minion. Jika tugas prod dapat ditempatkan hanya dengan memesan sumber daya, maka tugas batch dan idle harus ditempatkan, melacak pemanfaatan sumber daya aktual pada mesin minion.
  • Kebutuhan untuk memperhitungkan sumber daya yang dikonsumsi oleh tugas, seperti:
    • bandwidth jaringan;
    • jenis dan β€œspindel” disk.
  • Kebutuhan untuk menunjukkan prioritas layanan selama tanggap darurat, hak dan kuota komando sumber daya, yang diselesaikan dengan menggunakan antrian hierarki dalam satu cloud.
  • Perlunya penamaan kontainer secara manusiawi untuk mengurangi waktu respons terhadap kecelakaan dan insiden
  • Ketidakmungkinan penerapan Service Discovery secara luas satu kali saja; kebutuhan untuk hidup berdampingan untuk waktu yang lama dengan tugas-tugas yang dihosting di host perangkat keras - sesuatu yang diselesaikan dengan alamat IP "statis" yang mengikuti kontainer, dan, sebagai konsekuensinya, kebutuhan akan integrasi unik dengan infrastruktur jaringan yang besar.

Semua fungsi ini memerlukan modifikasi signifikan terhadap solusi yang ada agar sesuai dengan kami, dan setelah menilai jumlah pekerjaan, kami menyadari bahwa kami dapat mengembangkan solusi kami sendiri dengan biaya tenaga kerja yang kira-kira sama. Namun solusi Anda akan lebih mudah dioperasikan dan dikembangkan - tidak mengandung abstraksi yang tidak perlu yang mendukung fungsionalitas yang tidak kita perlukan.

Kepada mereka yang membaca baris terakhir, terima kasih atas kesabaran dan perhatian Anda!

Sumber: www.habr.com

Tambah komentar