reverse proxy nginx - coba upstream A, lalu B, lalu A lagi

22

Saya mencoba mengatur nginx sebagai proxy terbalik, dengan sejumlah besar server backend. Saya ingin memulai backend on-demand (berdasarkan permintaan pertama yang masuk), jadi saya memiliki proses kontrol (dikendalikan oleh permintaan HTTP) yang memulai backend tergantung pada permintaan yang diterimanya.

Masalah saya adalah mengkonfigurasi nginx untuk melakukannya. Inilah yang saya miliki sejauh ini:

server {
    listen 80;
    server_name $DOMAINS;

    location / {
        # redirect to named location
        #error_page 418 = @backend;
        #return 418; # doesn't work - error_page doesn't work after redirect

        try_files /nonexisting-file @backend;
    }

    location @backend {
        proxy_pass http://$BACKEND-IP;
        error_page 502 @handle_502; # Backend server down? Try to start it
    }

    location @handle_502 { # What to do when the backend server is not up
        # Ping our control server to start the backend
        proxy_pass http://127.0.0.1:82;
        # Look at the status codes returned from control server
        proxy_intercept_errors on;
        # Fallback to error page if control server is down
        error_page 502 /fatal_error.html;
        # Fallback to error page if control server ran into an error
        error_page 503 /fatal_error.html;
        # Control server started backend successfully, retry the backend
        # Let's use HTTP 451 to communicate a successful backend startup
        error_page 451 @backend;
    }

    location = /fatal_error.html {
        # Error page shown when control server is down too
        root /home/nginx/www;
        internal;
    }
}

Ini tidak berfungsi - nginx tampaknya mengabaikan kode status apa pun yang dikembalikan dari server kontrol. Tidak ada error_pagearahan di @handle_502lokasi yang berfungsi, dan kode 451 akan dikirim apa adanya ke klien.

Saya menyerah mencoba menggunakan redirection nginx internal untuk ini, dan mencoba memodifikasi server kontrol untuk memancarkan 307 redirect ke lokasi yang sama (sehingga klien akan mencoba kembali permintaan yang sama, tetapi sekarang dengan server backend memulai). Namun, sekarang nginx dengan bodohnya menimpa kode status dengan kode yang didapatnya dari upaya permintaan backend (502), meskipun server kontrol mengirim header "Lokasi". Saya akhirnya "bekerja" dengan mengubah baris error_page menjadierror_page 502 =307 @handle_502;, sehingga memaksa semua balasan server kontrol untuk dikirim kembali ke klien dengan kode 307. Ini sangat hacky dan tidak diinginkan, karena 1) tidak ada kontrol atas apa yang harus dilakukan nginx selanjutnya tergantung pada respon server kontrol (idealnya kami hanya ingin mencoba kembali backend hanya jika server kontrol melaporkan keberhasilan), dan 2) tidak semua HTTP klien mendukung pengalihan HTTP (mis. pengguna curl dan aplikasi yang menggunakan libcurl perlu mengaktifkan pengalihan berikut secara eksplisit).

Apa cara yang tepat untuk mendapatkan nginx untuk mencoba proksi ke server hulu A, lalu B, lalu A lagi (idealnya, hanya ketika B mengembalikan kode status tertentu)?

Vladimir Panteleev
sumber

Jawaban:

20

Poin-poin penting:

  • Jangan repot-repot dengan upstreamblok untuk failover, jika ping satu server akan membawa yang lain - tidak ada cara untuk memberitahu nginx (setidaknya, bukan versi FOSS) bahwa server pertama naik lagi. nginx akan mencoba server sesuai permintaan pertama, tetapi tidak menindaklanjuti permintaan, meskipun ada backup, weightatau fail_timeoutpengaturan.
  • Anda harus mengaktifkan recursive_error_pagesketika menerapkan failover menggunakan error_pagedan menamai lokasi.
  • Memungkinkan proxy_intercept_errorsuntuk menangani kode kesalahan yang dikirim dari server hulu.
  • The =sintaks (misalnya error_page 502 = @handle_502;) diperlukan untuk benar menangani kode kesalahan di lokasi bernama. Jika =tidak digunakan, nginx akan menggunakan kode kesalahan dari blok sebelumnya.

Jawaban asli / log penelitian ikuti:


Inilah solusi yang lebih baik yang saya temukan, yang merupakan peningkatan karena tidak memerlukan redirect klien:

upstream aba {
    server $BACKEND-IP;
    server 127.0.0.1:82 backup;
    server $BACKEND-IP  backup;
}

...

location / {
    proxy_pass http://aba;
    proxy_next_upstream error http_502;
}

Kemudian, dapatkan server kontrol untuk mengembalikan 502 pada "sukses" dan berharap kode tidak pernah dikembalikan oleh backends.


Pembaruan: nginx terus menandai entri pertama di upstreamblok sebagai turun, sehingga tidak mencoba server untuk permintaan yang berurutan. Saya sudah mencoba menambah weight=1000000000 fail_timeout=1entri pertama tanpa efek. Sejauh ini saya belum menemukan solusi yang tidak melibatkan redirect klien.


Sunting: Satu hal lagi yang saya harap saya tahu - untuk mendapatkan status kesalahan dari error_pagehandler, gunakan sintaks ini: error_page 502 = @handle_502;- yang sama dengan tanda akan menyebabkan nginx mendapatkan status kesalahan dari handler.


Sunting: Dan saya membuatnya bekerja! Selain error_pageperbaikan di atas, semua yang diperlukan adalah memungkinkan recursive_error_pages!

Vladimir Panteleev
sumber
1
Bagi saya proxy_next_upstreamtriknya (skenario saya tidak serumit milik Anda), saya hanya ingin nginx untuk mencoba server berikutnya jika ada kesalahan, jadi saya harus menambahkan proxy_next_upstream error timeout invalid_header non_idempotent;( non_idempotent, karena saya ingin meneruskan POSTpermintaan).
Philipp
1

Anda dapat mencoba sesuatu seperti yang berikut ini

upstream backend {
    server a.example.net;
    server b.example.net backup;
}

server {
    listen   80;
    server_name www.example.net;

    proxy_next_upstream error timeout http_502;

    location / {
        proxy_pass http://backend;
        proxy_redirect      off;
        proxy_set_header    Host              $host;
        proxy_set_header    X-Real-IP         $remote_addr;
        proxy_set_header    X-Forwarded-for   $remote_addr;
    }

}
ALex_hha
sumber
nginx tidak akan mencoba lagi a.example.netsetelah gagal sekali pada permintaan yang sama. Ini akan mengirim ke klien kesalahan yang ditemui ketika mencoba terhubung b.example.net, yang tidak akan menjadi apa yang mereka harapkan kecuali saya akan mengimplementasikan proxy di server kontrol juga.
Vladimir Panteleev
Dan apa yang terjadi dengan konfigurasi Anda dalam situasi berikutnya: permintaan ke hulu A kembali gagal, hulu B kembali gagal, lalu kami lagi mencoba hulu A dan juga gagal (502)?
ALex_hha
Hulu B adalah server kontrol. Tujuannya adalah untuk memastikan bahwa permintaan berikutnya ke hulu A akan berhasil. Tujuannya adalah untuk mencoba upstream A, jika gagal coba upstream B, jika "berhasil" (menggunakan konvensi internal kami tentang "sukses"), coba upstream A lagi. Jika pertanyaan saya tidak cukup jelas, beri tahu saya cara memperbaikinya.
Vladimir Panteleev
Hmm, mari kita asumsikan hulu A turun, untuk misalnya beberapa masalah perangkat keras. Apa yang akan membuat hulu B? Apakah mampu mengembalikan respons ke permintaan dari klien?
ALex_hha
Masalah ini bukan tentang failover untuk kegagalan perangkat keras. Masalah ini adalah tentang memulai backend on-demand. Jika server kontrol (hulu B) tidak dapat mengaktifkan kembali backend (hulu A), maka idealnya pengguna harus mendapatkan pesan kesalahan yang sesuai, tetapi itu bukan masalah yang saya coba pecahkan - masalahnya semakin nginx untuk mencoba lagi A setelah B lagi, dalam permintaan yang sama.
Vladimir Panteleev