“Kubernetes meningkatkan latensi sebanyak 10 kali lipat”: siapa yang harus disalahkan dalam hal ini?

Catatan. terjemahan: Artikel ini, ditulis oleh Galo Navarro, yang menjabat sebagai Principal Software Engineer di perusahaan Eropa Adevinta, merupakan “investigasi” yang menarik dan instruktif di bidang operasi infrastruktur. Judul aslinya sedikit diperluas dalam terjemahan karena alasan yang penulis jelaskan di awal.

“Kubernetes meningkatkan latensi sebanyak 10 kali lipat”: siapa yang harus disalahkan dalam hal ini?

Catatan dari penulis: Sepertinya postingan ini tertarik perhatian lebih dari yang diharapkan. Saya masih mendapat komentar marah karena judul artikelnya menyesatkan dan beberapa pembaca merasa sedih. Saya memahami alasan atas apa yang terjadi, oleh karena itu, meskipun ada risiko merusak seluruh intrik, saya ingin segera memberi tahu Anda tentang apa artikel ini. Hal aneh yang saya lihat saat tim bermigrasi ke Kubernetes adalah setiap kali masalah muncul (seperti peningkatan latensi setelah migrasi), pihak pertama yang disalahkan adalah Kubernetes, namun ternyata orkestratornya tidak benar-benar melakukan hal tersebut. menyalahkan. Artikel ini menceritakan tentang salah satu kasus tersebut. Namanya mengulangi seruan salah satu pengembang kami (nanti Anda akan melihat bahwa Kubernetes tidak ada hubungannya dengan itu). Anda tidak akan menemukan informasi mengejutkan apa pun tentang Kubernetes di sini, namun Anda dapat mengharapkan beberapa pelajaran bagus tentang sistem yang kompleks.

Beberapa minggu yang lalu, tim saya memigrasikan satu layanan mikro ke platform inti yang mencakup CI/CD, runtime berbasis Kubernetes, metrik, dan fitur lainnya. Perpindahan ini bersifat percobaan: kami berencana untuk menjadikannya sebagai dasar dan mentransfer sekitar 150 layanan lagi dalam beberapa bulan mendatang. Semuanya bertanggung jawab atas pengoperasian beberapa platform online terbesar di Spanyol (Infojobs, Fotocasa, dll.).

Setelah kami menerapkan aplikasi ke Kubernetes dan mengalihkan sebagian lalu lintas ke sana, kejutan yang mengkhawatirkan menanti kami. Menunda (latensi) permintaan di Kubernetes 10 kali lebih tinggi dibandingkan di EC2. Secara umum, penting untuk menemukan solusi untuk masalah ini, atau mengabaikan migrasi layanan mikro (dan, mungkin, keseluruhan proyek).

Mengapa latensi di Kubernetes jauh lebih tinggi dibandingkan di EC2?

Untuk menemukan hambatannya, kami mengumpulkan metrik di sepanjang jalur permintaan. Arsitektur kami sederhana: gateway API (Zuul) memproksi permintaan ke instance layanan mikro di EC2 atau Kubernetes. Di Kubernetes kami menggunakan NGINX Ingress Controller, dan backendnya adalah objek biasa Penyebaran dengan aplikasi JVM pada platform Spring.

                                  EC2
                            +---------------+
                            |  +---------+  |
                            |  |         |  |
                       +-------> BACKEND |  |
                       |    |  |         |  |
                       |    |  +---------+  |                   
                       |    +---------------+
             +------+  |
Public       |      |  |
      -------> ZUUL +--+
traffic      |      |  |              Kubernetes
             +------+  |    +-----------------------------+
                       |    |  +-------+      +---------+ |
                       |    |  |       |  xx  |         | |
                       +-------> NGINX +------> BACKEND | |
                            |  |       |  xx  |         | |
                            |  +-------+      +---------+ |
                            +-----------------------------+

Masalahnya tampaknya terkait dengan latensi awal di backend (saya menandai area masalah pada grafik sebagai "xx"). Di EC2, respons aplikasi memerlukan waktu sekitar 20 md. Di Kubernetes, latensi meningkat menjadi 100-200 ms.

Kami dengan cepat menghilangkan kemungkinan tersangka terkait dengan perubahan waktu proses. Versi JVM tetap sama. Masalah containerisasi juga tidak ada hubungannya dengan itu: aplikasi sudah berhasil berjalan di container di EC2. Memuat? Namun kami mengamati latensi tinggi bahkan pada 1 permintaan per detik. Jeda pengumpulan sampah juga bisa diabaikan.

Salah satu admin Kubernetes kami bertanya-tanya apakah aplikasi tersebut memiliki ketergantungan eksternal karena kueri DNS pernah menyebabkan masalah serupa di masa lalu.

Hipotesis 1: resolusi nama DNS

Untuk setiap permintaan, aplikasi kita mengakses instans AWS Elasticsearch satu hingga tiga kali dalam domain serupa elastic.spain.adevinta.com. Di dalam wadah kami ada cangkang, agar kita bisa mengecek apakah pencarian domain memang memakan waktu lama.

Kueri DNS dari penampung:

[root@be-851c76f696-alf8z /]# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 22 msec
;; Query time: 22 msec
;; Query time: 29 msec
;; Query time: 21 msec
;; Query time: 28 msec
;; Query time: 43 msec
;; Query time: 39 msec

Permintaan serupa dari salah satu instans EC2 tempat aplikasi berjalan:

bash-4.4# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 77 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec

Mengingat pencarian memakan waktu sekitar 30 md, menjadi jelas bahwa resolusi DNS saat mengakses Elasticsearch memang berkontribusi terhadap peningkatan latensi.

Namun, hal ini aneh karena dua alasan:

  1. Kami sudah memiliki banyak sekali aplikasi Kubernetes yang berinteraksi dengan sumber daya AWS tanpa mengalami latensi tinggi. Apapun alasannya, ini berkaitan khusus dengan kasus ini.
  2. Kita tahu bahwa JVM melakukan cache DNS dalam memori. Di gambar kami, nilai TTL tertulis $JAVA_HOME/jre/lib/security/java.security dan atur ke 10 detik: networkaddress.cache.ttl = 10. Dengan kata lain, JVM harus menyimpan semua permintaan DNS dalam cache selama 10 detik.

Untuk mengkonfirmasi hipotesis pertama, kami memutuskan untuk berhenti memanggil DNS untuk sementara waktu dan melihat apakah masalahnya telah hilang. Pertama, kami memutuskan untuk mengonfigurasi ulang aplikasi agar berkomunikasi langsung dengan Elasticsearch berdasarkan alamat IP, bukan melalui nama domain. Ini memerlukan perubahan kode dan penerapan baru, jadi kami cukup memetakan domain ke alamat IP-nya /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Sekarang penampung tersebut menerima IP hampir seketika. Hal ini menghasilkan beberapa perbaikan, namun kami hanya sedikit mendekati tingkat latensi yang diharapkan. Meskipun resolusi DNS memakan waktu lama, alasan sebenarnya masih belum diketahui.

Diagnostik melalui jaringan

Kami memutuskan untuk menganalisis lalu lintas dari wadah menggunakan tcpdumpuntuk melihat apa yang sebenarnya terjadi di jaringan:

[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap

Kami kemudian mengirimkan beberapa permintaan dan mengunduh tangkapannya (kubectl cp my-service:/capture.pcap capture.pcap) untuk analisis lebih lanjut di Wireshark.

Tidak ada yang mencurigakan tentang permintaan DNS (kecuali satu hal kecil yang akan saya bicarakan nanti). Namun ada keanehan tertentu dalam cara layanan kami menangani setiap permintaan. Di bawah ini adalah tangkapan layar yang menunjukkan permintaan diterima sebelum respons dimulai:

“Kubernetes meningkatkan latensi sebanyak 10 kali lipat”: siapa yang harus disalahkan dalam hal ini?

Nomor paket ditampilkan di kolom pertama. Untuk lebih jelasnya, saya telah memberi kode warna pada aliran TCP yang berbeda.

Aliran hijau yang dimulai dengan paket 328 menunjukkan bagaimana klien (172.17.22.150) membuat koneksi TCP ke kontainer (172.17.36.147). Setelah jabat tangan awal (328-330), paket 331 dibawa HTTP GET /v1/.. — permintaan masuk ke layanan kami. Seluruh proses memakan waktu 1 ms.

Aliran abu-abu (dari paket 339) menunjukkan bahwa layanan kami mengirimkan permintaan HTTP ke instance Elasticsearch (tidak ada jabat tangan TCP karena menggunakan koneksi yang sudah ada). Ini membutuhkan waktu 18 ms.

Sejauh ini semuanya baik-baik saja, dan waktunya kira-kira sesuai dengan penundaan yang diharapkan (20-30 ms bila diukur dari klien).

Namun, bagian biru membutuhkan waktu 86ms. Apa yang terjadi di dalamnya? Dengan paket 333, layanan kami mengirimkan permintaan HTTP GET ke /latest/meta-data/iam/security-credentials, dan segera setelahnya, melalui koneksi TCP yang sama, permintaan GET lainnya ke /latest/meta-data/iam/security-credentials/arn:...

Kami menemukan bahwa hal ini berulang pada setiap permintaan sepanjang penelusuran. Resolusi DNS memang sedikit lebih lambat di container kami (penjelasan fenomena ini cukup menarik, tapi saya akan menyimpannya untuk artikel tersendiri). Ternyata penyebab penundaan yang lama adalah panggilan ke layanan AWS Instance Metadata pada setiap permintaan.

Hipotesis 2: panggilan yang tidak perlu ke AWS

Kedua titik akhir milik API Metadata Instans AWS. Layanan mikro kami menggunakan layanan ini saat menjalankan Elasticsearch. Kedua panggilan tersebut merupakan bagian dari proses otorisasi dasar. Titik akhir yang diakses pada permintaan pertama mengeluarkan peran IAM yang terkait dengan instans tersebut.

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
arn:aws:iam::<account_id>:role/some_role

Permintaan kedua meminta titik akhir kedua untuk izin sementara untuk contoh ini:

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role`
{
    "Code" : "Success",
    "LastUpdated" : "2012-04-26T16:39:16Z",
    "Type" : "AWS-HMAC",
    "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
    "SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "Token" : "token",
    "Expiration" : "2017-05-17T15:09:54Z"
}

Klien dapat menggunakannya untuk jangka waktu singkat dan harus mendapatkan sertifikat baru secara berkala (sebelumnya Expiration). Modelnya sederhana: AWS sering merotasi kunci sementara untuk alasan keamanan, namun klien dapat menyimpannya dalam cache selama beberapa menit untuk mengkompensasi penalti kinerja yang terkait dengan perolehan sertifikat baru.

AWS Java SDK harus mengambil alih tanggung jawab untuk mengatur proses ini, tetapi karena alasan tertentu hal ini tidak terjadi.

Setelah mencari masalah di GitHub, kami menemukan masalah #1921. Dia membantu kami menentukan arah untuk “menggali” lebih jauh.

AWS SDK memperbarui sertifikat ketika salah satu kondisi berikut terjadi:

  • Tanggal habis tempo (Expiration) Jatuh ke dalam EXPIRATION_THRESHOLD, di-hardcode hingga 15 menit.
  • Lebih banyak waktu telah berlalu sejak upaya terakhir untuk memperbarui sertifikat daripada REFRESH_THRESHOLD, di-hardcode selama 60 menit.

Untuk melihat tanggal kedaluwarsa sebenarnya dari sertifikat yang kami terima, kami menjalankan perintah cURL di atas dari kontainer dan instans EC2. Masa berlaku sertifikat yang diterima dari wadah ternyata jauh lebih pendek: tepatnya 15 menit.

Sekarang semuanya menjadi jelas: untuk permintaan pertama, layanan kami menerima sertifikat sementara. Karena tidak valid selama lebih dari 15 menit, AWS SDK akan memutuskan untuk memperbaruinya pada permintaan berikutnya. Dan ini terjadi pada setiap permintaan.

Mengapa masa berlaku sertifikat menjadi lebih pendek?

AWS Instance Metadata dirancang untuk bekerja dengan instans EC2, bukan Kubernetes. Di sisi lain, kami tidak ingin mengubah antarmuka aplikasi. Untuk ini kami menggunakan KIAM - sebuah alat yang, dengan menggunakan agen pada setiap node Kubernetes, memungkinkan pengguna (insinyur yang menyebarkan aplikasi ke sebuah cluster) untuk menetapkan IAM role ke container di pod seolah-olah mereka adalah instance EC2. KIAM mencegat panggilan ke layanan AWS Instance Metadata dan memprosesnya dari cache, setelah sebelumnya menerimanya dari AWS. Dari sudut pandang aplikasi, tidak ada perubahan.

KIAM memasok sertifikat jangka pendek ke pod. Hal ini masuk akal mengingat umur rata-rata sebuah pod lebih pendek dibandingkan dengan instans EC2. Masa berlaku default untuk sertifikat sama dengan 15 menit yang sama.

Akibatnya, jika Anda melapisi kedua nilai default di atas satu sama lain, masalah akan muncul. Setiap sertifikat yang diberikan ke aplikasi akan kedaluwarsa setelah 15 menit. Namun, AWS Java SDK memaksa perpanjangan sertifikat apa pun yang tersisa kurang dari 15 menit sebelum tanggal kedaluwarsanya.

Akibatnya, sertifikat sementara terpaksa diperbarui dengan setiap permintaan, yang memerlukan beberapa panggilan ke API AWS dan menyebabkan peningkatan latensi yang signifikan. Di AWS Java SDK kami menemukan permintaan fitur, yang menyebutkan masalah serupa.

Solusinya ternyata sederhana. Kami cukup mengkonfigurasi ulang KIAM untuk meminta sertifikat dengan masa berlaku lebih lama. Setelah hal ini terjadi, permintaan mulai mengalir tanpa partisipasi layanan AWS Metadata, dan latensi turun ke tingkat yang lebih rendah dibandingkan di EC2.

Temuan

Berdasarkan pengalaman kami dalam migrasi, salah satu sumber masalah paling umum bukanlah bug di Kubernetes atau elemen platform lainnya. Ini juga tidak mengatasi kelemahan mendasar apa pun pada layanan mikro yang kami porting. Masalah sering kali muncul hanya karena kita menyatukan elemen-elemen yang berbeda.

Kami menggabungkan sistem-sistem kompleks yang belum pernah berinteraksi satu sama lain sebelumnya, dengan harapan bahwa bersama-sama mereka akan membentuk satu sistem yang lebih besar. Sayangnya, semakin banyak elemen, semakin banyak ruang untuk kesalahan, semakin tinggi entropinya.

Dalam kasus kami, latensi tinggi bukan disebabkan oleh bug atau keputusan buruk di Kubernetes, KIAM, AWS Java SDK, atau layanan mikro kami. Ini adalah hasil penggabungan dua pengaturan default independen: satu di KIAM, yang lainnya di AWS Java SDK. Jika dilihat secara terpisah, kedua parameter tersebut masuk akal: kebijakan pembaruan sertifikat aktif di AWS Java SDK, dan masa berlaku singkat sertifikat di KAIM. Namun ketika Anda menggabungkannya, hasilnya menjadi tidak dapat diprediksi. Dua solusi yang independen dan logis tidak harus masuk akal jika digabungkan.

PS dari penerjemah

Anda dapat mempelajari lebih lanjut tentang arsitektur utilitas KIAM untuk mengintegrasikan AWS IAM dengan Kubernetes di Artikel ini dari penciptanya.

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komentar