NGINX - Creare un Load Balancer per server DNS

NGINX è -tra le altre cose- un ottimo load Balancer. In questa guida vedremo come usarlo per mettere più server DNS in Load Balancing.

Condividi
NGINX - Creare un Load Balancer per server DNS

Introduzione

NGINX (pronunciato Engine-X) è

"un HTTP web server, reverse proxy, content cache, load balancer, TCP/UDP proxy server, e mail proxy server"

utilizzato dalla maggioranza degli HomeLabber.
Permette di fare Reverse Proxy con poche righe di configurazione ed offre prestazioni spaventose con un minimo di risorse a disposizione.

In questa guida vedremo come usarlo per mettere più server DNS in Load Balancing. Nello specifico, gli faremo gestire due server AdGuard Home per avere un DNS bilanciato, che gestisca il fault di uno dei server, e soprattutto per non avere più quella fastidiosissima pubblicità in tutta la rete di casa.

Prerequisiti

Prima di seguire i passaggi indicati di seguito, è necessario avere almeno un nodo con NGINX installato, ed almeno due server DNS. Poco importa di quale server DNS si tratti, la configurazione è universale.

Dettaglio molto importante: per ovvi motivi (due software non posso usare la stessa porta in ascolto) non potete effettuare questa configurazione se sulla macchina dove risiede NGINX avete anche un DNS Server.

❗
NGINX è compatibile con la maggior parte dei sistemi operativi Linux, ma in questa guida userò i comandi che ho usato su Ubuntu Server 24.04

Installazione delle dipendenze

Il modulo di NGINX che ci permette di gestire questo tipo di load balancing è il modulo stream, che non è fornito di default nelle nuove installazioni di NGINX.

Installarlo è semplice, infatti è disponibile su apt:

sudo apt install -y libnginx-mod-stream

Controllare che sia stato creato il file 50-mod-stream.conf

nano /etc/nginx/modules-available/50-mod-stream.conf
❗
L'editor di testo potrebbe mostrare un file vuoto, ma non dovrebbe segnalare che il file non sia esistente. In tal caso controllare di aver effettivamente installato il modulo.

Se il file è vuoto, compilarlo con la seguente direttiva per indicare ad NGINX come caricare il modulo stream:

load_module modules/ngx_stream_module.so;

Ricaricare NGINX per fargli caricare il modulo appena installato

systemctl reload nginx

Configurazione su NGINX

Una volta caricato il modulo stream, è necessario configurarlo. La configurazione può essere fatta direttamente sul file /etc/nginx/nginx.conf , anche se usare il meccanismo degli include e dei symlink sarebbe la best practice. In questo esempio inseriamo la configurazione direttamente in nginx.conf

nano /etc/nginx/nginx.conf

La sezione stream

Prima di tutto, va creata la sezione stream, nel primo livello del file di configurazione. Consiglio di crearla sopra alla sezione http (mi raccomando, non al suo interno)

stream {

}

Dentro alla sezione stream, creiamo una sezione upstream dove andiamo ad elencare una lista di server DNS. Questa lista deve avere un nome (a cui faremo riferimento successivamente, nella sezione server)

stream {
  upstream dns_servers {
    server 192.168.0.27:53;
    server 192.168.0.28:53;
    server 192.168.0.1:53;
  }
}

In questa configurazione, la lista contiene tre server DNS che NGINX tratterà in modo equo. Per esempio, in round robin, a turno verrà interrogato ciascuno dei tre server indicati.

Nel mio caso, 192.168.0.1 è il mio Gateway, che però non è un DNS AdGuard. Voglio che venga utilizzato solamente se gli altri server DNS vanno offline.
Per fare questo, lo indico come server di Backup.

stream {
  upstream dns_servers {
    server 192.168.0.27:53;
    server 192.168.0.28:53;
    server 192.168.0.1:53 backup;
  }
}

Infine, per indicare con quale criterio decidere quando un server DNS non va più usato perchè offline, uso i seguenti parametri:

  • max_fails per indicare quante richieste senza risposta mi servono per giudicarlo offline.
  • fail_timeout per indicare dopo quanto tempo decretare che una richiesta non ha ricevuto risposta.
stream {
  upstream dns_servers {
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.1:53 backup;
  }
}

In questo modo, se per tre volte di fila un server DNS non risponde entro 5 secondi, viene contato come offline.

Se entrambi i server DNS AdGuard sono offline, viene utilizzato il Gateway (che non bloccherà certo le pubblicità, ma è meglio di niente!)

Altre modalità di load balancing

La configurazione mostrata sopra utilizzerà un algoritmo round robin per smistare le richieste, cioè utilizzerà a turno ciascuno dei server. Altre modalità possibili con NGINX sono:

  • least connections, cioè la richiesta verrà passata al server DNS che al momento sta gestendo meno connessioni (ma nel caso di un server DNS, spesso entrambi saranno molto scarichi)
stream {
  upstream dns_servers {
    least_conn;
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.1:53 backup;
  }
}
  • max connections, cioè l'algoritmo Round Robin, ma escludendo temporaneamente uno dei server quando raggiunge il numero massimo di connessioni indicato (in questo esempio, 3 per il primo e 5 per il secondo)
stream {
  upstream dns_servers {
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s max_conns=3;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s max_conns=5;
    server 192.168.0.1:53 backup;
  }
}
  • weighted, cioè bilanciando in maniera pesata le richieste. Nell'esempio sotto, se devono essere gestite 300 richieste, 200 verranno mandate al primo, 100 al secondo. Questo parametro è molto utile se i vostri DNS non hanno le stesse risorse a disposizione.
stream {
  upstream dns_servers {
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s weight=2;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s weight=1;
    server 192.168.0.1:53 backup;
  }
}
  • IP Hashing (o più comunemente chiamata connessione persistente) che ha molto poco senso per i DNS, ma utile negli scenari in cui serve che lo stesso client venga gestito sempre dallo stesso server.
    Ovviamente, se il server a cui è stato associato uno specifico client diventa offline, le richieste vengono inoltrate ad uno dei restanti.
stream {
  upstream dns_servers {
    ip_hash;
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.1:53 backup;
  }
}

La sezione server

Infine, ci serve una sezione server per dire ad NGINX che cosa deve fare, su quali porte, e dove loggare eventuali errori.

stream {
  upstream dns_servers {
    server 192.168.0.27:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.28:53 max_fails=3 fail_timeout=5s;
    server 192.168.0.1:53 backup;
  }

  server {
    listen 53 udp;
    listen 53;

    proxy_responses 1;
    proxy_pass dns_servers;
    error_log /var/log/nginx/dns.log error;
  }
}

Analizziamo la sezione server riga per riga:

  • listen 53 udp dice ad NGINX di rimanere in ascolto sulla porta 53 UDP, dove arriveranno la maggior parte delle richieste DNS.
  • listen 53 dice ad NGINX di rimanere in ascolto sulla 53 anche in TCP, dove in casi limite possono arrivare alcune richieste DNS.
  • proxy_responses impostato ad 1 fa sì che NGINX chiuda il socket quando riceve una risposta dal server DNS. Sappiamo per certo che in una richiesta DNS questo è il limite di risposte, ed in questo modo liberiamo subito il processo che è stato aperto al momento della richiesta del client.
    Questo parametro non è strettamente necessario, ma ho notato che migliora notevolmente le performance.
  • proxy_pass dns_servers indica ad NGINX di far riferimento, per i server a cui inoltrare la richiesta, a quelli che abbiamo indicato in dns_servers, con le regole che abbiamo indicato.
  • infine, error_log /var/log/nginx/dns.log error fa sì che NGINX scriva i log di errore relativi a questa regola in un file dns.log . Anche questo parametro non è strettamente necessario, ma se ci dovesse essere bisogno di fare troubleshooting, sarà sicuramente meglio avere un file dedicato alle richieste DNS (e lo dico per esperienza).

Adesso non rimane che far caricare ad NGINX le ultime modifiche al file di configurazione, e goderci lo spettacolo.

systemctl reload nginx

Controllate subito dopo che il servizio si sia ricaricato senza errori

systemctl status nginx
❗
Se un simpatico messaggio relativo alla porta 53 già in uso da un altro processo vi accoglie, si tratta di quel mascalzone di systemd-resolved che se l'è presa tutta per sè. Dando per scontato che non abbiate installato un server DNS sulla stessa macchina dove avete installato NGINX, questo binding non ci serve e potete disattivarlo seguenti questi passaggi.

Conclusioni

Ora che avete messo in load balancing i vostri DNS, potrete finalmente dormire sonni tranquilli. La maggior parte dei problemi di rete è imputabile a problemi con la risoluzione dei nomi, e ridondando questo servizio vi siete risparmiati un sacco di problemi.

Se avete messo in load balancing delle istanze di AdGuard, perchè adesso non le monitorate tramite Telegraf, Prometheus e Grafana?