Nginx als TCP-Proxy (Beispiel Dovecot)

Hintergrund

Ich betreibe zwei Dovecot-Installationen, die sich untereinander replizieren. Änderungen werden sofort/mit minimaler Verzögerung im entfernten Maildir übernommen. Ein master/master Szenario. (http://wiki2.dovecot.org/Replication)
Weil mir die Zeit fehlt, mich mit echten IMAP-Proxys auseinanderzusetzen, habe ich mich für das Nginx Modul nginx_tcp_proxy_module entschieden.

Ich war ein wenig überrascht, dass das Modul in keinem Pre-Build auftaucht und musste Nginx kurzerhand selber bauen. Das ist vielleicht gar nicht so verkehrt, immerhin kann so ein großer Teil der HTTP-Module deaktiviert werden.

Das Modul liefert außer der reinen TCP-Proxy-Funktionalität noch eine HTTP-Statusseite aus und verwendet weiterhin die „Upstream“-Funktion des Nginx. So ist es leider nicht möglich, HTTP mit dem Switch „–without-http“ komplett zu deaktivieren.

Der Proxy unterstützt zur Zeit die Modi „Round-Robin“ sowie „Busyness“.
Round-Robin: Verfügbare Server im Upstream werden der Reihe nach verwendet.
Busyness: Der Proxy erkennt aktive TCP-Verbindungen zu einem Server im Upstream und wertet „busyness = n-Verbindungen“. Der Server mit den wenigsten Verbindungen wird ausgewählt.

Und UDP? UDP ist ein verbindungsloses Protokoll. Ein Proxy kann diese Verbindung nicht annehmen und weiterleiten, da es den Sender nicht interessiert, ob das Paket angenommen wurde oder überhaupt ankam.

Installiert habe ich den Proxy auf einem Debian Jessie System, wobei der Artikel auf beinahe alle Derivate übertragbar sein sollte.

Let’s build Nginx

Vorab die Abhängigkeiten installieren:

apt-get install libssl-dev build-essential git
Sollten im Verlauf Probleme auftreten, bitte die gesamten „build-deps“ laden („apt-get build-dep nginx“).

Die zur Zeit des Artikels als stabil gekennzeichnete Ningx Version ist 1.6.2, diese lade ich in den Ordner „~/build“ herunter, den ich vorab erstelle. Ebenso in „~/build“ landet das Proxy-Modul, welches aus dem Git-Repository geklont wird:

mkdir ~/build ; cd ~/build
wget -O - http://nginx.org/download/nginx-1.6.2.tar.gz | tar xfvz -
git clone git://github.com/yaoweibin/nginx_tcp_proxy_module

Nginx muss an dieser Stelle durch einen Patch aus dem Proxy-Modul verändert werden:

cd nginx-1.6.2/
patch -p1 < ../nginx_tcp_proxy_module/tcp.patch

Die minimalste Konfiguration, die mir möglich war. Im Anschluss findet der Build-Vorgang statt:

./configure --add-module=../nginx_tcp_proxy_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --without-http_rewrite_module --without-http_charset_module --without-http_gzip_module --without-http_ssi_module --without-http_userid_module --without-http_access_module --without-http_auth_basic_module --without-http_autoindex_module --without-http_geo_module --without-http_map_module --without-http_split_clients_module  --without-http_referer_module --without-http_proxy_module --without-http_fastcgi_module --without-http_uwsgi_module --without-http_scgi_module --without-http_memcached_module --without-http_limit_conn_module --without-http_limit_req_module --without-http_empty_gif_module --without-http_browser_module --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body
make
make install

Obige Konfiguration verwendet die Pfade der Nginx-Installation aus dem offiziellen Repository.

nginx -V gibt die verwendeten Build-Konfiguration aus

Einige Verzeichnisse müssen evtl. noch angelegt- oder die Berechtigungen korrekt vergeben werden:

mkdir -p /usr/share/nginx/logs
mkdir /var/log/nginx
mkdir -p /var/lib/nginx/body
touch /var/log/nginx/error.log /var/logs/nginx/access.log
touch /var/log/nginx/access.log
touch /usr/share/nginx/logs/tcp_access.log
chown -R www-data: /var/{lib,log}/nginx /usr/share/nginx/logs

Das Verzeichnis "/usr/share/nginx/logs" wird vom Proxy verwendet.
Das Konfigurationsverzeichnis darf geleert- und auf das Notwendigste beschränkt werden:

rm -r /etc/nginx/*
nano /etc/nginx/nginx.conf

Hier nun der Inhalt einer Beispielkonfiguration:

user  www-data;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
        server {
                listen 901;

                location /status {
                        tcp_check_status;
                }
        }
}
tcp {
        upstream cluster_imaps {
                server 192.168.2.10:993;
                server 192.168.2.11:993;
                check interval=2000 rise=2 fall=5 timeout=1000 type=tcp;
        }

        server {
                listen 993;
                proxy_pass cluster_imaps;
        }

        upstream cluster_imap {
                server 192.168.2.10:143;
                server 192.168.2.11:143;
                check interval=2000 rise=2 fall=5 timeout=1000 type=imap;
                busyness;
        }

        server {
                listen 143;
                proxy_pass cluster_imap;
        }
}

Im "http"-Block befindet sich die angesprochene HTTP-Statusseite. Ich möchte euch diese Übersicht nicht vorenthalten und habe sie via Reverse-Proxy veröffentlicht: http://mailstatus.debinux.de/

Ein Upstream-Cluster besteht immer aus einem eindeutigen Namen, die Server in diesem Cluster beschreibe ich untereinander, einschließlich des Ports.
Ohne die Angabe des Parameters "busyness" im "upstream"-Block, wird Nginx die Auswahl nach dem Round-Robin Prinzip treffen (siehe oben).

Innerhalb des "tcp"-Blocks gibt es weiterhin den "server"-Block. Hier wird nun der Port eingetragen, auf dem Nginx Verbindungen für das darunter beschriebene Cluster in "proxy_pass" entgegen nimmt.

Zum "check"-Parameter im "upstream"-Block:

check 
interval=2000 # Überprüfungsintervall, 2s
rise=2 # Wie viele Überprüfungen müssen positiv verlaufen, damit der Server als online erkannt wird?
fall=5 # Wie viele Überprüfungen müssen negativ verlaufen, damit der Server als offline erkannt wird?
timeout=1000 # Zeitüberschreitung einer Überprüfung: Wann schlägt eine Überprüfung fehl? Hier: 1s.
type=imap; # Die Art der Überprüfung 

Eine Übersicht der "types" aus dem Git:

1. *tcp* is a simple tcp socket connect and peek one byte.
2. *ssl_hello* sends a client ssl hello packet and receives the server ssl hello packet.
3. *http* sends a http request packet, receives and parses the http response to diagnose if the upstream server is alive.
4. *smtp* sends a smtp request packet, receives and parses the smtp response to diagnose if the upstream server is alive. The response begins with '2' should be an OK response.
5. *mysql* connects to the mysql server, receives the greeting response to diagnose if the upstream server is alive.
6. *pop3* receives and parses the pop3 response to diagnose if the upstream server is alive. The response begins with '+' should be an OK response.
7. *imap* connects to the imap server, receives the greeting response to diagnose if the upstream server is alive.

Obige Konfiguration ist wirklich nur ein sehr einfaches Beispiel, weitere Optionen bitte unbedingt aus dem Git herauslesen!

4 Antworten auf “Nginx als TCP-Proxy (Beispiel Dovecot)

  1. wynni

    Hallo André,
    kannst du vielleicht auch dein Dovecot master/master Konfigurationsfile posten? Leider funktioniert bei mir der „fast sync“ nicht sofort (ein paar Minuten vergehen immer) sobald ein Mail eintrifft.

    Vielen Dank
    wynni

    1. André P. Autor

      Hi wynni,
      das passierte mir am Anfang auch. Grund kann sein, dass du das Plug-in nicht überall aktiviert hast. Vor allem nicht in IMAP.
      Beispiel, wie es richtig sein kann:

      mail_plugins = notify replication
      [...]
      service aggregator {
        fifo_listener replication-notify-fifo {
          user = vmail
        }
        unix_listener replication-notify {
          user = vmail
        }
      }
      [...]
      service replicator {
        unix_listener replicator-doveadm {
          mode = 0600
          user = vmail
        }
        process_min_avail = 1
      }
      [...]
      protocol imap {
        mail_plugins = notify replication
      }
      [...]
      plugin {
        mail_replica = tcp:192.168.99.21
      }
      [...]
      protocol lda {
        mail_plugins = notify replication
        postmaster_address = xyz@domain.tld
      }

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.