Batas CPU dan pembatasan agresif di Kubernetes

Catatan perev. : Kisah peringatan Omio, agregator perjalanan Eropa, membawa pembaca dari teori dasar ke seluk-beluk praktis praktis konfigurasi Kubernetes. Keakraban dengan kasus-kasus seperti itu membantu tidak hanya untuk memperluas wawasan seseorang, tetapi juga untuk mencegah masalah-masalah yang tidak sepele.



Pernahkah Anda menemukan fakta bahwa aplikasi "macet" pada tempatnya, berhenti merespons permintaan pemeriksaan kesehatan, dan Anda tidak dapat memahami alasan perilaku ini? Satu penjelasan yang mungkin adalah batas kuota untuk sumber daya CPU. Dia akan dibahas dalam artikel ini.

TL; DR:
Kami sangat menyarankan Anda memilih keluar dari batas CPU di Kubernetes (atau menonaktifkan kuota CFS di Kubelet) jika Anda menggunakan versi kernel Linux dengan kesalahan kuota CFS. Pada intinya adaBug yang serius dan terkenal yang menyebabkan pelambatan dan penundaan yang berlebihan
.

Di Omio, seluruh infrastruktur dikelola oleh Kubernetes . Semua muatan negara dan stateless kami bekerja secara eksklusif di Kubernetes (kami menggunakan Google Kubernetes Engine). Dalam enam bulan terakhir, kami mulai mengamati perlambatan acak. Aplikasi membeku atau berhenti merespons pemeriksaan kesehatan, kehilangan koneksi ke jaringan, dll. Perilaku seperti itu telah lama membingungkan kami, dan, akhirnya, kami memutuskan untuk menangani masalah ini dengan cermat.

Ringkasan artikel:

  • Beberapa kata tentang wadah dan Kubernet;
  • Bagaimana permintaan dan batasan CPU diimplementasikan;
  • Bagaimana batas CPU bekerja di lingkungan multi-core;
  • Cara melacak throttling CPU;
  • Memecahkan masalah dan nuansa.

Beberapa kata tentang wadah dan Kubernet


Kubernetes, pada kenyataannya, adalah standar modern di dunia infrastruktur. Tugas utamanya adalah mengatur wadah.

Wadah


Di masa lalu, kami harus membuat artefak seperti Java JARs / WARs, Python Eggs, atau executable untuk peluncuran berikutnya di server. Namun, untuk membuatnya berfungsi, mereka harus melakukan pekerjaan tambahan: menginstal runtime (Java / Python), menempatkan file yang diperlukan di tempat yang tepat, memastikan kompatibilitas dengan versi spesifik dari sistem operasi, dll. Dengan kata lain, Anda harus memperhatikan manajemen konfigurasi (yang sering menyebabkan pertentangan antara pengembang dan administrator sistem).

Wadah telah mengubah segalanya.Sekarang gambar wadah bertindak sebagai artefak. Ini dapat direpresentasikan sebagai jenis file executable yang diperluas yang tidak hanya berisi program, tetapi juga runtime penuh (Java / Python / ...), serta file / paket yang diperlukan yang sudah diinstal sebelumnya dan siap dijalankan. Kontainer dapat digunakan dan dijalankan di berbagai server tanpa langkah tambahan.

Selain itu, kontainer bekerja di lingkungan kotak pasir mereka sendiri. Mereka memiliki adapter jaringan virtual mereka sendiri, sistem file mereka sendiri dengan akses terbatas, hierarki proses mereka sendiri, pembatasan mereka sendiri pada CPU dan memori, dll. Semua ini diwujudkan berkat subsistem khusus dari kernel Linux - namespaces (namespace).

Kubernetes


Seperti yang dinyatakan sebelumnya, Kubernetes adalah wadah orkestra. Ini berfungsi sebagai berikut: Anda memberinya kumpulan mesin, dan kemudian berkata: "Hai Kubernetes, luncurkan sepuluh instance dari wadah saya dengan masing-masing 2 prosesor dan 3 GB memori, dan biarkan operasional!" Kubernetes mengurus sisanya. Dia akan menemukan kapasitas gratis, meluncurkan wadah, dan memulai kembali jika perlu, meluncurkan pembaruan saat mengubah versi, dll. Bahkan, Kubernetes memungkinkan Anda untuk abstrak dari komponen perangkat keras dan membuat seluruh variasi sistem cocok untuk penerapan dan pengoperasian aplikasi.


Kubernet dari sudut pandang orang awam yang sederhana

Apa itu permintaan dan batasan di Kubernetes


Oke, kami sudah menemukan wadah dan Kubernet. Kita juga tahu bahwa beberapa kontainer bisa berada di mesin yang sama.

Anda dapat menggambar analogi dengan apartemen komunal. Ruang yang luas diambil (mobil / node) dan disewakan ke beberapa penyewa (wadah). Kubernetes bertindak sebagai makelar. Muncul pertanyaan, bagaimana menjaga penyewa dari konflik satu sama lain? Bagaimana jika salah satu dari mereka, misalnya, memutuskan untuk menempati kamar mandi selama setengah hari?

Di sinilah permintaan dan batasan ikut berperan. Permintaan CPU hanya untuk tujuan perencanaan. Ini adalah sesuatu seperti "daftar keinginan" wadah, dan digunakan untuk memilih simpul yang paling cocok. Pada saat yang sama, Batas CPU dapat dibandingkan dengan leasing - segera setelah kami mengambil node untuk container, itutidak akan dapat melampaui batas yang ditetapkan. Dan di sini muncul masalah ...

Bagaimana permintaan dan batasan diterapkan di Kubernetes


Kubernetes menggunakan mekanisme pelambatan kernel (skipping clock) untuk mengimplementasikan batas CPU. Jika aplikasi melebihi batas, pembatasan diaktifkan (mis. Menerima siklus CPU lebih sedikit). Permintaan dan batasan memori diatur secara berbeda, sehingga lebih mudah dideteksi. Untuk melakukan ini, cukup memeriksa status restart pod terakhir: apakah "OOMKilled". Dengan pembatasan CPU, semuanya tidak begitu sederhana, karena K8 hanya membuat metrik yang tersedia untuk digunakan, bukan cgroup.

Permintaan CPU



Bagaimana permintaan CPU diimplementasikan

Untuk kesederhanaan, mari kita lihat proses menggunakan contoh mesin dengan CPU 4-core.

K8 menggunakan mekanisme cgroup untuk mengontrol alokasi sumber daya (memori dan prosesor). Model hierarkis tersedia untuknya: seorang keturunan mewarisi batas-batas kelompok induk. Detail distribusi disimpan dalam sistem file virtual ( /sys/fs/cgroup). Dalam hal prosesor, ini /sys/fs/cgroup/cpu,cpuacct/*.

K8 menggunakan file cpu.shareuntuk mengalokasikan sumber daya prosesor. Dalam kasus kami, grup kontrol root menerima 4096 bagian sumber daya CPU - 100% dari daya prosesor yang tersedia (1 inti = 1024; ini adalah nilai tetap). Grup root mendistribusikan sumber daya secara proporsional tergantung pada bagian keturunan yang ditentukan dalamcpu.share, dan mereka, pada gilirannya, melakukan hal yang sama kepada keturunan mereka, dll. Biasanya Kubernetes akar kelompok kontrol node memiliki tiga anak: system.slice, user.slicedan kubepods. Dua subkelompok pertama digunakan untuk mendistribusikan sumber daya antara beban sistem kritis dan program pengguna di luar K8. Yang terakhir - - kubepodsdibuat oleh Kubernetes untuk mendistribusikan sumber daya antar pod.

Diagram di atas menunjukkan bahwa subkelompok pertama dan kedua menerima 1024 saham, dengan 4096 saham dialokasikan untuk subkelompok kuberpod . Bagaimana ini mungkin: setelah semua, grup root hanya memiliki 4.096 saham tersedia, dan jumlah saham keturunannya secara signifikan melebihi jumlah ini ( 6144)? Faktanya adalah bahwa nilainya masuk akal, sehingga Linux Scheduler (CFS) menggunakannya untuk mengalokasikan sumber daya CPU secara proporsional. Dalam kasus kami, dua kelompok pertama menerima 680 saham riil (16,6% dari 4096), dan kubepod menerima sisanya 2736 saham. Dalam hal downtime, dua kelompok pertama tidak akan menggunakan sumber daya yang dialokasikan.

Untungnya, scheduler memiliki mekanisme untuk menghindari hilangnya sumber daya CPU yang tidak digunakan. Ini mentransfer kapasitas "idle" ke kumpulan global, dari mana mereka didistribusikan di antara kelompok-kelompok yang membutuhkan kapasitas prosesor tambahan (transfer terjadi dalam batch untuk menghindari pembulatan kerugian). Metode serupa berlaku untuk semua keturunan keturunan.

Mekanisme ini memastikan distribusi daya prosesor yang adil dan memastikan bahwa tidak ada proses yang "mencuri" sumber daya dari orang lain.

Batas CPU


Terlepas dari kenyataan bahwa konfigurasi batas dan permintaan dalam K8 terlihat serupa, implementasinya secara fundamental berbeda: ini adalah bagian yang paling menyesatkan dan paling tidak terdokumentasi.

K8 menggunakan mekanisme kuota CFS untuk menerapkan batas. Pengaturan mereka ditentukan dalam file cfs_period_usdan cfs_quota_usdalam direktori cgroup (file tersebut juga terletak di sana cpu.share).

Sebaliknya cpu.share, kuota didasarkan pada periode waktu , dan bukan pada kekuatan prosesor yang tersedia. cfs_period_usmenetapkan durasi periode (era) - selalu 100.000 ฮผs (100 ms). K8 memiliki kemampuan untuk mengubah nilai ini, tetapi saat ini hanya tersedia dalam versi alpha. Penjadwal menggunakan era untuk me-restart kuota yang digunakan. File keduacfs_quota_us, menetapkan waktu yang tersedia (kuota) di setiap era. Harap dicatat bahwa ini juga ditunjukkan dalam mikrodetik. Kuota dapat melebihi durasi era; dengan kata lain, bisa lebih dari 100 ms.

Mari kita lihat dua skenario pada mesin 16-core (tipe komputer paling umum yang kita miliki di Omio):


Skenario 1: 2 utas dan batas 200 ms. Tanpa pelambatan


Skenario 2: 10 mengalir dan batas 200 ms. Pelambatan dimulai setelah 20 ms, akses ke sumber daya prosesor dilanjutkan setelah 80 ms lainnya.

Misalkan Anda menetapkan batas CPU menjadi 2 core; Kubernetes akan menerjemahkan nilai ini menjadi 200 ms. Ini berarti bahwa wadah dapat menggunakan waktu CPU maksimum 200 ms tanpa pembatasan.

Dan di sini kesenangan dimulai. Seperti disebutkan di atas, kuota yang tersedia adalah 200 ms. Jika Anda memiliki sepuluh utas yang berjalan secara paralel pada mesin 12-inti (lihat ilustrasi untuk skenario 2), sementara semua polong lainnya menganggur, kuota akan habis hanya dalam 20 ms (karena 10 * 20 ms = 200 ms), dan semua utas pod ini adalah throttle untuk 80 ms berikutnya. Bug scheduler yang sudah disebutkan memperparah situasi , karena terjadi pelambatan yang berlebihan dan kontainer bahkan tidak dapat mengerjakan kuota yang ada.

Bagaimana cara mengevaluasi pembatasan dalam polong?


Cukup buka pod dan jalankan cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods - jumlah total periode penjadwal;
  • nr_throttled- jumlah periode yang dibatasi dalam komposisi nr_periods;
  • throttled_time - Waktu dipercepat kumulatif dalam nanodetik.



Apa yang sebenarnya sedang terjadi?


Hasilnya, kami mendapatkan pelambatan tinggi di semua aplikasi. Terkadang satu setengah kali lebih kuat dari yang dihitung!

Hal ini menyebabkan berbagai kesalahan - kegagalan pemeriksaan kesiapan, hang kontainer, putus koneksi jaringan, timeout di dalam panggilan layanan. Pada akhirnya, ini berarti latensi yang meningkat dan kesalahan yang meningkat.

Keputusan dan konsekuensi


Semuanya sederhana di sini. Kami mengabaikan batas CPU dan mulai memperbarui kernel OS dalam kelompok ke versi terbaru di mana bug diperbaiki. Jumlah kesalahan (HTTP 5xx) dalam layanan kami segera turun secara signifikan:

Kesalahan HTTP 5xx



HTTP 5xx kesalahan satu layanan penting

Waktu respons P95



Penundaan Permintaan Layanan Penting, persentil ke-95

Biaya operasional



Jumlah jam yang dihabiskan

Apa yang menangkap?


Seperti yang dinyatakan di awal artikel:

Anda dapat menggambar analogi dengan apartemen komunal ... Kubernetes bertindak sebagai makelar. Tetapi bagaimana menjaga penyewa dari konflik satu sama lain? Bagaimana jika salah satu dari mereka, misalnya, memutuskan untuk menempati kamar mandi selama setengah hari?

Itu tangkapannya. Satu wadah lalai dapat menyerap semua sumber daya prosesor yang tersedia di mesin. Jika Anda memiliki tumpukan aplikasi yang cerdas (misalnya, JVM, Go, Node VM dikonfigurasi dengan benar), maka ini bukan masalah: Anda dapat bekerja dalam kondisi seperti itu untuk waktu yang lama. Tetapi jika aplikasi dioptimalkan dengan buruk atau tidak dioptimalkan sama sekali ( FROM java:latest), situasinya mungkin lepas kendali. Kami di Omio memiliki Dockerfiles dasar otomatis dengan pengaturan default yang memadai untuk tumpukan bahasa utama, jadi tidak ada masalah seperti itu.

Kami menyarankan Anda memantau metrik USE (penggunaan, saturasi, dan kesalahan), penundaan API, dan tingkat kesalahan. Pastikan hasilnya seperti yang diharapkan.

Referensi


Itu adalah kisah kami. Materi berikut sangat membantu untuk memahami apa yang terjadi:


Pelaporan Kesalahan Kubernetes:


Pernahkah Anda mengalami masalah serupa dalam praktik Anda atau memiliki pengalaman dengan pelambatan di lingkungan produksi yang kemas? Bagikan kisah Anda di komentar!

PS dari penerjemah


Baca juga di blog kami:


All Articles