Nginx A/B-Testing, Analytics und GeoIP konfigurieren
Nginx ist ein leistungsfähiger Webserver und Proxy, der von zahlreichen stark frequentierten Websites eingesetzt wird, um Client-Verbindungen zu verwalten und Inhalte auszuliefern. Während die grundlegenden Funktionen vielen Anwendern bekannt sind, bietet Nginx darüber hinaus zusätzliche Möglichkeiten, die im alltäglichen Betrieb weniger offensichtlich sein können.
Dieser Leitfaden behandelt verschiedene Funktionen, die beim Testen neuer Inhalte und beim Erfassen statistischer Informationen über Website-Besucher hilfreich sein können. Sie lassen sich für die Entwicklung von Inhalten, für einfaches A/B-Testing und zur Untersuchung des Verhaltens unterschiedlicher Besuchergruppen einsetzen. Nginx kann diese Aufgaben direkt auf Ebene des Webservers übernehmen.
Einfaches A/B-Testing mit dem Nginx Split-Clients-Modul
Eine grundlegende Form des A/B-Testings lässt sich direkt mit Nginx umsetzen. Dafür kann das standardmäßige HTTP-Modul http_split_clients verwendet werden. Sofern dieses Modul beim Kompilieren von Nginx nicht ausdrücklich deaktiviert wurde, ist es normalerweise Bestandteil einer üblichen Nginx-Installation.
Das Modul stellt die Direktive split_clients bereit. Mit ihr können eingehende Verbindungen entsprechend festgelegter Bedingungen auf zwei oder mehr Gruppen verteilt werden. Die Direktive wird im http-Kontext und außerhalb einzelner Server-Blöcke definiert. Dabei wird ein Wert festgelegt, der für jede Verbindung ausgewertet wird, sowie eine zusätzliche Variable, in der das jeweilige Ergebnis gespeichert wird.
Die grundlegende Syntax der Direktive sieht folgendermaßen aus:
http {
. . .
split_clients "variable_to_evaluate" $new_variable {
percent_group% value_to_store;
percent_group% value_to_store;
}
}
Die Direktive wertet den Inhalt der zuerst angegebenen Variable aus und erzeugt daraus einen Hashwert. Derselbe Ausgangswert führt immer zum gleichen Hash, wodurch eine konsistente Zuordnung möglich ist. Die ausgewertete Variable muss einen String zurückgeben.
Jede Zeile innerhalb des split_clients-Blocks beschreibt eine eigene Gruppe beziehungsweise Alternative. Die einzelnen Gruppen werden anhand des prozentualen Anteils definiert, den sie innerhalb des verfügbaren Hashbereichs einnehmen sollen. Nginx erzeugt daraus entsprechende Wertebereiche. Jeder Bereich ist mit einem Wert verknüpft, der gesetzt wird, sobald der erzeugte Hash in diesen Bereich fällt. Die zweite Variable aus der split_clients-Definition erhält anschließend diesen Wert.
Anhand eines Beispiels lässt sich dieses Verhalten einfacher nachvollziehen. Die folgende Konfiguration zeigt eine mögliche Aufteilung:
split_clients "${remote_addr}" $designtest {
10% ".first";
10% ".second";
* "";
}
In diesem Beispiel wird für jede Verbindung der Wert von $remote_addr ausgewertet. Nginx setzt diese Variable auf die IP-Adresse des jeweiligen Clients. Anschließend wird die IP-Adresse mit dem Hashing-Algorithmus murmurhash2 verarbeitet. Danach prüft Nginx, in welchen der konfigurierten Hashbereiche der berechnete Wert fällt. Da die von Nginx verwendete murmurhash2-Implementierung mit 32 Bit arbeitet, können die Hashwerte zwischen 0 und 4294967295 liegen. Letzterer Wert entspricht dem maximal möglichen 32-Bit-Wert.
Die erste Gruppe ist für 10 % aller möglichen Hashwerte vorgesehen. Sie umfasst damit den Bereich von 0 bis 429496729 und entspricht ungefähr dem ersten Zehntel des gesamten Wertebereichs. Erzeugt eine Client-IP-Adresse einen Hash innerhalb dieses Bereichs, erhält die Variable $designtest den Wert ".first".
IP-Adressen, deren Hashwerte ungefähr zwischen 429496730 und 858993459 liegen, gehören zum zweiten Bereich, der ebenfalls 10 % des verfügbaren Hashraums umfasst. In diesem Fall wird $designtest auf ".second" gesetzt.
Für alle übrigen IP-Adress-Hashes, also ungefähr für Werte zwischen 858993460 und 4294967295, erhält die Variable $designtest einen leeren String.
A/B-Testing auf einem Server umsetzen
Mit diesem Verfahren kann ein definierter Prozentsatz der Verbindungen unterschiedlichen Variablenwerten zugeordnet werden. Anschließend lässt sich diese Variable verwenden, um unterschiedliche Inhalte auszuliefern.
Beispielsweise kann eine Server- und Location-Konfiguration anhand des in $designtest gespeicherten Werts entscheiden, welcher Inhalt zurückgegeben werden soll. Im folgenden Beispiel wird die Variable genutzt, um eine bestimmte Indexdatei auszuwählen:
http {
. . .
split_clients "${remote_addr}" $designtest {
10% ".first";
10% ".second";
* "";
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index${designtest}.html;
location / {
try_files $uri $uri/ =404;
}
}
}
Für IP-Adressen, deren Hash zur ersten Gruppe gehört, versucht Nginx die Datei index.first.html auszuliefern. Bei Adressen aus der zweiten Gruppe wird dagegen nach index.second.html gesucht. Gehört eine IP-Adresse zur dritten Gruppe, enthält $designtest einen leeren String, sodass die gewöhnliche Datei index.html verwendet wird.
Nun können drei Indexdateien im konfigurierten Document Root angelegt werden. Abhängig vom Hashwert ihrer IP-Adresse erhalten verschiedene Besucher unterschiedliche Dateien:
echo "<h1>First Site</h1>" | sudo tee /usr/share/nginx/html/index.first.html
echo "<h1>Second Site</h1>" | sudo tee /usr/share/nginx/html/index.second.html
echo "<h1>Default Site</h1>" | sudo tee /usr/share/nginx/html/index.html
Nachdem diese Änderungen an der Nginx-Konfiguration vorgenommen wurden, kann zunächst geprüft werden, ob Syntaxfehler vorhanden sind. Anschließend lässt sich der Webserver mit den folgenden Befehlen neu starten:
sudo nginx -t
sudo service nginx restart
Beim anschließenden Aufruf der Website im Browser sollte eine der drei Dateien angezeigt werden. In den meisten Fällen erscheint die Standardvariante, da sie für 80 % der Besucher vorgesehen ist. Um zu prüfen, welche Version Clients mit anderen IP-Adressen erhalten, kann die Website über einen Proxyserver oder einen geeigneten webbasierten Testdienst aufgerufen werden.
Der Dienst GeoPeeker kann beispielsweise eine Website aus verschiedenen Regionen der Welt aufrufen. Nach Eingabe des Domainnamens kann dabei möglicherweise eine oder mehrere der alternativen Varianten angezeigt werden:
Ein vergleichbarer Dienst ist LocaBrowser, bei dem Zugriffe aus verschiedenen Ländern ausgewählt werden können. Dabei sollte beachtet werden, dass die ausgelieferte Seite nicht direkt anhand der geografischen Position gewählt wird. Ausschlaggebend ist der Hashwert der Client-IP-Adresse. Besucher aus demselben Land müssen daher nicht zwangsläufig dieselbe Datei erhalten.
Obwohl dieses Beispiel bewusst einfach gehalten ist, lässt sich das Prinzip deutlich erweitern. Mit split_clients gesetzte Variablen können beispielsweise zum Setzen von Cookies oder Benutzerkennungen, zum Übermitteln von Headern an Backend-Proxys und für weitere Aufgaben eingesetzt werden. Für umfangreicheres A/B-Testing kann die Variable außerdem bestimmen, welcher Document Root für die jeweilige Gruppe verwendet wird.
Der Wert ${remote_addr} dient lediglich als praktisches Beispiel. Ein Hash kann grundsätzlich auf Basis jeder Nginx-Variable erzeugt werden, die sich als geeigneter String auswerten lässt. Variablen des Core-Moduls sind in der Referenz zu den Core-Modul-Variablen dokumentiert. Weitere Module stellen zusätzliche Variablen bereit, die in der jeweiligen Dokumentation beschrieben werden.
Tracking-Pixel mit der empty_gif-Direktive verwenden
Eine Möglichkeit für Administratoren, Informationen über Website-Aufrufe zu erfassen, besteht im Einsatz eines Tracking-Pixels. Damit lassen sich über Serverprotokolle beispielsweise Informationen darüber sammeln, welche IP-Adressen bestimmte Seiten anfordern und zu welchem Zeitpunkt diese Zugriffe erfolgen.
Ein klassisches Tracking-Pixel funktioniert über ein sehr kleines transparentes Bild, das in eine Webseite eingebunden wird. Sobald ein Besucher die Seite öffnet, fordert der Browser dieses Bild gemeinsam mit den übrigen Inhalten an. Die Bildanfragen können in einer separaten Protokolldatei gespeichert werden. Darin lassen sich unter anderem die IP-Adresse des Clients, die aufrufende Seite und weitere Details zur Anfrage erfassen. Diese Daten können anschließend als Grundlage für die Analyse des Besucherverhaltens auf einer Website dienen.
Das Modul empty_gif stellt eine solche Funktion direkt in Nginx bereit. Grundsätzlich könnte zwar nahezu jede angeforderte Ressource zum Tracking verwendet werden, die Direktive empty_gif liefert jedoch eine sehr kleine transparente .gif-Datei direkt aus dem Arbeitsspeicher aus. Da kein Zugriff auf das Dateisystem erforderlich ist, können entsprechende Anfragen besonders effizient verarbeitet werden. Die Direktive kann innerhalb eines Location-Kontexts eingesetzt werden.
In der Praxis wird sie häufig mit einer separaten Logging-Konfiguration kombiniert, damit sich die betreffenden Anfragen für spätere Auswertungen getrennt erfassen lassen. Eine entsprechende Konfiguration könnte beispielsweise folgendermaßen aussehen:
. . .
http {
log_format tracking '[$time_local] : $remote_addr : $remote_user : '
'$args : $http_referer : $http_user_agent';
server {
. . .
location = /logme.gif {
empty_gif;
access_log /var/log/nginx/tracking.log tracking;
expires epoch;
}
}
}
In dieser Konfiguration wird innerhalb des http-Kontexts ein log_format mit dem Namen tracking definiert. Es legt fest, welche Informationen aufgezeichnet werden sollen. Die verwendeten Felder können je nach benötigten Daten angepasst werden.
Anschließend wird ein Location-Block eingerichtet, der exakt auf die Anfrage nach einer bestimmten .gif-Datei reagiert. Der Modifier = sorgt für einen exakten Treffer. Dadurch können Anfragen an den angegebenen .gif-Pfad direkt verarbeitet werden, ohne nach weiteren möglichen Location-Übereinstimmungen suchen zu müssen. Als Dateiname kann grundsätzlich jede geeignete .gif-Bezeichnung verwendet werden.
Innerhalb dieser Location liefert die Direktive empty_gif das transparente 1×1-Pixel-.gif direkt aus dem Speicher aus. Anfragen für diese Ressource werden mit dem zuvor definierten Format in einer separaten Protokolldatei gespeichert. Zusätzlich wird der Ablaufzeitpunkt auf epoch gesetzt. Dadurch wird dem Browser signalisiert, dass die Grafik nicht zwischengespeichert werden soll, sodass jeder erneute Seitenaufruf protokolliert werden kann.
Auf den gewünschten Seiten kann anschließend ein Bildelement eingefügt werden, das die ausgewählte Ressource anfordert. Eine sehr einfache Seite könnte beispielsweise so aussehen:
<html>
<head>
<title>Your Site</title>
</head>
<body>
<h1>Normal Content</h1>
<img src="/logme.gif">
</body>
</html>
Öffnet ein Besucher diese Seite, fordert der Browser die Ressource /logme.gif an. Nginx schreibt daraufhin Informationen zu dieser Anfrage in die Datei tracking.log. Durch Anpassungen am log_format und die anschließende Verarbeitung der Protokolldaten mit Textwerkzeugen lässt sich daraus ein umfangreicheres Tracking-System entwickeln.
Inhalte anhand des geografischen Standorts ausliefern
Das Modul split_clients kann Besucher für A/B-Tests automatisch auf unterschiedliche Gruppen verteilen. Darüber hinaus kann Nginx Clients auch anhand der ungefähren geografischen Position gruppieren, die mit ihrer IP-Adresse verbunden ist.
IP-Adressen können über Datenbanken ungefähren Standorten zugeordnet werden, die von spezialisierten Datenanbietern zusammengestellt werden. Ein großer Teil dieser Informationen basiert auf Registern, die für die Zuweisung von IP-Adressbereichen in verschiedenen geografischen Regionen zuständig sind. Aus einer IP-Adresse abgeleitete Standortinformationen stellen jedoch immer nur eine Annäherung dar und sollten deshalb als bestmögliche Schätzung und nicht als präzise Methode zur Ermittlung des Ursprungs von Website-Traffic betrachtet werden.
Geolokalisierungsdatenbanken beschaffen
Nginx kann geografische Daten über die Direktiven des Moduls ngx_http_geoip_module verwenden, um Clients zu kategorisieren. Die dafür notwendigen Datenbanken, in denen IP-Adressen geografischen Standorten zugeordnet werden, sind jedoch nicht Bestandteil des Moduls und müssen separat bezogen werden.
Auf Debian- oder Ubuntu-basierten Systemen können Zuordnungen auf Länderebene mit den folgenden Befehlen installiert werden:
sudo apt-get update
sudo apt-get install geoip-database
Eine allgemeinere Möglichkeit besteht darin, die benötigte Datenbank auf Länderebene mit wget herunterzuladen. Dazu kann zunächst ein Verzeichnis für die Datenbank erstellt und anschließend die Datei heruntergeladen werden:
sudo mkdir -p /usr/local/share/GeoIP
cd /usr/local/share/GeoIP
sudo wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
Das heruntergeladene Archiv muss anschließend entpackt werden:
sudo gunzip GeoIP.dat.gz
Eine detailliertere Datenbank auf Stadtebene kann ebenfalls mit wget heruntergeladen werden:
sudo mkdir -p /usr/local/share/GeoIP
cd /usr/local/share/GeoIP
sudo wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
Auch dieses Archiv muss anschließend entpackt werden:
sudo gunzip GeoLiteCity.dat.gz
Nginx für die Verwendung von Geolokalisierungsdaten konfigurieren
Nachdem die Standortdatenbanken vorhanden sind, kann Nginx so konfiguriert werden, dass die darin enthaltenen Informationen genutzt werden.
Über die entsprechenden Direktiven lassen sich die Speicherorte der Datenbanken festlegen. Diese Angaben müssen innerhalb des http-Kontexts der Nginx-Konfiguration stehen:
. . .
http {
# Wenn die Datenbank auf Länderebene mit `apt-get` installiert wurde, auskommentieren und verwenden:
#geoip_country /usr/share/GeoIP/GeoIP.dat;
# Wenn die Datenbank auf Länderebene mit `wget` heruntergeladen wurde, verwenden:
geoip_country /usr/local/share/GeoIP/GeoIP.dat;
geoip_city /usr/local/share/GeoIP/GeoLiteCity.dat;
. . .
}
Sobald die Speicherorte der Datenbanken konfiguriert sind, können die von Nginx dafür bereitgestellten Variablen verwendet werden. Die Datenbank auf Länderebene stellt folgende Variablen zur Verfügung:
$geoip_country_code: Ein aus zwei Buchstaben bestehender Ländercode. Beispiele dafür sindUSfür die Vereinigten Staaten oderRUfür Russland. Die verfügbaren Codes sind unter ISO 3166-1 alpha-2 aufgeführt.$geoip_country_code3: Entspricht grundsätzlich dem vorherigen Wert, verwendet jedoch den dreistelligen Ländercode, beispielsweiseUSAoderRUS.$geoip_country_name: Der dem jeweiligen Ländercode zugeordnete Ländername. FürNZlautet der Wert beispielsweiseNew Zealand.
Die Datenbank auf Stadtebene stellt eine größere Anzahl von Variablen zur Verfügung. Mit dieser Datenbank können folgende Werte abgerufen werden:
$geoip_area_code: Ein älteres Feld für Telefonvorwahlen in den Vereinigten Staaten. Dieser Wert sollte nicht als zuverlässige Quelle für genaue Informationen verwendet werden.$geoip_city_continent_code: Ein zweistelliger Code für den Kontinent.$geoip_city_country_code: Derselbe zweistellige Ländercode, der auch von der Datenbank auf Länderebene bereitgestellt wird.$geoip_city_country_code3: Derselbe dreistellige Ländercode, der auch über die Datenbank auf Länderebene verfügbar ist.$geoip_city_country_name: Derselbe Ländername, der auch von der Datenbank auf Länderebene geliefert wird.$geoip_dma_code: Der DMA-Regions- beziehungsweise Metropolcode für Standorte in den Vereinigten Staaten. Zugehörige Werte sind über die Google AdWords API – Cities & DMA Regions verfügbar.$geoip_latitude: Ein geschätzter Breitengrad für den Standort, der der ursprünglichen IP-Adresse zugeordnet ist.$geoip_longitude: Ein geschätzter Längengrad für den Standort, der mit der ursprünglichen IP-Adresse verbunden ist.$geoip_region: Ein zweistelliger Regionscode für eine geografische oder politische Region wie einen Staat, eine Provinz oder ein Territorium.$geoip_region_name: Der vollständige Name der zuvor angegebenen Region.$geoip_city: Der Stadtname, der mit der ursprünglichen IP-Adresse verbunden ist.$geoip_postal_code: Die Postleitzahl des ungefähren Gebiets, dem die IP-Adresse zugeordnet wird.
Auch hier ist wichtig zu berücksichtigen, dass die über diese Variablen bereitgestellten Informationen nur ungefähre Werte darstellen und keine garantierte geografische Genauigkeit bieten. Dennoch ergeben sich daraus verschiedene Möglichkeiten, Besuchern aus unterschiedlichen Regionen jeweils passende Inhalte bereitzustellen.
Eine häufig verwendete Methode besteht darin, eine dieser Variablen mit der Direktive map zu kombinieren und abhängig vom jeweiligen Wert eine weitere Variable zu setzen. So lässt sich eine eigene Variable erstellen, deren Inhalt von den Standortinformationen abhängt, die für den Client in der Datenbank vorhanden sind.
Auch die Direktive map muss im http-Kontext verwendet werden. Beispielsweise kann eine Website so konfiguriert werden, dass Besucher aus Australien oder Singapur andere Inhalte erhalten. Eine geeignete Grundlage für die Prüfung sind die zwei- oder dreistelligen Ländercodes aus der Länder- oder Städtedatenbank.
In diesem Beispiel wird $geoip_country_code verwendet. Der Wert dieser Variable bestimmt, welcher Inhalt in der neu erstellten Variable $site_version gespeichert wird. Diese Variable wird anschließend dazu genutzt, den Document Root für die auszuliefernden Inhalte festzulegen:
http {
# Wenn die Datenbank auf Länderebene mit `apt-get` installiert wurde, auskommentieren und verwenden:
#geoip_country /usr/share/GeoIP/GeoIP.dat;
# Wenn die Datenbank auf Länderebene mit `wget` heruntergeladen wurde, verwenden:
geoip_country /usr/local/share/GeoIP/GeoIP.dat;
geoip_city /usr/local/share/GeoIP/GeoLiteCity.dat;
map $geoip_country_code $site_version {
default "";
AU "/australia";
SG "/singapore";
}
. . .
}
Diese Konfiguration weist der eigens erstellten Variable $site_version einen Wert zu. Zeigt der Ländercode an, dass ein Besucher aus Australien (AU) stammt, erhält $site_version den Wert "/australia". Bei einem Besucher aus Singapur (SG) wird die Variable auf "/singapore" gesetzt. Für jeden anderen Ländercode bleibt $site_version ein leerer String.
Der daraus resultierende Wert kann verwendet werden, um den Document Root für Besucher aus bestimmten Ländern zu verändern. Dies ist eine mögliche Methode, Inhalte mithilfe geografischer Informationen zur IP-Adresse eines Clients unterschiedlich auszuliefern.
Um den Document Root entsprechend anzupassen, kann die Direktive root innerhalb des Server-Blocks beispielsweise folgendermaßen konfiguriert werden:
http {
# Wenn die Datenbank auf Länderebene mit `apt-get` installiert wurde, auskommentieren und verwenden:
#geoip_country /usr/share/GeoIP/GeoIP.dat;
# Wenn die Datenbank auf Länderebene mit `wget` heruntergeladen wurde, verwenden:
geoip_country /usr/local/share/GeoIP/GeoIP.dat;
geoip_city /usr/local/share/GeoIP/GeoLiteCity.dat;
map $geoip_country_code $site_version {
default "";
AU "/australia";
SG "/singapore";
}
. . .
server {
. . .
root /usr/share/nginx/html${site_version};
. . .
}
}
Für einen Besucher aus Australien ändert sich der für Anfragen verwendete Document Root damit zu /usr/share/nginx/html/australia. Für Besucher aus Singapur werden die Inhalte aus /usr/share/nginx/html/singapore ausgeliefert. Bei allen anderen Besuchern enthält $site_version einen leeren String, sodass die Inhalte weiterhin aus /usr/share/nginx/html stammen.
Zum Testen dieser Konfiguration kann der standardmäßige Document Root /usr/share/nginx/html verwendet werden. Zunächst wird in dieses Verzeichnis gewechselt:
cd /usr/share/nginx/html
Anschließend können die zuvor verwendeten Verzeichnisse erstellt und jeweils mit einer einfachen index.html-Datei versehen werden. Dadurch lässt sich überprüfen, ob der erkannte Standort tatsächlich beeinflusst, welcher Inhalt ausgeliefert wird:
sudo mkdir australia && echo "<h1>australia</h1>" | sudo tee australia/index.html
sudo mkdir singapore && echo "<h1>singapore</h1>" | sudo tee singapore/index.html
Nachdem alle Einstellungen vorgenommen wurden, kann die Nginx-Konfiguration geprüft und der Dienst anschließend neu gestartet werden:
sudo nginx -t
sudo service nginx restart
Mit GeoPeeker kann anschließend erneut überprüft werden, ob aus verschiedenen Standorten unterschiedliche Inhalte ausgeliefert werden. Australien und Singapur gehören zu den Standorten, die über den Dienst getestet werden können.
Für Besucher, die aus den Vereinigten Staaten oder Irland zugreifen, sollte die Standardseite angezeigt werden. Bei Zugriffen aus Australien oder Singapur sollte dagegen der zuvor erstellte Testinhalt erscheinen:
Damit lässt sich bestätigen, dass Nginx die auszuliefernden Inhalte anhand der Client-IP-Adresse und der Informationen in der geografischen Datenbank auswählt.
Fazit
Durch den Einsatz dieser Nginx-Funktionen und Verfahren können Analysedaten gesammelt werden, die fundiertere Entscheidungen über die Inhalte einer Website ermöglichen. Zwar existieren zahlreiche externe Werkzeuge zur Erfassung vergleichbarer Daten, doch die direkt in Nginx verfügbaren Möglichkeiten können eine sinnvolle Alternative darstellen, bevor zusätzlicher Aufwand in andere Lösungen investiert wird.


