Debugging aplikasi Golang yang sarat muatan atau bagaimana kami mencari masalah di Kubernet yang tidak ada

Dalam dunia modern cloud Kubernetes, dengan satu atau lain cara, seseorang harus menghadapi kesalahan perangkat lunak yang tidak dibuat oleh Anda atau kolega Anda, tetapi Anda harus menyelesaikannya. Artikel ini dapat membantu pendatang baru di dunia Golang dan Kubernet memahami beberapa cara untuk men-debug perangkat lunak mereka sendiri dan asing.

gambar

Nama saya Viktor Yagofarov, saya sedang mengembangkan cloud Kubernetes di DomKlik, dan hari ini saya ingin berbicara tentang bagaimana kami menyelesaikan masalah dengan salah satu komponen kunci dari kluster k8s (Kubernetes) produksi kami.

Dalam cluster tempur kami (pada saat penulisan):

  • 1890 pod dan 577 layanan diluncurkan (jumlah layanan Microsoft nyata juga ada di wilayah gambar ini)
  • Ingress- controllers melayani sekitar 6k RPS dan kira-kira jumlah yang sama masuk oleh Ingress langsung ke hostPort .


Masalah


Beberapa bulan yang lalu, pod kami mulai mengalami masalah dengan penyelesaian nama DNS. Faktanya adalah bahwa DNS bekerja terutama di atas UDP, dan di kernel Linux ada beberapa masalah dengan conntrack dan UDP. DNAT saat mengakses alamat layanan dari Layanan k8 hanya memperburuk masalah dengan perlintasan conntrack . Perlu ditambahkan bahwa di cluster kami pada saat masalah ada sekitar 40k RPS menuju server DNS, CoreDNS.

gambar

Diputuskan untuk menggunakan server DNS caching lokal NodeLocal DNS (nodelocaldns) yang dibuat khusus oleh komunitas pada setiap node pekerja dari cluster, yang masih dalam versi beta dan dirancang untuk menyelesaikan semua masalah. Singkatnya: singkirkan UDP saat menghubungkan ke DNS cluster, hapus NAT, tambahkan lapisan cache tambahan.

Dalam iterasi pertama implementasi nodelocaldns kami menggunakan versi 1.15.4 (jangan bingung dengan versi kubus ), yang datang dengan «kubernetes-installer» Kubespray - kita berbicara tentang garpu Fork perusahaan kami dari Southbridge.

Hampir segera setelah pendahuluan, masalah dimulai: memori mengalir dan perapian restart sesuai dengan batas memori (OOM-Kill). Pada saat memulai kembali ini, semua lalu lintas pada host hilang, karena di semua pod / etc /resolv.conf menunjuk tepat ke alamat IP dari nodelocaldns.

Situasi ini jelas tidak cocok untuk semua orang, dan tim OPS kami mengambil sejumlah langkah untuk menghilangkannya.

Karena saya sendiri baru mengenal Golang, saya sangat tertarik untuk pergi sejauh ini dan berkenalan dengan aplikasi debug dalam bahasa pemrograman yang luar biasa ini.

Kami mencari solusi


Jadi ayo pergi!

Versi 1.15.7 diunduh ke cluster dev , yang sudah dianggap beta, dan bukan alpha sebagai 1.15.4, tetapi gadis itu tidak memiliki lalu lintas seperti itu di DNS (40k RPS). Ini menyedihkan.

Dalam prosesnya, kami melepaskan ikatan simpul dari Kubespray dan menulis grafik Helm khusus untuk peluncuran yang lebih nyaman. Pada saat yang sama, mereka menulis buku pedoman untuk Kubespray, yang memungkinkan Anda untuk mengubah pengaturan kubelet tanpa mencerna seluruh status cluster per jam; selain itu, ini dapat dilakukan secara langsung (memeriksa terlebih dahulu pada sejumlah kecil node).

Selanjutnya, kami meluncurkan versi nodelocaldns 1.15.7 ke prod. Situasinya, sayangnya, terulang. Memori itu mengalir. Repositori nodelocaldn

resmi memiliki versi yang ditandai dengan 1,15. 8, tetapi karena suatu alasan saya tidak dapat membuat buruh pelabuhan menarik versi ini dan berpikir bahwa saya belum mengkompilasi gambar Docker resmi, jadi versi ini tidak boleh digunakan. Ini adalah poin penting, dan kami akan kembali ke sana.

Debugging: Tahap 1


Untuk waktu yang lama saya pada dasarnya tidak bisa memahami cara merakit versi nodelocaldn saya, karena Makefile dari lobak jatuh dengan kesalahan yang tidak dapat dipahami dari dalam gambar buruh pelabuhan, dan saya tidak benar-benar mengerti bagaimana cara membangun proyek Go dengan govendor , yang kemudian diurutkan dengan cara aneh ke dalam direktori langsung. untuk beberapa opsi server DNS yang berbeda. Masalahnya adalah saya mulai belajar Go ketika versi dependensi out-of-box yang normal sudah muncul .

Pavel Selivanov banyak membantu saya dengan masalah ini.pauljamm, untuk yang banyak terima kasih padanya. Saya berhasil menyusun versi saya.

Selanjutnya, kami mengacaukan profilprofil , menguji perakitan pada gadis dan menggulungnya ke dalam prod.

Seorang kolega dari tim Obrolan benar-benar membantu memahami pembuatan profil sehingga Anda dapat dengan mudah berpegangan pada utilitas pprof melalui URL CLI dan mempelajari memori dan utas proses menggunakan menu interaktif di browser, yang banyak terima kasih juga kepadanya.

Pada pandangan pertama, berdasarkan pada output profiler, prosesnya berjalan dengan baik - sebagian besar memori dialokasikan pada stack dan, tampaknya, terus digunakan oleh rutinitas Go .

Tetapi pada beberapa titik menjadi jelas bahwa perapian nodelocald yang “buruk” memiliki terlalu banyak benang aktif dibandingkan dengan yang “sehat”. Dan utas tidak hilang di mana pun, tetapi terus menggantung di memori. Pada saat ini, dugaan Pavel Selivanov bahwa "utas mengalir" telah dikonfirmasi.

gambar

Debugging: Tahap 2


Menjadi menarik mengapa ini terjadi (utas mengalir), dan tahap berikutnya dalam studi proses nodelocaldn telah dimulai. Kode

penganalisa statis staticcheck menunjukkan bahwa ada beberapa masalah hanya pada tahap membuat thread di perpustakaan , yang digunakan dalam nodelocaldn (itu inkluda CoreDNS, yang inkluda nodelocaldns'om). Seperti yang saya pahami, di beberapa tempat bukan penunjuk ke struktur ditransmisikan , tetapi salinan nilainya .

Diputuskan untuk membuat proses "buruk" menggunakan utilitas gcore dan melihat apa yang ada di dalamnya.

Terjebak di coredump dengan alat dlv seperti gdbSaya menyadari kekuatannya, tetapi saya menyadari bahwa saya akan mencari alasan dengan cara ini untuk waktu yang sangat lama. Oleh karena itu, saya memasukkan coredump ke dalam Goland IDE dan menganalisis keadaan memori proses.

Debugging: Tahap 3


Sangat menarik untuk mempelajari struktur program, melihat kode yang menciptakannya. Dalam sekitar 10 menit menjadi jelas bahwa banyak go-rutin membuat semacam struktur untuk koneksi TCP, tandai mereka salah dan tidak pernah menghapusnya (ingat tentang 40k RPS?).

gambar

gambar

Di tangkapan layar, Anda dapat melihat bagian kode yang bermasalah dan struktur yang tidak dihapus saat sesi UDP ditutup.

Juga, dari coredump, penyebab sejumlah RPS menjadi dikenal oleh alamat IP dalam struktur ini (terima kasih telah membantu menemukan hambatan di cluster kami :).

Keputusan


Selama perang melawan masalah ini, saya menemukan dengan bantuan rekan-rekan dari komunitas Kubernetes bahwa gambar Docker resmi nodelocaldns 1.15.8 masih ada (dan saya benar-benar memiliki tangan yang bengkok dan entah bagaimana melakukan kesalahan menarik docker, atau WIFI nakal di tarik momen).

Dalam versi ini, versi perpustakaan yang ia gunakan sangat "kesal": khususnya, "pelakunya" "diafirasikan" sekitar 20 versi ke atas!

Selain itu, versi baru sudah memiliki dukungan untuk profil melalui pprof dan diaktifkan melalui Configmap, Anda tidak perlu memasang kembali apa pun.

Versi baru diunduh terlebih dahulu di dev dan kemudian di prod.
III ... Kemenangan !
Proses mulai mengembalikan ingatannya ke sistem dan masalah berhenti.

Pada grafik di bawah ini Anda dapat melihat gambar: "DNS Smoker vs. DNS orang yang sehat. "

gambar

temuan


Kesimpulannya sederhana: periksa kembali apa yang Anda lakukan beberapa kali dan jangan meremehkan bantuan komunitas. Akibatnya, kami menghabiskan lebih banyak waktu untuk masalah selama beberapa hari daripada yang kami bisa, tetapi kami menerima operasi gagal-DNS dalam wadah. Terima kasih telah membaca sampai titik ini :)

Tautan yang berguna:

1. www.freecodecamp.org/news/how-i-investigated-memory-leaks-in-go-using-pprof-on-a--l--codebase-4bec4325e192
2 . habr.com/en/company/roistat/blog/413175
3. rakyll.org

All Articles