web-dev-qa-db-fra.com

proxy inverse nginx - essayez en amont A, puis B, puis A à nouveau

J'essaie de configurer nginx en tant que proxy inverse, avec un grand nombre de serveurs principaux. Je voudrais démarrer les backends à la demande (à la première demande qui arrive), j'ai donc un processus de contrôle (contrôlé par les requêtes HTTP) qui démarre le backend en fonction de la demande qu'il reçoit.

Mon problème est de configurer nginx pour le faire. Voici ce que j'ai jusqu'à présent:

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;
    }
}

Cela ne fonctionne pas - nginx semble ignorer tous les codes d'état renvoyés par le serveur de contrôle. Aucun des error_page directives dans le @handle_502 l'emplacement fonctionne et le code 451 est envoyé tel quel au client.

J'ai renoncé à essayer d'utiliser la redirection interne nginx pour cela, et j'ai essayé de modifier le serveur de contrôle pour émettre une redirection 307 vers le même emplacement (afin que le client réessaye la même demande, mais maintenant avec le serveur principal démarré). Cependant, maintenant nginx écrase stupidement le code d'état avec celui qu'il a obtenu lors de la tentative de demande de backend (502), malgré le fait que le serveur de contrôle envoie un en-tête "Location". Je l'ai finalement "fait fonctionner" en changeant la ligne error_page en error_page 502 =307 @handle_502;, forçant ainsi toutes les réponses du serveur de contrôle à être renvoyées au client avec un code 307. C'est très hacky et indésirable, car 1) il n'y a aucun contrôle sur ce que nginx devrait faire ensuite en fonction de la réponse du serveur de contrôle (idéalement, nous ne voulons réessayer le backend que si le serveur de contrôle signale le succès), et 2) pas tous HTTP les clients prennent en charge les redirections HTTP (par exemple, les utilisateurs curl et les applications utilisant libcurl doivent activer explicitement les redirections suivantes).

Quelle est la bonne façon de faire en sorte que nginx essaie de faire un proxy vers le serveur en amont A, puis B, puis A de nouveau (idéalement, uniquement lorsque B renvoie un code d'état spécifique)?

23
Vladimir Panteleev

Points clés:

  • Ne vous embêtez pas avec les blocs upstream pour le basculement, si envoyer un ping à un serveur en amènera un autre - il n'y a aucun moyen de dire à nginx (du moins, pas la version FOSS) que le premier serveur est de nouveau opérationnel. nginx essaiera les serveurs dans l'ordre lors de la première demande, mais pas les demandes de suivi, malgré les backup, weight ou fail_timeout réglages.
  • Vous devez activer recursive_error_pages lors de l'implémentation du basculement à l'aide de error_page et les emplacements nommés.
  • Activer proxy_intercept_errors pour gérer les codes d'erreur envoyés par le serveur en amont.
  • Le = syntaxe (par exemple error_page 502 = @handle_502;) est requis pour gérer correctement les codes d'erreur à l'emplacement nommé. Si = n'est pas utilisé, nginx utilisera le code d'erreur du bloc précédent.

Le rapport original de réponse/recherche suit:


Voici un meilleur solution que j'ai trouvée, ce qui est une amélioration car elle ne nécessite pas de redirection client:

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;
}

Ensuite, demandez au serveur de contrôle de renvoyer 502 en cas de "succès" et espérez que le code ne sera jamais renvoyé par les backends.


Mise à jour: nginx continue de marquer la première entrée du bloc upstream comme étant en panne, il n'essaie donc pas les serveurs dans l'ordre lors de requêtes successives. J'ai essayé d'ajouter weight=1000000000 fail_timeout=1 à la première entrée sans effet. Jusqu'à présent, je n'ai trouvé aucune solution n'impliquant pas de redirection client.


Edit: Encore une chose que j'aimerais savoir - pour obtenir le statut d'erreur à partir du error_page gestionnaire, utilisez cette syntaxe: error_page 502 = @handle_502; - ce signe égal fera que nginx obtiendra le statut d'erreur du gestionnaire.


Edit: Et je l'ai fait fonctionner! En plus de error_page correction ci-dessus, il suffisait d'activer recursive_error_pages!

20
Vladimir Panteleev

Vous pouvez essayer quelque chose comme le suivant

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;
    }

}
1
ALex_hha