Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM

Kondisi umum saat mengimplementasikan CI/CD di Kubernetes: aplikasi harus tidak dapat menerima permintaan klien baru sebelum berhenti sepenuhnya, dan yang terpenting, berhasil menyelesaikan permintaan yang sudah ada.

Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM

Kepatuhan terhadap kondisi ini memungkinkan Anda mencapai nol waktu henti selama penerapan. Namun, bahkan ketika menggunakan bundel yang sangat populer (seperti NGINX dan PHP-FPM), Anda dapat menemui kesulitan yang akan menyebabkan lonjakan kesalahan pada setiap penerapan...

Teori. Bagaimana pod hidup

Kami telah mempublikasikan secara rinci tentang siklus hidup sebuah pod artikel ini. Dalam konteks topik yang sedang dibahas, kami tertarik pada hal berikut: pada saat pod memasuki status Mengakhiri, permintaan baru berhenti dikirim ke sana (pod DIHAPUS dari daftar titik akhir untuk layanan). Oleh karena itu, untuk menghindari downtime selama penerapan, cukup kita menyelesaikan masalah penghentian aplikasi dengan benar.

Anda juga harus ingat bahwa masa tenggang default adalah 30 detik: setelah ini, pod akan dihentikan dan aplikasi harus memiliki waktu untuk memproses semua permintaan sebelum periode ini. Catatan: meskipun permintaan apa pun yang memakan waktu lebih dari 5-10 detik sudah menjadi masalah, dan penutupan yang baik tidak akan membantu lagi...

Untuk lebih memahami apa yang terjadi ketika sebuah pod dihentikan, lihat saja diagram berikut:

Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM

A1, B1 - Menerima perubahan keadaan perapian
A2 - SIGTERM Keberangkatan
B2 - Menghapus pod dari titik akhir
B3 - Menerima perubahan (daftar titik akhir telah berubah)
B4 - Perbarui aturan iptables

Harap dicatat: penghapusan pod titik akhir dan pengiriman SIGTERM tidak terjadi secara berurutan, tetapi secara paralel. Dan karena Ingress tidak segera menerima daftar Endpoint yang diperbarui, permintaan baru dari klien akan dikirim ke pod, yang akan menyebabkan kesalahan 500 selama penghentian pod (untuk materi lebih rinci tentang masalah ini, kami diterjemahkan). Masalah ini perlu diselesaikan dengan cara-cara berikut:

  • Kirim Koneksi: tutup di header respons (jika ini menyangkut aplikasi HTTP).
  • Jika tidak memungkinkan untuk membuat perubahan pada kode, artikel berikut menjelaskan solusi yang memungkinkan Anda memproses permintaan hingga akhir masa tenggang.

Teori. Bagaimana NGINX dan PHP-FPM menghentikan prosesnya

nginx

Mari kita mulai dengan NGINX, karena semuanya kurang lebih jelas dengannya. Dengan mendalami teorinya, kita mengetahui bahwa NGINX memiliki satu proses master dan beberapa “pekerja” - ini adalah proses turunan yang memproses permintaan klien. Opsi mudah disediakan: menggunakan perintah nginx -s <SIGNAL> menghentikan proses baik dalam mode pematian cepat atau pematian dengan anggun. Tentu saja, pilihan terakhir itulah yang menarik minat kami.

Maka semuanya sederhana: Anda perlu menambahkan kait preStop sebuah perintah yang akan mengirimkan sinyal mematikan dengan baik. Ini dapat dilakukan di Deployment, di blok container:

       lifecycle:
          preStop:
            exec:
              command:
              - /usr/sbin/nginx
              - -s
              - quit

Sekarang, ketika pod dimatikan, kita akan melihat yang berikut ini di log container NGINX:

2018/01/25 13:58:31 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2018/01/25 13:58:31 [notice] 11#11: gracefully shutting down

Dan ini berarti apa yang kita butuhkan: NGINX menunggu permintaan selesai, lalu menghentikan prosesnya. Namun, di bawah ini kami juga akan mempertimbangkan masalah umum yang menyebabkannya, bahkan dengan perintah nginx -s quit proses dihentikan secara tidak benar.

Dan pada tahap ini kita selesai dengan NGINX: setidaknya dari log Anda dapat memahami bahwa semuanya berjalan sebagaimana mestinya.

Apa masalahnya dengan PHP-FPM? Bagaimana cara menangani penutupan yang baik? Mari kita cari tahu.

PHP-FPM

Dalam kasus PHP-FPM, informasinya sedikit lebih sedikit. Jika Anda fokus pada panduan resmi menurut PHP-FPM, akan dikatakan bahwa sinyal POSIX berikut diterima:

  1. SIGINT, SIGTERM — penutupan cepat;
  2. SIGQUIT — penutupan yang anggun (apa yang kita butuhkan).

Sinyal lainnya tidak diperlukan dalam tugas ini, jadi kami akan menghilangkan analisisnya. Untuk menghentikan proses dengan benar, Anda perlu menulis hook preStop berikut:

        lifecycle:
          preStop:
            exec:
              command:
              - /bin/kill
              - -SIGQUIT
              - "1"

Pada pandangan pertama, hanya ini yang diperlukan untuk melakukan pematian yang baik di kedua kontainer. Namun, tugas ini lebih sulit dari yang terlihat. Di bawah ini adalah dua kasus di mana penghentian yang baik tidak berfungsi dan menyebabkan tidak tersedianya proyek dalam jangka pendek selama penerapan.

Praktik. Kemungkinan masalah dengan pematian yang baik

nginx

Pertama-tama, perlu diingat: selain menjalankan perintah nginx -s quit Ada satu tahap lagi yang patut diperhatikan. Kami mengalami masalah ketika NGINX masih mengirimkan SIGTERM dan bukan sinyal SIGQUIT, sehingga menyebabkan permintaan tidak diselesaikan dengan benar. Kasus serupa juga dapat ditemukan, misalnya di sini. Sayangnya, kami tidak dapat menentukan alasan spesifik untuk perilaku ini: ada kecurigaan tentang versi NGINX, namun tidak dikonfirmasi. Gejalanya adalah pesan-pesan diamati di log kontainer NGINX "buka soket #10 yang tersisa di koneksi 5", setelah itu pod berhenti.

Kita dapat mengamati masalah seperti ini, misalnya dari respon pada Ingress yang kita perlukan:

Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM
Indikator kode status pada saat penerapan

Dalam kasus ini, kami hanya menerima kode kesalahan 503 dari Ingress sendiri: ia tidak dapat mengakses container NGINX karena tidak dapat diakses lagi. Jika Anda melihat log kontainer dengan NGINX, log tersebut berisi hal berikut:

[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13

Setelah mengubah sinyal berhenti, kontainer mulai berhenti dengan benar: ini dikonfirmasi oleh fakta bahwa kesalahan 503 tidak lagi terlihat.

Jika Anda mengalami masalah serupa, masuk akal untuk mencari tahu sinyal berhenti apa yang digunakan dalam wadah dan seperti apa sebenarnya bentuk kait preStop. Mungkin saja alasannya justru terletak pada hal ini.

PHP-FPM... dan banyak lagi

Masalah dengan PHP-FPM dijelaskan dengan cara yang sepele: ia tidak menunggu selesainya proses anak, ia menghentikannya, itulah sebabnya kesalahan 502 terjadi selama penerapan dan operasi lainnya. Ada beberapa laporan bug di bug.php.net sejak tahun 2005 (mis di sini и di sini), yang menjelaskan masalah ini. Namun kemungkinan besar Anda tidak akan melihat apa pun di log: PHP-FPM akan mengumumkan penyelesaian prosesnya tanpa kesalahan atau pemberitahuan pihak ketiga.

Perlu diklarifikasi bahwa masalah itu sendiri mungkin sedikit banyak bergantung pada aplikasi itu sendiri dan mungkin tidak muncul dengan sendirinya, misalnya, dalam pemantauan. Jika Anda menemukannya, solusi sederhana pertama kali terlintas dalam pikiran Anda: tambahkan kait preStop dengan sleep(30). Ini akan memungkinkan Anda untuk menyelesaikan semua permintaan sebelumnya (dan kami tidak menerima permintaan baru, karena pod sudah mampu Mengakhiri), dan setelah 30 detik pod itu sendiri akan berakhir dengan sinyal SIGTERM.

Ternyata bahwa lifecycle untuk containernya akan terlihat seperti ini:

    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sleep
          - "30"

Namun karena 30 detik sleep kami adalah sangat kuat kami akan menambah waktu penerapan, karena setiap pod akan dihentikan minimum 30 detik, itu buruk. Apa yang bisa dilakukan mengenai hal ini?

Mari kita beralih ke pihak yang bertanggung jawab atas pelaksanaan langsung aplikasi tersebut. Dalam kasus kami, memang demikian PHP-FPMYang secara default tidak memantau pelaksanaan proses anaknya: Proses master segera dihentikan. Anda dapat mengubah perilaku ini menggunakan arahan process_control_timeout, yang menentukan batas waktu proses anak menunggu sinyal dari master. Jika Anda menetapkan nilainya menjadi 20 detik, ini akan mencakup sebagian besar kueri yang berjalan di kontainer dan akan menghentikan proses master setelah selesai.

Dengan pengetahuan ini, mari kita kembali ke masalah terakhir kita. Seperti disebutkan, Kubernetes bukanlah platform monolitik: komunikasi antar komponen yang berbeda memerlukan waktu. Hal ini terutama berlaku ketika kita mempertimbangkan pengoperasian Ingresses dan komponen terkait lainnya, karena penundaan pada saat penerapan menyebabkan lonjakan 500 kesalahan. Misalnya, kesalahan mungkin terjadi pada tahap pengiriman permintaan ke upstream, namun “jeda waktu” interaksi antar komponen cukup singkat - kurang dari satu detik.

Oleh karena itu, Secara keseluruhan dengan arahan yang telah disebutkan process_control_timeout Anda dapat menggunakan konstruksi berikut untuk lifecycle:

lifecycle:
  preStop:
    exec:
      command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"]

Dalam hal ini, kami akan mengkompensasi penundaan tersebut dengan perintah sleep dan tidak terlalu menambah waktu penerapan: apakah ada perbedaan mencolok antara 30 detik dan satu detik?.. Faktanya, ini adalah process_control_timeoutDan lifecycle hanya digunakan sebagai “jaring pengaman” jika terjadi kelambatan.

Secara umum perilaku yang dijelaskan dan solusi terkait tidak hanya berlaku untuk PHP-FPM. Situasi serupa mungkin muncul ketika menggunakan bahasa/kerangka lain. Jika Anda tidak dapat memperbaiki penghentian yang baik dengan cara lain - misalnya, dengan menulis ulang kode sehingga aplikasi memproses sinyal penghentian dengan benar - Anda dapat menggunakan metode yang dijelaskan. Ini mungkin bukan yang terindah, tapi berhasil.

Praktik. Muat pengujian untuk memeriksa pengoperasian pod

Pengujian beban adalah salah satu cara untuk memeriksa cara kerja container, karena prosedur ini mendekatkan container ke kondisi pertempuran sebenarnya saat pengguna mengunjungi lokasi. Untuk menguji rekomendasi di atas, Anda dapat menggunakan Yandex.Tankom: Ini mencakup semua kebutuhan kita dengan sempurna. Berikut ini adalah tips dan rekomendasi untuk melakukan pengujian dengan contoh nyata dari pengalaman kami berkat grafik Grafana dan Yandex.Tank itu sendiri.

Yang paling penting di sini adalah periksa perubahan langkah demi langkah. Setelah menambahkan perbaikan baru, jalankan pengujian dan lihat apakah hasilnya telah berubah dibandingkan dengan pengujian terakhir. Jika tidak, akan sulit untuk mengidentifikasi solusi yang tidak efektif, dan dalam jangka panjang hal ini hanya akan merugikan (misalnya, menambah waktu penerapan).

Nuansa lainnya adalah dengan melihat log container selama penghentiannya. Apakah informasi tentang penutupan yang baik dicatat di sana? Apakah ada kesalahan dalam log saat mengakses sumber daya lain (misalnya, ke wadah PHP-FPM yang berdekatan)? Kesalahan dalam aplikasi itu sendiri (seperti halnya NGINX yang dijelaskan di atas)? Saya harap informasi pengantar dari artikel ini akan membantu Anda lebih memahami apa yang terjadi pada container selama penghentiannya.

Jadi, uji coba pertama berlangsung tanpa lifecycle dan tanpa arahan tambahan untuk server aplikasi (process_control_timeout dalam PHP-FPM). Tujuan dari pengujian ini adalah untuk mengidentifikasi perkiraan jumlah kesalahan (dan apakah ada). Selain itu, dari informasi tambahan, Anda harus mengetahui bahwa waktu penerapan rata-rata untuk setiap pod adalah sekitar 5-10 detik hingga pod siap sepenuhnya. Hasilnya adalah:

Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM

Panel informasi Yandex.Tank menunjukkan lonjakan 502 kesalahan, yang terjadi pada saat penerapan dan berlangsung rata-rata hingga 5 detik. Agaknya hal ini terjadi karena permintaan yang ada ke pod lama dihentikan ketika pod tersebut dihentikan. Setelah ini, kesalahan 503 muncul, yang merupakan akibat dari penghentian container NGINX, yang juga memutus koneksi karena backend (yang mencegah Ingress menyambung ke sana).

Mari kita lihat caranya process_control_timeout di PHP-FPM akan membantu kita menunggu selesainya proses anak, mis. memperbaiki kesalahan tersebut. Terapkan ulang menggunakan arahan ini:

Tip & trik Kubernetes: fitur shutdown yang baik di NGINX dan PHP-FPM

Tidak ada lagi kesalahan selama penerapan ke-500! Penerapan berhasil, pematian yang baik berhasil.

Namun, perlu diingat masalah dengan kontainer Ingress, persentase kecil kesalahan yang mungkin kami terima karena jeda waktu. Untuk menghindarinya, yang tersisa hanyalah menambahkan struktur dengan sleep dan ulangi penerapannya. Namun, dalam kasus khusus kami, tidak ada perubahan yang terlihat (sekali lagi, tidak ada kesalahan).

Kesimpulan

Untuk menghentikan proses dengan baik, kami mengharapkan perilaku berikut dari aplikasi:

  1. Tunggu beberapa detik lalu berhenti menerima koneksi baru.
  2. Tunggu hingga semua permintaan selesai dan tutup semua koneksi keepalive yang tidak menjalankan permintaan.
  3. Akhiri proses Anda.

Namun, tidak semua aplikasi bisa bekerja dengan cara ini. Salah satu solusi untuk masalah realitas Kubernetes adalah:

  • menambahkan pengait pra-berhenti yang akan menunggu beberapa detik;
  • mempelajari file konfigurasi backend kami untuk parameter yang sesuai.

Contoh NGINX memperjelas bahwa bahkan aplikasi yang awalnya memproses sinyal penghentian dengan benar mungkin tidak dapat melakukannya, jadi sangat penting untuk memeriksa 500 kesalahan selama penerapan aplikasi. Hal ini juga memungkinkan Anda untuk melihat masalah secara lebih luas dan tidak fokus pada satu pod atau container saja, namun melihat keseluruhan infrastruktur secara keseluruhan.

Sebagai alat pengujian, Anda dapat menggunakan Yandex.Tank bersama dengan sistem pemantauan apa pun (dalam kasus kami, data diambil dari Grafana dengan backend Prometheus untuk pengujian). Masalah dengan penghentian yang baik terlihat jelas di bawah beban berat yang dapat dihasilkan oleh benchmark, dan pemantauan membantu menganalisis situasi secara lebih rinci selama atau setelah pengujian.

Menanggapi umpan balik pada artikel: perlu disebutkan bahwa masalah dan solusi dijelaskan di sini sehubungan dengan NGINX Ingress. Untuk kasus lain, ada solusi lain, yang dapat kita pertimbangkan dalam materi seri berikut.

PS

Lainnya dari seri tips & trik K8s:

Sumber: www.habr.com

Tambah komentar