class: center, middle # Varnish Eine Einführung in den "high performance web accelerator" --- # Grundlagen-Schnelldurchlauf: HTTP Das **H**yper**T**ext **T**ransfer **P**rotocol vermittelt Anfragen und Antworten zwischen Clients (bzw. User-Agents) und (Proxy-)Servern. Es wurde zum Übertragen von Hypertextdaten, die das World Wide Web aufspannen, erdacht. Heute (oft und leider) weitgehend als Ersatz für Layer 4 bzw. TCP in Gebrauch. Es existiert in den Versionen 0.9 (seit 1991; seit 2014 historisch), 1.0, 1.1 (RFC 2616; seit 1999) und 2.0. -- Datenströme zwischen HTTP-Konversationspartnern folgen dem **Request-Response**-Muster, und sind fundamental in **Header** und **Body** unterteilt. Header beinhalten hauptsächlich **Statusinformationen** und signalisieren Details zu Fähigkeiten von Clients und Servern sowie von Fehler- und Erfolgszuständen. Sie bestimmen u. a. das **Caching-Verhalten** der Teilnehmer. Ein Request dreht sich um eine Methode (bzw. ein Verb), und einen URI. Zentrales Element der Response ist ein dreistelliger, dezimaler Statuscode (200, 204, 301, 404, 500, 503, ...). -- ```no-highlight > GET / HTTP/1.1 > Host: example.com > < HTTP/1.1 200 OK < Content-Type: text/html < Content-Length: 1337 < < < < [...] ``` --- # Grundlagen-Schnelldurchlauf: Caching **Caching** bezeichnet das zeitlich begrenzte **Zwischenspeichern** von Daten, welche ursprünglich von einem aufwendiger anzusprechenden Ursprungs-Speicherort gelesen wurden. In einem modernen Computersystem gibt es viele **Caching-Layer**, z. B. in der CPU (L1-Cache), im Hauptspeicher (Buffer+Page-Cache des Kernels), in der SSD (DRAM), im Dateisystem (Cache für über das Netzwerk bezogene Inhalte) ... Sinnvolles, "intelligentes" Caching ist oft **der Schlüssel zu hoher Performance**. Aber Caching erhöht die Gesamtkomplexität eines Systems, denn es bedingt in Folge Konzepte wie Cache-Invalidation und Caching- bzw. Retention-Policies. -- ## Spezialfall HTTP: HTTP kontrolliert das Caching von Inhalten (Response Bodies bzw. Resource Representations) über Header: _Cache-Control_, _ETag_, _Expires_, _Vary_, _If-Modified-Since_, _If-None-Match_, ... und HTTP-Status-Codes (304 _Not Modified_). --- name: was ist varnish # Was ist Varnish? - Varnish ist ein vielseitiger **HTTP/1.1 Reverse Proxy Server** - als Backends dienen ihm **andere HTTP-Server**; Varnish ist i. A. **kein "Origin Server"** - Varnish wird als **Load Balancer** und **Cache** (als Bestandteil einer Caching-Strategie) eingesetzt - Varnish taugt auch als **Application Layer Firewall** -- * * * > "Varnish Cache is a state of the art web accelerator written with performance > and flexibility in mind." > > — _Debian package description_ * * * > "Varnish is an HTTP accelerator designed for content-heavy dynamic web sites." > > — _Fastly_ * * * --- # Geschichtliches, Rechtliches, Verschiedenes - Varnish wurde **2006 in Version 1.0** veröffentlicht, derzeit aktuell ist **Version 4.1.2**. - Varnish ist in **C** geschrieben und erfordert zwingend einen funktionstüchtigen C-Compiler (in der Praxis GCC) auf dem Installationsziel - Der ursprüngliche und bis heute federführende Autor ist [Poul-Henning Kamp](http://phk.freebsd.dk/) - vor allem in BSD-Kreisen kein Unbekannter - kommerziellen Support gibt es z. B. durch die [Varnish Software AB](https://www.varnish-software.com/) oder [Redpill Linpro](http://www.redpill-linpro.com/) - Varnish steht unter einer **2-Clause BSD**-Style Lizenz und ist Freie Software --- # Was macht Varnish besonders? - Varnish **cachet** HTTP Responses im **Hauptspeicher** Es gibt auch noch andere Storage Backends als _malloc(3)_, auf diese werde ich aber nicht näher eingehen. - Varnish **loggt** Zugriffe in einem zirkulären Puffer **in Shared Memory** Möchte man Logs dauerhaft aufbewahren, muss man diese mit einem geeigneten Client aus dem Shared Memory lesen, und danach selbst persistieren. - zur Konfiguration dient die **Varnish Configuration Language** (VCL) **VCL** ist eine Domain-Specific Language (DSL), die das **Request Handling** und die **Caching Policy** steuert. Sie ist sehr flexibel und wird vor dem Laden in C, und in Folge **in Maschinencode übersetzt**. Varnish lädt das resultierende Shared Object ohne Serviceunterbrechung in die Worker-Prozesse, worauf die VCL-Änderungen sofort aktiv werden. - Varnish ist über **VMODs erweiterbar**, die nach dem Laden neue VCL-Funktionen bereitstellen VMODs implementieren z. B. Schnittstellen zu Redis oder memcached, HTTP Basic Auth, Surrogate Cache Keys, GeoIP-Lookups, ... --- # Wie arbeitet Varnish? Varnish verarbeitet HTTP-Anfragen mithilfe mehrerer **State Machines**. Beim Erreichen eines neuen Zustandes wird jeweils eine C-Funktion aufgerufen, die durch Varnish "**Core Code**" vorgegeben ist. Am Ende dieser wird (durch den Administrator definierte) **VCL** abgearbeitet, welche die weiteren Zustandsübergänge bestimmt, sowie durch Core Code und VMODs bereitgestellte Objekte mutieren kann. ![:scale 25%](img/cache_req_fsm.svg) ![:scale 45%](img/cache_fetch.svg) --- # VCL - Varnish Configuration Language -- Ein fiktives Beispiel ↓ ``` vcl 4.0; import std; import directors; backend web_app_01 {.host = "192.168.2.101"; .probe = { .url = "/health/get"; .interval = 1s; .timeout = 1s; }} backend web_app_02 {.host = "192.168.2.102"; .probe = { .url = "/health/get"; .interval = 1s; .timeout = 1s; }} backend web_static_01 {.host = "192.168.2.1"; .probe = { .url = "/"; .interval = 5s; .timeout = 1s; }} sub vcl_init { new director_app = directors.hash(); new director_static = directors.random(); director_app.add_backend(web_app_01, 1.0); director_app.add_backend(web_app_02, 1.0); director_static.add_backend(web_static_01, 1.0); } sub vcl_recv { if(req.url ~ "^/(css|img|assets)/") { unset req.http.Cookie; set req.backend_hint = director_static.backend(); } else { set req.backend_hint = director_app.backend(); } } ``` --- # VCL - eine Kurzübersicht (1/2) - syntaktisch C und Perl ähnlich Statements terminieren mit "**;**", Blocks werden von "**{**" und "**}**" umschlossen. Whitespace ist nicht signifikant. - weitgehend vorgegebener Kontrollfluss Varnish arbeitet Requests in einer Reihe von VCL-Prozeduren ab, die man durch benutzerdefinierte VCL modifzieren und erweitern kann. Intern bildet Varnish einen Satz von State Machines, deren Verhalten und Zustandsübergange durch VCL-Statements (v. a. **return**) kontrolliert werden. - keine benutzerdefinierten Variablen Zum Transport von Daten zwischen VCL-Subroutinen eignen sich stattdessen z. B. HTTP-Header, die man in einer Routine in einen Request implantiert, und in einer später aktiven Routine wieder auswertet. --- # VCL - eine Kurzübersicht (2/2) - benutzerdefinierte Subroutinen Dienen zum Zusammenfassen von Code zur Erhöhung der Lesbarkeit und für DRY - sie haben einen Namen, aber keinen Argumentvektor. Arbeiten dementsprechend ähnlich der primitivsten C-Präprozessor-Substitution von Konstanten. - Strings und RegEx Werden durch doppelte Anführungszeichen (**""**) ausgezeichnet; es gibt auch eine mehrzeilige Syntax für **Long Strings**. Jede Bytefolge, außer 0x00, ist erlaubt. Strings sind die Basis für reguläre Ausdrücke (eigentlich: **PCRE**) in Varnish. - kleine Anzahl Operatoren Zuweisung (=), Vergleich (==), logisches Nicht (!), Und (&&), Oder (||), PCRE- und ACL-Match (~), Concatenation (+). - praktische Datentypen Strings, Wahrheitswerte (Booleans), Zeitpunkte, Zeitspannen, Ganzzahlen (Floats sind geplant). --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (1/6) ## Request- und Response-Objekte - **req**: Request-Objekt zwischen Client ⇔ Varnish - **resp**: Response-Objekt zwischen Varnish ⇔ Client - **bereq**: Request-Objekt zwischen Varnish ⇔ Backend - **beresp**: Response-Objekt zwischen Backend ⇔ Varnish -- Beispiel ↓ ``` [...] unset req.http.Cookie; [...] ``` Beispiel ↓ ``` [...] set beresp.http.Expires = "" + (now + 120s); [...] ``` --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (2/6) ## vcl_recv Wird nach dem Empfangen und Parsen eines Requests aufgerufen. Hier wird entschieden, ob bzw. wie der Request verarbeitet und aus dem Cache beantwortet oder an einen Backend-Server weitergeleitet wird. -- Beispiel ↓ ``` sub vcl_recv { set req.http.X-UA-Generation = "default"; if(req.http.User-Agent ~ " MSIE 6\.") { # "X-UA-Generation: $wert" ergeht als HTTP-Request-Header an ein (in vcl_init definiertes) Backend set req.http.X-UA-Generation = "stone-age"; set req.backend_hint = director_legacybrowsers.backend; } else { set req.backend_hint = director_sanebrowsers.backend; } if(req.url ~ "\.(js|jpg|png|gif|mp4|webm)$" ) { unset req.http.cookie; } if(req.http.host == 'app.example.com') { set req.url = regsuball("\.php$", ".pl"); } } ``` --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (3/6) ## vcl_hash Bestimmt, welche Request-Parameter (additiv) Einfluss auf den **Cache-Key** haben. Standard: **HTTP-Host** und **URL** des Requests. NB: Die "builtin" VCL **cachet keine Responses**, deren assoziierte Requests mit einem _Cookie_-Header ankommen. Ebenfalls respektiert Varnish alle fuer das Caching relevanten HTTP-Response-Header des Backends ("_Cache-Control: private_" et al.) -- Beispiel ↓ ``` sub vcl_hash { [...] # Die Response hängt vom HTTP-Referer hash_data(req.http.Referer); [...] # Die Response ist weiters abhängig von der Do-Not-Track-Einstellung des User-Agents hash_data(req.http.DNT); [...] } ``` --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (4/6) ## vcl_backend_response Wird nach dem vollständigen Erhalten der HTTP-Response-Header eines Requests gegen ein Backend aufgerufen. -- Beispiel ↓ ``` sub vcl_backend_response { # Das für die Response verantwortliche Backend wird via HTTP-Header in vcl_deliver "mitgenommen" set beresp.http.X-Request-Handled-By = beresp.backend.name; if(beresp.http.status >= 500) { # Das probieren wir nochmal - bis zu max_retries Mal, dann landen wir in vcl_backend_error return(retry); } } ``` --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (5/6) ## vcl_deliver Diese Routine ist für das Ausliefern der HTTP Response an den User-Agent verantwortlich. Hier kann man HTTP-Response-Header, die ein Backend gesetzt hat, modifizieren. -- Beispiel ↓ ``` sub vcl_deliver { if(resp.status == 503) { set resp.http.Retry-After = "30"; } # Rule #1 of the Varnish Club: ... unset resp.http.X-Varnish; unset resp.http.Via; set resp.http.server = "Microsoft-IIS/8.5"; set resp.http.X-AspNet-Version = "4.0.30319"; } ``` --- # Die (wahrscheinlich) wichtigsten VCL-Konstrukte (6/6) ## synth Wird verwendet, um eine HTTP-Response auszuliefern, die nicht (ursprünglich) von einem Backend generiert wurde. -- Beispiel ↓ ``` sub vcl_recv { [...] if(!req.http.User-Agent) { return(synth(400, "Bad Request - declare your User-Agent and try again"); } [...] } ``` --- # So wird's gemacht: VHosts -- Apache 2.x: ```no-highlight
ServerName example.com ServerAlias www.example.com ProxyPass ... [...]
``` -- Varnish 4: ``` sub vcl_recv { if (req.http.host == "example.com" || req.http.host == "www.example.com") { set req.backend_hint = ...; } [...] } ``` --- # So wird's gemacht: Rewrites -- Apache 2.x: ```no-highlight RewriteEngine On # WTF? RewriteRule ^/abc_(.+)_([^_]+)\.(js|css)$ /b/$3$2.$1 [L,PT] ``` -- Varnish 4: ``` set req.url = regsub(req.url, "^/abc_(.+)_([^_]+)\.(js|css)$", /b/\3\2.\1"); ``` --- # Varnish ausprobieren - aber wie anfangen? ## Plug & Play, nahezu ohne Risiko Wenn man auch nur etwas Arbeitsspeicher frei hat, ist es sehr einfach, Varnish einzusetzen - die **konservativen Standardeinstellungen** gewährleisten, dass man der eigenen Seite nicht schadet, wenn man Varnish "nur schnell zum Testen" als HTTP-Terminationspunkt ausprobieren will. Inhalte, die den Regeln der HTTP-Spezifikation und der Vernunft entsprechend gechachet werden können, wird Varnish cachen. Andere Responses werden einfach zwischen Backend und User-Agent durchvermittelt. Ist der von Varnish allokierte Speicher aufgebraucht, fliegen wenig gefragte Objekte aus dem Cache - die Welt ist deswegen um nichts schlechter als zuvor, ganz ohne Varnish. Varnish vor eine Webapplikation zu schalten ist für letztere **nahezu transparent**. Man muss aber aufpassen, dass diese mit einem Reverse Proxy zurechtkommen. **Vorsicht** ist u. a. **bei Redirects** (HTTP Status 3xx) und bei **Zugriffsbeschränkungen auf Source-IP-Adressen** geboten! --- # Wie und wovon man sofort profitiert ## Backends haben mehr Zeit für das Wesentliche Nachdem statischer Content von Varnish direkt aus dem Cache serviert wird, können sich die Backend-Webserver auf jene Requests konzentrieren, die einen Handler-Thread oder -Prozess auch wirklich nötig haben bzw. verdienen. ## Entkoppelung schafft Flexibilität Wenn Varnish anstatt des Backends die Verbindung terminiert, kann man dort Features aktivieren, die sonst vielleicht Probleme schaffen - bspw. Unterstützung für HTTP **Pipelining** oder aggressiveres TCP **Keepalive**. ## Automatische Immunität vor einigen DoS-Angriffsformen Manche DoS-Attacken laufen gegen Varnish aus arichtektonischen Gründen automatisch ins Leere - z. B. "Slowloris". Andere Unschönheiten kann man durch das Response-Caching umschiffen. --- # Für Fortgeschrittene (1/2) ## Traffic-Analyse Wie bereits erwähnt, loggt Varnish in Shared Memory. Durch Tools, die sich in das von `varnishd` eröffnete shm-Segment einklinken, kann man dieses Log sehr flexibel und in Echtzeit mitverfolgen. Mithilfe der Varnish-API kann man eigene Clients schreiben, die die shm-Inhalte auswerten. Varnish bringt aber von Haus aus zwei Werkzeuge mit, die viel Interessantes ermöglichen: - varnishncsa Gibt VSL-Daten in einem Apache-"Common"-ähnlichen Textformat aus. - varnishlog Ermöglicht das Dekodieren und Betrachten von VSL-Segmenten unter dem Text-Mikroskop. Beide unterstützen die **VSL Query Language**. --- class: center, middle # Demo --- # Für Fortgeschrittene (2/2) ## Introspektion und Kontrolle zur Laufzeit - varnishadm Mit varnishadm steht ein konsolenbasiertes Werkzeug zur Steuerung von varnishd-Instanzen zur Verfügung. Mit ihm kann man den Zustand von Varnish prüfen, sowie die aktuelle Konfiguration direkt beeinflussen (varnishd-Parameter, sowie VCL). - varnishstat Dieses Werkzeug gibt Einblick in viele interessante und teilweise wichtige Kennwerte (Counter), die die Effektivität der Konfiguration messen helfen. --- class: center, middle # Demo --- # Weiterführende Links - [Google Developers: Web Fundamentals - HTTP Caching](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching) - [Varnish User Manual](https://www.varnish-cache.org/docs/4.1/users-guide/index.html) - [VCL: Built-in subroutines](https://www.varnish-cache.org/docs/4.1/users-guide/vcl-built-in-subs.html) - [Varnish Reference Manual](https://www.varnish-cache.org/docs/4.1/reference/index.html) - [Varnish Counters](https://www.varnish-cache.org/docs/4.1/reference/varnish-counters.html) - [varnishlog](https://www.varnish-cache.org/docs/4.1/reference/varnishlog.html) - [VSL Query Expressions](https://www.varnish-cache.org/docs/4.1/reference/vsl-query.html) - [Varnish 4.1 builtin VCL](https://github.com/varnishcache/varnish-cache/blob/4.1/bin/varnishd/builtin.vcl) - [The Varnish Book](http://book.varnish-software.com/4.0/)