Varnish + Nginx für WordPress

Es wird mal wieder Zeit etwas über NginX zu schreiben. In meinem heutigen Artikel widme ich mich dem Thema „Caching“. Durch ein Kundenprojekt darauf angestoßen habe ich mich mit verschiedenen Möglichkeiten auseinander gesetzt um einer PHP Anwendung auf die Sprünge zu helfen. Im Falle des Kundensystems war NginX als Reverse Proxy vor einem Apachen eine sehr gute Wahl. Der heutige Ansattz geht allerdings in eine andere Richtung zumal NginX als Webserver für WordPress bereits konfiguriert ist und Varnish das Caching übernehmen wird.

Dieser Artikel wird nicht auf Feinheiten bei der Konfiguration eingehen. Damit muss ich jeder selbst auseinander setzen. Vielmehr soll diese Anleitung einen Weg vorzeigen, der sequentiell abgearbeitet werden kann und der am Ende ein tolles Ergebnis liefert.

 Ein alter Bekannter

Nginx

Die ist nicht der erste Artikel zum Thema, wahrscheinlich auch nicht der Letzte. Das heutige Szenario sieht NginX aber nicht in der Rolle des schnellen Webservers. Denn obwohl sich der „Kleine“ im Vergleich zum „Großen“ (Apachen) bei der Auslieferung von WordPress als durchaus schneller erweist, ist der Bench dennoch nicht weltbewegend.

WordPress + NginX

Die Quelle für diese quick & dirty Konfiguration ist die Seite von Nginx selbst [1].

# Upstream to abstract backend connection(s) for php
# Use either or: socket or ip as defined in the php-fpm configuration
upstream php_backend {
    server unix:/tmp/php-cgi.socket;
    server 127.0.0.1:9000;
}

server {
    ## Your website name goes here.
    server_name domain.tld;
    ## Your only path reference.
    root /var/www/wordpress;
    ## This should be in your http block and if it is, it's not needed here.
    index index.php;

    location = /favicon.ico {
          log_not_found off;
          access_log off;
    }

    location = /robots.txt {
          allow all;
          log_not_found off;
          access_log off;
    }

    location / {
          # This is cool because no php is touched for static content
          try_files $uri $uri/ /index.php;
    }

    location ~ \.php$ {
          #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
          include fastcgi.conf;
          fastcgi_intercept_errors on;
          fastcgi_pass php_backend;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires max;
          log_not_found off;
    }
}

Damit ist NginX in der Lage WordPress zu servieren. Wie der Konfiguration entnommen werden kann, übergibt Nginx php-Requests an den Upstream ‚php_backend‘. Was im Umkehrschluss wiederum bedeutet, dass PHP-FPM bereits installiert und konfiguriert ist. Mit den aktuellen Distributionen (wie z.B. Ubuntu 12.04 LTS) kann dies mittels apt-get install php5-fpm geschehen. Ältere Distributionen (inkl. 10.04 LTS) liefern noch kein Paket. Hier helfen backports, PPA, oder einfach selbst Hand anlegen.

In einem älteren Artikel finden sich hierzu weitere Informationen, die hilfreich sein können.

PHP-FPM Pool

Angelehnt an genannten älteren Artikel hier eine mögliche PHP Konfigurationsdatei. Diese muss an einigen Stellen an die eigenen Bedürfnisse angepasst werden. Zum Beispiel wird sich der Pfad /var/lib/php5/sockets auf keinem System finden lassen (wurde von Hand angelegt). Ebenso sind die Pfade zu den Logdateien zu kontrollieren.

[wp]

;listen = 127.0.0.1:9000
listen = /var/lib/php5/sockets/wp.socket
;listen.backlog = -1

listen.allowed_clients = 127.0.0.1
listen.owner = www-data
listen.group = www-data
listen.mode = 0666

user = www-data
group = www-data

pm = dynamic
pm.max_children = 50
pm.start_servers = 15
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
;pm.status_path = /status

;ping.path = /ping
;ping.response = pong

;request_terminate_timeout = 0
;request_slowlog_timeout = 0

slowlog = /usr/local/php/var/log/wp.log.slow

;rlimit_files = 1024
;rlimit_core = 0

;chroot =
chdir = /
;catch_workers_output = yes

;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f y@ou.tld
php_flag[display_errors] = off
php_admin_value[error_log] = /usr/local/php/var/log/fpm-php.wp.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 128M
php_admin_value[apc.shm_size] = 64M

Funktioniert doch. Benchmark

Funktionieren alleine genügt uns nicht – das kann ja jeder! Um die Performance von WordPress zu steigern verbauen wir einen HTTP-Accelerator: Varnish.

An dieser Stelle könnte man die Anleitung mit Benchmarks auflockern, die zeigen wo die Reise hingeht. Gängiges Benchmarking Werkzeug ist hier ab – Apache Benchmark [2]. Mit nachfolgendem Befehl lässt sich ein Vorabbild der Situation machen:

ab -k -c 10 -n 1000 http://www.domain.tld/

Die einzelnen Parameter erklärt dabei der Aufruf von ab an der Kommandozeile. Interessant ist dabei der Vergleich vorher – nachher bzgl. der Werte Requests per second

Es sollte dabei darauf geachtet werden, dass der Aufruf der gleiche bleibt. Zudem sei an dieser Stelle darauf hingewiesen, dass man sich mit dem Thema Benchmarking immer sehr kritisch auseinander setzen muss. Warum beschreibt Kristian Lyngstøl[3] in seinem Blog.

Damit dieser Artikel jetzt Sinn macht und um das beeindruckende Ergebnis zu veranschaulichen, zunächst die VORHER Werte. Dabei liefert NginX die Seite aus mit obiger Konfiguration.

Requests per second:    32.92 [#/sec] (mean)

Wobei es sich um einen Server mit 1 AMD Phenom(tm) II X6 1055T Processor handelt, 8GB RAM und Ubuntu 10.04 LTS als OS.

Ein anderes Bench-Werkzeug ist httperf. Der Aufruf gestaltet sich etwas schwerer als der von ab.

httperf --rate 2000 --num-conns=10000 --num-calls 20 \
                         --burst-length 20 --server localhost --port 80 --uri /

httperf liefert auch hier anschauliche Daten, ähnlich ab. Diese sind aber nicht weiter wichtig. Warum?

Benchmark Fazit

Wir brauchen für diesen Artikel einen Benchmark – ansonsten lässt sich das Ergebnis unserer Arbeit nicht messen. Es sei jedoch auf darauf hingewiesen, dass wir unsere Webseite … lokal benchen. Zudem haben angesprochene Tools Eigenschaften, die sie nicht perfekt machen. So läuft httperf beispielsweise single threaded. Es kann zwar mehrere gleichzeitige Verbindungen aufbauen, aber nur in einem einzigen Thread. Zudem stösst httperf an interne Limits: . Dies lässt sich umgehen, wenn man das Tool selbst kompiliert.

Es sind schlicht synthetische Ergebnisse, die man kritisch interpretieren muss.

Was und wo ist nun Varnish?

Varnish is a web application accelerator. You install it in front of your web application and it will speed it up significantly.

Ausgehend von einer neuen Distribution kann einfach der Paketmanager angeschmissen werden, z.b. apt oder yum. Wer es mit etwas älterem zu tun hat, z.B. Ubuntu 10.04 LTS, der installiert „varnish“ wie folgt:

curl http://repo.varnish-cache.org/debian/GPG-key.txt | apt-key add -
echo "deb http://repo.varnish-cache.org/ubuntu/ lucid varnish-3.0" >> /etc/apt/sources.list
apt-get update
apt-get install varnish

Für andere Distributionen gibt es hier entsprechende Anleitungen.

Damit ist Varnish zunächst unangepasst installiert und bereit lauffähig. Um ihn in das obige Konstrukt einzubauen, sind einige Anpassungen notwendig.

Konfiguration Varnish

In einem ersten Schritt wird /etc/default/varnish angepasst.

#...
START=yes
#...
DAEMON_OPTS="-a :80 \
             -T localhost:6082 \
             -f /etc/varnish/newfile.vcl \
             -S /etc/varnish/secret \
             -s malloc,256m"
# ...

START=yes lässt Varnish nach einem reboot wieder starten. Die DAEMON_OPTS, die bereits vorhanden sind werden in -a angepasst. :80 definiert den Port an dem Varnish auf Anfragen hören wird. In vorderster Front ist dies der HTTP-Port.

Die zweite Anpassung ist die Zuweisung einer neuen .vcl Datei. In dieser newfile.vcl wird der WordPress spezifische Code geschrieben, der Varnish das Handling von WordPress ermöglicht.

Ein großes Augenmerk sei dabei auf den Anfang gerichtet, denn der Block backend default bestimmt von wo Varnish Inhalte bezieht.

backend default {
    .host = "127.0.0.1";
    .port = "8888";
}

sub vcl_recv {
  if (req.http.Accept-Encoding) {
    #revisit this list
    if (req.url ~ "\.(gif|jpg|jpeg|swf|flv|mp3|mp4|pdf|ico|png|gz|tgz|bz2)(\?.*|)$") {
      remove req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate") {
      set req.http.Accept-Encoding = "deflate";
    } else {
      remove req.http.Accept-Encoding;
    }
  }
  if (req.url ~ "\.(gif|jpg|jpeg|swf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
    unset req.http.cookie;
    set req.url = regsub(req.url, "\?.*$", "");
  }
  if (req.url ~ "\?(utm_(campaign|medium|source|term)|adParams|client|cx|eid|fbid|feed|ref(id|src)?|v(er|iew))=") {    set req.url = regsub(req.url, "\?.*$", "");
  }
  if (req.http.cookie) {
    if (req.http.cookie ~ "(wordpress_|wp-settings-)") {
      return(pass);
    } else {
      unset req.http.cookie;
    }
  }
}

sub vcl_fetch {
  if (req.url ~ "wp-(login|admin)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") {
    return (hit_for_pass);
  }
  if ( (!(req.url ~ "(wp-(login|admin)|login)")) || (req.request == "GET") ) {
    unset beresp.http.set-cookie;
  }
  if (req.url ~ "\.(gif|jpg|jpeg|swf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
    set beresp.ttl = 365d;
  }
}

sub vcl_deliver {
   # multi-server webfarm? set a variable here so you can check
   # the headers to see which frontend served the request
   #   set resp.http.X-Server = "server-01";   
   if (obj.hits > 0) {
     set resp.http.X-Cache = "HIT";
   } else {
     set resp.http.X-Cache = "MISS";
   }
}

Die Varnish Konfiguration wird in C-Code übersetzt und zurück in Varnish gelinkt. Fehler in der Konfiguration werden dabei direkt beim Start des Caches ausgegeben.

Konfiguration Nginx

Mit Varnish in der ersten Reihe muss Nginx zwangsläufig in die Zweite. Der Block

backend default {
    .host = "127.0.0.1";
    .port = "8888";
}

der Varnish-Konfiguration hat dies bereits angedeutet. Die Virtuellen Hosts in Nginx werden so angepasst, dass der Port 8888 in der listen Direktive steht.

Ende

Das ist tatsächlich schon alles. Die Server sind zu starten, bzw. die Konfigurationen neu einzulesen.

service nginx restart && service varnish start

Bench…immer wieder bench

Der Vollständigkeit halber testen wir das System noch ein mal mit

ab -k -c 10 -n 1000 http://www.domain.tld/

und schauen uns das Ergebnis an:

Requests per second:    9664.95 [#/sec] (mean)

Beeindruckende Steigerung, wenn auch unter Berücksichtigung obiger Hinweise bzgl. Benchmarking.

[1] http://wiki.nginx.org/WordPress
[2] Die Installation von ab erfolgt mit dem Package apache2-utils. Der Apache Webserver muss dazu nicht installiert werden.
[3] http://kly.no/me.html


Beitrag veröffentlicht

in

, , , , ,

von

Kommentare

4 Antworten zu „Varnish + Nginx für WordPress“

  1. Hast du schonmal cachify für wordpress probiert das reicht meist, den varnish Brauch ich seitdem nicht mehr. Das lässt auch den Arbeitsaufwand nicht so groß werden.

  2. Nein, habe ich nicht. Ich muss auch gestehen, ich habe nicht dezidiert nach einer Lösung für WordPress gesucht. Meine Seite ist zu klein, als das dies überhaupt notwendig wäre. Im Zuge von Kundenprojekten war ich aber auf der Suche nach einer universellen Lösung, also unabhängig von der App.

    Aber danke für den Tipp. Auf alle Fälle eine Erwähnung wert.

  3. Wir setzten Varnish ein. Und ich kann sagen puhh die Ladezeit ist damit extrem geschrumpft. Aber nicht nur die Ladezeit. Unser Webserver ist durch Varnish ist Spitzenzeiten nicht mehr am Ende seiner Kräfte.

    Wir haben diesen Artikel in unserem Beitrag „10 Tipps um WordPress zu beschleunigen“ verlinkt.

  4. […] Das ultimative Mittel gegen langsame Blogs und vieles mehr ist Varnish. Varnish ist so komplex, dass die Erklärung den Rahmen dieses Beitrags sprengen würde. Daher verlinken wir Euch hier einen Wikipedia Eintrag zu Varnish und hier eine Installations-Anleitung.  […]

Schreibe einen Kommentar