ProHoster > blog > administrasi > 6 bug sistem menghibur dalam pengoperasian Kubernetes [dan solusinya]
6 bug sistem menghibur dalam pengoperasian Kubernetes [dan solusinya]
Selama bertahun-tahun menggunakan Kubernetes dalam produksi, kami telah mengumpulkan banyak cerita menarik tentang bagaimana bug di berbagai komponen sistem menyebabkan konsekuensi yang tidak menyenangkan dan/atau tidak dapat dipahami yang memengaruhi pengoperasian container dan pod. Pada artikel ini kami telah memilih beberapa yang paling umum atau menarik. Sekalipun Anda tidak pernah cukup beruntung untuk menghadapi situasi seperti itu, membaca cerita detektif pendek seperti itu - terutama yang βlangsungβ - selalu menarik, bukan?..
Cerita 1. Supercronic dan Docker gantung
Di salah satu cluster, kami secara berkala menerima Docker yang dibekukan, yang mengganggu fungsi normal cluster. Pada saat yang sama, hal berikut diamati di log Docker:
level=error msg="containerd: start init process" error="exit status 2: "runtime/cgo: pthread_create failed: No space left on device
SIGABRT: abort
PC=0x7f31b811a428 m=0
goroutine 0 [idle]:
goroutine 1 [running]:
runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:252 fp=0xc420026768 sp=0xc420026760
runtime.main() /usr/local/go/src/runtime/proc.go:127 +0x6c fp=0xc4200267c0 sp=0xc420026768
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4200267c8 sp=0xc4200267c0
goroutine 17 [syscall, locked to thread]:
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1
β¦
Yang paling menarik bagi kami tentang kesalahan ini adalah pesannya: pthread_create failed: No space left on device. Belajar Cepat dokumentasi menjelaskan bahwa Docker tidak dapat melakukan fork suatu proses, itulah sebabnya proses tersebut terhenti secara berkala.
Dalam pemantauan, gambar berikut sesuai dengan apa yang terjadi:
Masalahnya adalah ini: ketika suatu tugas dijalankan dalam mode superkronik, proses yang dihasilkan oleh tugas tersebut tidak dapat diakhiri dengan benar, berubah menjadi zombie.
Catatan: Lebih tepatnya, proses dihasilkan oleh tugas cron, tetapi supercronic bukanlah sistem init dan tidak dapat βmengadopsiβ proses yang dihasilkan oleh turunannya. Ketika sinyal SIGHUP atau SIGTERM dimunculkan, sinyal tersebut tidak diteruskan ke proses anak, sehingga proses anak tidak berhenti dan tetap dalam status zombie. Anda dapat membaca lebih lanjut tentang semua ini, misalnya, di artikel seperti itu.
Ada beberapa cara untuk menyelesaikan masalah:
Sebagai solusi sementara - tingkatkan jumlah PID dalam sistem pada satu waktu:
/proc/sys/kernel/pid_max (since Linux 2.5.34)
This file specifies the value at which PIDs wrap around (i.e., the value in this file is one greater than the maximum PID). PIDs greater than this value are not alloβ
cated; thus, the value in this file also acts as a system-wide limit on the total number of processes and threads. The default value for this file, 32768, results in the
same range of PIDs as on earlier kernels
Atau meluncurkan tugas di supercronic tidak secara langsung, tetapi menggunakan hal yang sama kecil, yang mampu menghentikan proses dengan benar dan tidak memunculkan zombie.
Cerita 2. βZombieβ saat menghapus cgroup
Kubelet mulai memakan banyak CPU:
Tidak ada yang akan menyukai ini, jadi kami mempersenjatai diri Perf dan mulai mengatasi masalahnya. Hasil penyelidikannya adalah sebagai berikut:
Kubelet menghabiskan lebih dari sepertiga waktu CPU-nya untuk menarik data memori dari semua cgroup:
Di milis pengembang kernel, Anda dapat menemukannya pembahasan masalah tersebut. Singkatnya, intinya begini: berbagai file tmpfs dan hal serupa lainnya tidak sepenuhnya dihapus dari sistem saat menghapus cgroup, yang disebut memcg zombie. Cepat atau lambat mereka akan dihapus dari cache halaman, tetapi ada banyak memori di server dan kernel tidak melihat gunanya membuang waktu untuk menghapusnya. Itu sebabnya mereka terus menumpuk. Mengapa ini bisa terjadi? Ini adalah server dengan pekerjaan cron yang terus-menerus menciptakan pekerjaan baru, dan dengan itu pod baru. Dengan demikian, cgroup baru dibuat untuk wadah di dalamnya, yang segera dihapus.
Mengapa cAdvisor di kubelet membuang banyak waktu? Ini mudah dilihat dengan eksekusi paling sederhana time cat /sys/fs/cgroup/memory/memory.stat. Jika pada mesin yang sehat pengoperasiannya memerlukan waktu 0,01 detik, maka pada mesin cron02 yang bermasalah memerlukan waktu 1,2 detik. Masalahnya adalah cAdvisor, yang membaca data dari sysfs dengan sangat lambat, mencoba memperhitungkan memori yang digunakan dalam grup zombie.
Untuk menghapus zombie secara paksa, kami mencoba membersihkan cache seperti yang direkomendasikan di LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - tapi kernelnya ternyata lebih rumit dan membuat mobil crash.
Apa yang harus dilakukan? Masalahnya sedang diperbaiki (melakukan, dan untuk deskripsi lihat rilis pesan) memperbarui kernel Linux ke versi 4.16.
Sejarah 3. Systemd dan mountnya
Sekali lagi, kubelet memakan terlalu banyak sumber daya pada beberapa node, namun kali ini kubelet memakan terlalu banyak memori:
Ternyata ada masalah pada systemd yang digunakan di Ubuntu 16.04, dan itu terjadi saat mengelola mount yang dibuat untuk koneksi subPath dari ConfigMaps atau rahasia. Setelah pod menyelesaikan tugasnya layanan systemd dan mount layanannya tetap ada dalam sistem. Seiring waktu, sejumlah besar dari mereka terakumulasi. Bahkan ada isu mengenai topik ini:
...yang terakhir mengacu pada PR di systemd: #7811 (masalah di systemd - #7798).
Masalahnya tidak lagi ada di Ubuntu 18.04, tetapi jika Anda ingin terus menggunakan Ubuntu 16.04, solusi kami tentang topik ini mungkin berguna bagi Anda.
#!/bin/bash
# we will work only on xenial
hostrelease="/etc/lsb-release-host"
test -f ${hostrelease} && grep xenial ${hostrelease} > /dev/null || exit 0
# sleeping max 30 minutes to dispense load on kube-nodes
sleep $((RANDOM % 1800))
stoppedCount=0
# counting actual subpath units in systemd
countBefore=$(systemctl list-units | grep subpath | grep "run-" | wc -l)
# let's go check each unit
for unit in $(systemctl list-units | grep subpath | grep "run-" | awk '{print $1}'); do
# finding description file for unit (to find out docker container, who born this unit)
DropFile=$(systemctl status ${unit} | grep Drop | awk -F': ' '{print $2}')
# reading uuid for docker container from description file
DockerContainerId=$(cat ${DropFile}/50-Description.conf | awk '{print $5}' | cut -d/ -f6)
# checking container status (running or not)
checkFlag=$(docker ps | grep -c ${DockerContainerId})
# if container not running, we will stop unit
if [[ ${checkFlag} -eq 0 ]]; then
echo "Stopping unit ${unit}"
# stoping unit in action
systemctl stop $unit
# just counter for logs
((stoppedCount++))
# logging current progress
echo "Stopped ${stoppedCount} systemd units out of ${countBefore}"
fi
done
... dan itu berjalan setiap 5 menit menggunakan superkronik yang disebutkan sebelumnya. Dockerfile-nya terlihat seperti ini:
Telah diketahui bahwa: jika kita mempunyai sebuah pod yang ditempatkan pada sebuah node dan gambarnya dipompa keluar dalam waktu yang sangat lama, maka pod lain yang βmenabrakβ node yang sama akan langsung terpompa keluar. tidak mulai menarik gambar pod baru. Sebaliknya, ia menunggu hingga gambar dari pod sebelumnya ditarik. Akibatnya, pod yang sudah dijadwalkan dan gambarnya dapat diunduh hanya dalam satu menit akan berstatus containerCreating.
Acaranya akan terlihat seperti ini:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"
Ternyata bahwa satu gambar dari registri yang lambat dapat memblokir penerapan per simpul.
Sayangnya, tidak banyak jalan keluar dari situasi ini:
Coba gunakan Docker Registry Anda secara langsung di cluster atau langsung dengan cluster (misalnya, GitLab Registry, Nexus, dll.);
Selama pengoperasian berbagai aplikasi, kami juga menghadapi situasi di mana sebuah node benar-benar tidak lagi dapat diakses: SSH tidak merespons, semua daemon pemantauan hilang, dan kemudian tidak ada (atau hampir tidak ada) apa pun yang aneh di log.
Saya akan memberi tahu Anda melalui gambar menggunakan contoh satu node tempat MongoDB berfungsi.
Seperti inilah penampakan di atas untuk kecelakaan:
Dan seperti ini - setelah kecelakaan:
Dalam pemantauan, ada juga lompatan tajam, di mana node tidak lagi tersedia:
Jadi, dari tangkapan layar terlihat jelas bahwa:
RAM pada mesin hampir habis;
Ada lonjakan tajam dalam konsumsi RAM, setelah itu akses ke seluruh mesin dinonaktifkan secara tiba-tiba;
Tugas besar tiba di Mongo, yang memaksa proses DBMS menggunakan lebih banyak memori dan secara aktif membaca dari disk.
Ternyata jika Linux kehabisan memori bebas (tekanan memori terjadi) dan tidak ada swap, maka untuk Ketika pembunuh OOM tiba, tindakan penyeimbangan mungkin muncul antara memasukkan halaman ke dalam cache halaman dan menulisnya kembali ke disk. Hal ini dilakukan oleh kswapd, yang dengan berani mengosongkan halaman memori sebanyak mungkin untuk distribusi selanjutnya.
Sayangnya, dengan beban I/O yang besar ditambah dengan jumlah memori bebas yang sedikit, kswapd menjadi penghambat keseluruhan sistem, karena mereka terikat padanya semua alokasi (kesalahan halaman) halaman memori dalam sistem. Ini bisa berlangsung sangat lama jika prosesnya tidak ingin menggunakan memori lagi, tetapi tetap berada di ujung jurang pembunuh OOM.
Pertanyaan wajarnya adalah: mengapa pembunuh OOM datang terlambat? Dalam iterasi saat ini, pembunuh OOM sangat bodoh: ia akan mematikan proses hanya ketika upaya untuk mengalokasikan halaman memori gagal, yaitu. jika kesalahan halaman gagal. Hal ini tidak terjadi dalam waktu yang cukup lama, karena kswapd dengan berani membebaskan halaman memori, membuang cache halaman (sebenarnya seluruh disk I/O dalam sistem) kembali ke disk. Anda dapat membaca lebih detail dengan deskripsi langkah-langkah yang diperlukan untuk menghilangkan masalah seperti itu di kernel di sini.
Di beberapa cluster, di mana terdapat banyak sekali pod yang beroperasi, kami mulai memperhatikan bahwa sebagian besar dari mereka βmenggantungβ untuk waktu yang sangat lama di negara bagian tersebut. Pending, meskipun container Docker sendiri sudah berjalan di node dan dapat digunakan secara manual.
Selain itu, di describe tidak ada yang salah:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned sphinx-0 to ss-dev-kub07
Normal SuccessfulAttachVolume 1m attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "sphinx-config"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "default-token-fzcsf"
Normal SuccessfulMountVolume 49s (x2 over 51s) kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx-exporter/sphinx-indexer:v1" already present on machine
Normal Created 43s kubelet, ss-dev-kub07 Created container
Normal Started 43s kubelet, ss-dev-kub07 Started container
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx/sphinx:v1" already present on machine
Normal Created 42s kubelet, ss-dev-kub07 Created container
Normal Started 42s kubelet, ss-dev-kub07 Started container
Setelah melakukan beberapa penggalian, kami berasumsi bahwa kubelet tidak mempunyai waktu untuk mengirimkan semua informasi tentang status pod dan uji keaktifan/kesiapan ke server API.
Dan setelah mempelajari bantuan, kami menemukan parameter berikut:
--kube-api-qps - QPS to use while talking with kubernetes apiserver (default 5)
--kube-api-burst - Burst to use while talking with kubernetes apiserver (default 10)
--event-qps - If > 0, limit event creations per second to this value. If 0, unlimited. (default 5)
--event-burst - Maximum size of a bursty event records, temporarily allows event records to burst to this number, while still not exceeding event-qps. Only used if --event-qps > 0 (default 10)
--registry-qps - If > 0, limit registry pull QPS to this value.
--registry-burst - Maximum size of bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0 (default 10)
Seperti yang terlihat, nilai defaultnya cukup kecil, dan 90% mencakup semua kebutuhan... Namun, dalam kasus kami ini tidak cukup. Oleh karena itu, kami menetapkan nilai berikut:
... dan memulai ulang kubelet, setelah itu kita melihat gambar berikut di grafik panggilan ke server API:
... dan ya, semuanya mulai terbang!
PS
Atas bantuan mereka dalam mengumpulkan bug dan mempersiapkan artikel ini, saya mengucapkan terima kasih yang sebesar-besarnya kepada banyak insinyur di perusahaan kami, dan terutama kepada rekan saya dari tim R&D kami Andrey Klimentyev (zuzza).