Batasan CPU dan pembatasan agresif di Kubernetes

Catatan. terjemahan: Sejarah Omio yang membuka mataβ€”agregator perjalanan Eropaβ€”membawa pembaca mulai dari teori dasar hingga seluk-beluk praktis yang menarik dari konfigurasi Kubernetes. Keakraban dengan kasus-kasus seperti itu membantu tidak hanya memperluas wawasan Anda, tetapi juga mencegah masalah-masalah yang tidak sepele.

Batasan CPU dan pembatasan agresif di Kubernetes

Pernahkah aplikasi Anda terhenti, berhenti merespons pemeriksaan kesehatan, dan tidak tahu alasannya? Salah satu penjelasan yang mungkin terkait dengan batas kuota sumber daya CPU. Inilah yang akan kita bicarakan di artikel ini.

TL; DR:
Kami sangat menyarankan untuk menonaktifkan batas CPU di Kubernetes (atau menonaktifkan kuota CFS di Kubelet) jika Anda menggunakan versi kernel Linux dengan bug kuota CFS. Pada intinya tersedia serius dan terkenal bug yang menyebabkan pembatasan dan penundaan yang berlebihan
.

Di Omio seluruh infrastruktur dikelola oleh Kubernetes. Semua beban kerja stateful dan stateless kami berjalan secara eksklusif di Kubernetes (kami menggunakan Google Kubernetes Engine). Dalam enam bulan terakhir, kami mulai melihat adanya perlambatan secara acak. Aplikasi membeku atau berhenti merespons pemeriksaan kesehatan, kehilangan koneksi ke jaringan, dll. Perilaku ini membingungkan kami sejak lama, dan akhirnya kami memutuskan untuk menangani masalah ini dengan serius.

Ringkasan artikel:

  • Sedikit penjelasan tentang container dan Kubernetes;
  • Bagaimana permintaan dan batasan CPU diterapkan;
  • Cara kerja batas CPU di lingkungan multi-core;
  • Cara melacak pelambatan CPU;
  • Solusi masalah dan nuansa.

Sedikit penjelasan tentang container dan Kubernetes

Kubernetes pada dasarnya adalah standar modern di dunia infrastruktur. Tugas utamanya adalah orkestrasi container.

Wadah

Di masa lalu, kami harus membuat artefak seperti Java JAR/WAR, Python Eggs, atau executable untuk dijalankan di server. Namun, untuk membuatnya berfungsi, pekerjaan tambahan harus dilakukan: menginstal lingkungan runtime (Java/Python), menempatkan file yang diperlukan di tempat yang tepat, memastikan kompatibilitas dengan versi sistem operasi tertentu, dll. Dengan kata lain, perhatian yang cermat harus diberikan pada manajemen konfigurasi (yang sering menjadi sumber perselisihan antara pengembang dan administrator sistem).

Kontainer mengubah segalanya. Sekarang artefak tersebut adalah gambar container. Ini dapat direpresentasikan sebagai semacam file executable yang diperluas yang tidak hanya berisi program, tetapi juga lingkungan eksekusi lengkap (Java/Python/...), serta file/paket yang diperlukan, sudah diinstal sebelumnya dan siap untuk digunakan. berlari. Kontainer dapat disebarkan dan dijalankan di server berbeda tanpa langkah tambahan apa pun.

Selain itu, kontainer beroperasi di lingkungan sandboxnya sendiri. Mereka memiliki adaptor jaringan virtual mereka sendiri, sistem file mereka sendiri dengan akses terbatas, hierarki proses mereka sendiri, batasan mereka sendiri pada CPU dan memori, dll. Semua ini diimplementasikan berkat subsistem khusus dari kernel Linux - namespace.

Kubernetes

Seperti yang dinyatakan sebelumnya, Kubernetes adalah orkestrator container. Cara kerjanya seperti ini: Anda memberinya kumpulan mesin, lalu berkata: β€œHai, Kubernetes, mari luncurkan sepuluh instance container saya dengan masing-masing 2 prosesor dan memori 3 GB, dan biarkan tetap berjalan!” Kubernetes akan mengurus sisanya. Ia akan menemukan kapasitas kosong, meluncurkan container dan memulai ulang jika perlu, meluncurkan pembaruan saat mengubah versi, dll. Pada dasarnya, Kubernetes memungkinkan Anda mengabstraksi komponen perangkat keras dan membuat berbagai macam sistem cocok untuk menerapkan dan menjalankan aplikasi.

Batasan CPU dan pembatasan agresif di Kubernetes
Kubernetes dari sudut pandang orang awam

Apa itu permintaan dan batasan di Kubernetes

Oke, kita sudah membahas container dan Kubernetes. Kita juga tahu bahwa beberapa container bisa berada di mesin yang sama.

Sebuah analogi dapat ditarik dengan apartemen komunal. Tempat (mesin/unit) yang luas diambil dan disewakan kepada beberapa penyewa (container). Kubernetes bertindak sebagai makelar barang tak bergerak. Timbul pertanyaan, bagaimana caranya agar para penyewa tidak saling berkonflik? Bagaimana jika salah satu dari mereka, misalnya, memutuskan untuk meminjam kamar mandi selama setengah hari?

Di sinilah permintaan dan batasan berperan. CPU Meminta dibutuhkan semata-mata untuk tujuan perencanaan. Ini seperti β€œdaftar keinginan” wadah, dan digunakan untuk memilih node yang paling sesuai. Pada saat yang sama CPU batas dapat dibandingkan dengan perjanjian sewa - segera setelah kita memilih unit untuk kontainer, maka tidak bisa melampaui batas yang telah ditetapkan. Dan disinilah permasalahannya muncul...

Bagaimana permintaan dan batasan diterapkan di Kubernetes

Kubernetes menggunakan mekanisme pembatasan (melewatkan siklus jam) yang dibangun di dalam kernel untuk mengimplementasikan batasan CPU. Jika suatu aplikasi melampaui batas, pembatasan akan diaktifkan (yaitu aplikasi menerima siklus CPU lebih sedikit). Permintaan dan batasan memori diatur secara berbeda, sehingga lebih mudah dideteksi. Untuk melakukan ini, cukup periksa status restart terakhir dari pod: apakah itu β€œOOOMKilled”. Pelambatan CPU tidak sesederhana itu, karena K8 hanya menyediakan metrik berdasarkan penggunaan, bukan berdasarkan grup.

Permintaan CPU

Batasan CPU dan pembatasan agresif di Kubernetes
Bagaimana permintaan CPU diimplementasikan

Untuk mempermudah, mari kita lihat proses menggunakan contoh mesin dengan CPU 4-inti.

K8s menggunakan mekanisme grup kontrol (cgroups) untuk mengontrol alokasi sumber daya (memori dan prosesor). Model hierarki tersedia untuk itu: anak mewarisi batasan grup induk. Detail distribusi disimpan dalam sistem file virtual (/sys/fs/cgroup). Dalam hal prosesor, inilah yang terjadi /sys/fs/cgroup/cpu,cpuacct/*.

K8 menggunakan file cpu.share untuk mengalokasikan sumber daya prosesor. Dalam kasus kami, cgroup root mendapat 4096 bagian sumber daya CPU - 100% dari daya prosesor yang tersedia (1 inti = 1024; ini adalah nilai tetap). Grup akar mendistribusikan sumber daya secara proporsional bergantung pada bagian keturunan yang terdaftar cpu.share, dan mereka, pada gilirannya, melakukan hal yang sama terhadap keturunan mereka, dll. Pada node Kubernetes pada umumnya, cgroup root memiliki tiga anak: system.slice, user.slice ΠΈ kubepods. Dua subgrup pertama digunakan untuk mendistribusikan sumber daya antara beban sistem kritis dan program pengguna di luar K8. Terakhir - kubepods β€” dibuat oleh Kubernetes untuk mendistribusikan sumber daya antar pod.

Diagram di atas menunjukkan bahwa subkelompok pertama dan kedua menerima masing-masing 1024 dibagikan, dengan subgrup kuberpod yang dialokasikan 4096 saham Bagaimana ini mungkin: lagi pula, grup root hanya memiliki akses ke 4096 bagiannya, dan jumlah bagian keturunannya jauh melebihi jumlah ini (6144)? Intinya adalah nilainya masuk akal, sehingga penjadwal Linux (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 Jika terjadi downtime, dua grup pertama tidak akan menggunakan sumber daya yang dialokasikan.

Untungnya, penjadwal memiliki mekanisme untuk menghindari pemborosan sumber daya CPU yang tidak terpakai. Ini mentransfer kapasitas "menganggur" ke kumpulan global, yang kemudian didistribusikan ke kelompok yang membutuhkan daya prosesor tambahan (transfer terjadi secara batch untuk menghindari kerugian pembulatan). Cara serupa diterapkan pada semua keturunan.

Mekanisme ini memastikan distribusi kekuatan prosesor yang adil dan memastikan tidak ada satu proses yang β€œmencuri” sumber daya dari proses lainnya.

Batas CPU

Terlepas dari kenyataan bahwa konfigurasi batasan dan permintaan di K8 terlihat serupa, penerapannya sangat berbeda: ini paling menyesatkan dan bagian yang paling sedikit didokumentasikan.

K8 terlibat Mekanisme kuota CFS untuk menerapkan batasan. Pengaturannya ditentukan dalam file cfs_period_us ΠΈ cfs_quota_us di direktori cgroup (file juga terletak di sana cpu.share).

Berbeda cpu.share, kuota didasarkan pada jangka waktu, dan bukan pada daya prosesor yang tersedia. cfs_period_us menentukan durasi periode (epoch) - selalu 100000 ΞΌs (100 ms). Ada opsi untuk mengubah nilai ini di K8s, tetapi saat ini hanya tersedia dalam versi alfa. Penjadwal menggunakan epoch untuk memulai ulang kuota yang digunakan. Berkas kedua cfs_quota_us, menentukan waktu yang tersedia (kuota) di setiap zaman. Perhatikan bahwa ini juga ditentukan dalam mikrodetik. Kuota dapat melebihi jangka waktu; dengan kata lain, mungkin lebih besar dari 100 ms.

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

Batasan CPU dan pembatasan agresif di Kubernetes
Skenario 1: 2 thread dan batas 200 ms. Tidak ada pembatasan

Batasan CPU dan pembatasan agresif di Kubernetes
Skenario 2: 10 thread dan batas 200 ms. Pembatasan dimulai setelah 20 ms, akses ke sumber daya prosesor dilanjutkan setelah 80 ms

Katakanlah Anda menetapkan batas CPU ke 2 kernel; Kubernetes akan menerjemahkan nilai ini menjadi 200 ms. Artinya, container dapat menggunakan waktu CPU maksimal 200 ms tanpa pembatasan.

Dan disinilah kesenangan dimulai. Seperti disebutkan di atas, kuota yang tersedia adalah 200 ms. Jika Anda bekerja secara paralel sepuluh thread pada mesin 12-inti (lihat ilustrasi untuk skenario 2), saat semua pod lainnya menganggur, kuota akan habis hanya dalam 20 mdtk (karena 10 * 20 mdtk = 200 mdtk), dan semua thread pada pod ini akan hang Β» (mencekik) selama 80 ms berikutnya. Yang sudah disebutkan bug penjadwal, sehingga terjadi throttling yang berlebihan dan container bahkan tidak dapat memenuhi kuota yang ada.

Bagaimana cara mengevaluasi pembatasan di pod?

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

  • nr_periods β€” jumlah total periode penjadwalan;
  • nr_throttled β€” jumlah periode yang dibatasi dalam komposisi nr_periods;
  • throttled_time β€” waktu pembatasan kumulatif dalam nanodetik.

Batasan CPU dan pembatasan agresif di Kubernetes

Apa yang sebenarnya terjadi?

Hasilnya, kami mendapatkan pembatasan yang tinggi di semua aplikasi. Terkadang dia ada di dalam satu setengah kali lebih kuat dari yang dihitung!

Hal ini menyebabkan berbagai kesalahan - kegagalan pemeriksaan kesiapan, kontainer macet, koneksi jaringan terputus, batas waktu dalam panggilan layanan. Hal ini pada akhirnya menghasilkan peningkatan latensi dan tingkat kesalahan yang lebih tinggi.

Keputusan dan konsekuensi

Semuanya sederhana di sini. Kami mengabaikan batasan CPU dan mulai memperbarui kernel OS dalam cluster ke versi terbaru, yang bugnya telah diperbaiki. Jumlah kesalahan (HTTP 5xx) di layanan kami langsung turun secara signifikan:

Kesalahan HTTP 5xx

Batasan CPU dan pembatasan agresif di Kubernetes
Kesalahan HTTP 5xx untuk satu layanan penting

Waktu respons hal95

Batasan CPU dan pembatasan agresif di Kubernetes
Latensi permintaan layanan penting, persentil ke-95

Biaya operasional

Batasan CPU dan pembatasan agresif di Kubernetes
Jumlah jam instans yang dihabiskan

Apa yang menangkap?

Seperti yang dinyatakan di awal artikel:

Sebuah analogi dapat ditarik dengan apartemen komunal... Kubernetes bertindak sebagai makelar barang tak bergerak. Namun bagaimana cara agar penyewa tidak saling berkonflik? Bagaimana jika salah satu dari mereka, misalnya, memutuskan untuk meminjam kamar mandi selama setengah hari?

Inilah hasil tangkapannya. Satu container yang ceroboh dapat menghabiskan semua sumber daya CPU yang tersedia di suatu mesin. Jika Anda memiliki tumpukan aplikasi pintar (misalnya, JVM, Go, Node VM dikonfigurasi dengan benar), maka ini bukan masalah: Anda dapat bekerja dalam kondisi seperti itu untuk waktu yang lama. Namun jika aplikasi tidak dioptimalkan dengan baik atau tidak dioptimalkan sama sekali (FROM java:latest), situasi mungkin menjadi tidak terkendali. Di Omio kami memiliki Dockerfile dasar otomatis dengan pengaturan default yang memadai untuk tumpukan bahasa utama, jadi masalah ini tidak terjadi.

Kami merekomendasikan untuk memantau metriknya GUNAKAN (penggunaan, saturasi, dan kesalahan), penundaan API, dan tingkat kesalahan. Pastikan hasilnya sesuai harapan.

referensi

Ini adalah cerita kita. Materi berikut sangat membantu untuk memahami apa yang terjadi:

Laporan bug Kubernetes:

Pernahkah Anda mengalami masalah serupa dalam praktik Anda atau memiliki pengalaman terkait pembatasan di lingkungan produksi dalam container? Bagikan cerita Anda di komentar!

PS dari penerjemah

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komentar