varnish-cache, un molt bon aliat per a la web

Ja fa un parell d’anys que faig servir el varnish cache i és un molt bon aliat de qualsevol plataforma web per descarregar als servidors http (apache, nginx, lighthttpd, etc… ) i al backend, ja sigui per aplicacions amb poc tràfic com per aplicacions/plataformes d’alt rendiment, on pot ser un CLAU aliat.

Per això, en aquest post enumenarem les raons principals d’implantar el varnish, citarem els processos i posarem 4 exemples de configuracions amb diferents proposits.

Bueno, començaré enumerant les 3 funcionalitats principals:
– Accelerador de continguts web, minimitzant considerablement els temps mitjans de resposta dels continguts dinamics d’un site.
– Alliberar de càrrega el backend, des dels servidors web i sobretot a les bases de dades, sql i nosql.
– Permetre servir continguts “caducats” en cas que la plataforma tingui problemes de rendiment.

Altres funcionalitats més avançades que podem fer amb Varnish es,
– Si treballeu amb CDNs pot fer el paper d’amortidor en moments de canvis bruscos de tràfic, per exemple, un campanya amb molt èxit, una noticia important, etc…
– Securitzar el website, per segons quines parts de la web podem exigir que les peticions haguin de complir uns determinats requeriments. Això també ho pot fer el apache, però aixi el podem alliberar d’aquestes tasques.
– En plataformes d’alt rendiment, si treballeu amb CDNs, per bloquejar l’accés a determinades parts de les webs que no es puguin processar per averia en el sistema.

Segur que podeu trobar altres funcionalitats, aquestes són les que he utilitzat.

La filosofia del Varnish es emmagatzemar a memòria volàtil tots els cantinguts “cachejables” i estalviar de cicles de proces i cicles d’espera d’entrada i sortida, es a dir, l’equiliri IO-CPU-RAM es veurà afectat així: disminuim IO, disminuim us de CPU, increment ús de memoria, on és molt important dimensionar bé la memòria que utilitzarem.

Un dels principals inconvenients dels Varnish és el HTTPS, encara que té el sentit. Varnish és un accelerador de continguts HTTP que vol disminuir el temps de resposta, el httpS és el contrari, requereix de temps de proces per a tal d’assegurar la seguretat. Conclusió, Varnish no es capaç i no ho serà mai de servir contignut per HTTPS. Només el podrem fer servir per aquelles parts del website que no siguin segures. També ho podeu utilitzar en parts de la web segures si poseu un nginx per davant, en aquest altre post teniu explicat millor aquesta solució.

D’altra banda, la corba d’aprenentatge del varnish es considerable, de fet, un graph em va ajudar a entendre totes les transicions possibles, es a dir, és el tipic aliat que si el compliquem molt es pot tornar en enemic. De fet, el varnish són 2 procesos un pare i un fill (fork), on el pare fa el paper de gestió i compila les configuracion escrites amb llenguatge VCL i el fill és qui realment fa realment les funcions descrites en la configuracio VCL. En aquest link ho explica detalladament.

Es a dir, la clau del varnish, és escriure un VCL amb allò que realment necessitem que faci. Aqui us deixo un graph que vaig decidir fer un cop vaig llegir la documentació oficial del varnish d’aquest link.
Varnish_subroutines_Graph

Seguidament posarem 3 exemples de fitxers basics de VCL i comentarem la seva finalitat:

Exemple 1

backend default {
  .host = "127.0.0.1";
  .port = "8080";
  .max_connections = 40;
  .first_byte_timeout = 180s;
  .between_bytes_timeout = 180s;
}

sub vcl_recv {
      return(pass);
}

Aquesta configuració l’únic que voliem és limitar la quantitat de connexions(max_connections) que li passarem al un apache en local, en aquest cap no cachejem res.

Exemple 2

backend default {
  .host = "127.0.0.1";
  .port = "8080";
  .max_connections = 200;
  .connect_timeout = 180s;
  .first_byte_timeout = 180s;
  .between_bytes_timeout = 180s;
  .probe = {
    .url = "/healthcheck.php";
    .timeout = 100 ms;
    .interval = 1s;
    .window = 10;
    .threshold = 8;
  }
}

sub vcl_recv {
  if (req.http.Host == "setup.domain.com")
  {
    set req.grace = 30s;
    return(lookup);
  }
  else
  {
   return(pass);
  }
}

sub vcl_fetch {
  set beresp.grace = 1h;
}

En aquest exemple només cachejem les peticions del domini “setup.domain.com”, domini intern de la plataforma que serveix les configuracions de l’aplicació. Ademés, volem que si el backend no respon, seguiran sen vàlids durant 1 hora. Això ho conseguim en les tres últimes linies de codi. El meu company Tomas té un article on exemplifica molt bé aquest comportament. Una de les coses més interessants del article de Tomàs es diferenciar les dues situacions en les que aquest parametre es important.
– No hi ha cap backend disponible, per tant servim contingut antic.
– Hi ha backends disponibles, i un fil ja ha demanat nou contingut. Mentre aquest nou contingut arriba des del backend, el varnish continua servint l’antic a la resta de fils.

Exemple 3

sub vcl_recv {
…
        if (req.url ~ ".*remindmethepassword.*" && !req.http.User-Agent) {
            error 403 "Forbidden";
        }
…
        return(pass);
}

En el exemple anterior, podem aferir-hi regles que augmentent la seguretat, per exemple, en les peticions que tinguin remindmethepassword en la URL necessitaran tenir el User-Agent, sino retornem un 403.

Exemple 4

backend default {
  .host = "127.0.0.1";
  .port = "8080";
  .max_connections = 200;
  .connect_timeout = 180s;
  .first_byte_timeout = 180s;
  .between_bytes_timeout = 180s;
  .probe = {
    .url = "/healthcheck.php";
    .timeout = 10 ms;
    .interval = 5s;
    .window = 5;
    .threshold = 3;
  }
}

sub vcl_error {
}

sub vcl_recv {
     if (req.restarts == 0) {
       if (req.http.x-forwarded-for) {
           set req.http.X-Forwarded-For =
               req.http.X-Forwarded-For + ", " + client.ip;
       } else {
           set req.http.X-Forwarded-For = client.ip;
       }
     }
     if (req.request != "GET" &&
       req.request != "HEAD" &&
       req.request != "PUT" &&
       req.request != "POST" &&
       req.request != "TRACE" &&
       req.request != "OPTIONS" &&
       req.request != "DELETE") {
         /* Non-RFC2616 or CONNECT which is weird. */
         return (pipe);
     }
     if (req.request != "GET" && req.request != "HEAD" && req.request != "POST" ) {
         /* We only deal with GET and HEAD by default */
         return (pass);
     }
   set req.grace = 30s;
    return(lookup);
}

sub vcl_fetch {
     if (beresp.ttl <= 0s ||
         beresp.http.Vary == "*") {
               return (hit_for_pass);
     }
 if(beresp.ttl > 0s){  unset beresp.http.Set-Cookie;}

  set beresp.grace = 1h;
  return(deliver);
}

En aquest exemple ja comença a ser més complert:
– limitat a 200 connexions, a més peticions, retornem 503.
– accepted petitions GET, HEAD, PUT, POST, TRACE, OPTIONS, DELETE.
– cachejem GET, POST, HEAD segons les capceleres HTTP que emet el backend
– en el moment en que anem a buscar un objecte en la cache, eliminem les capçeleres Set-Cookie
– tindrem 1 hora els elements en la cache, en cas que el backend estigui caigut o no respongui

Finalment, el ideal seria tenir ben segmentada la plana web amb les diferents seccions i tenir identificades les url’s de cadascuna d’elles facilitant així:
– La propia aplicació generi les capçeleres HTTP adecuades especificant quant de temps serà vàlid el contingut, segons quines seccións o accions tindrem uns temps de caducitat diferents
– La propia aplicació permite caducar/invalidar el contingut en el varnish en les situacions que ho necessitin.
– Securitzar les parts de la web que ho requereixin jugant amb les capçeleres

Balancejador HTTP/S econòmic: Nginx+Haproxy+Pacemaker

Destacades

Seguidament us explicaré com es pot montar un balancejador httpS molt econòmic i molt eficient, el qual s’ha implementat tan en entorns productius com no productius, la motivació inicial del qual va ser centralitzar el balanceig dels diferents entorns d’integració continua d’una aplicació web. En entorns productius també l’he implementat després de fer varies probes de rendiment amb el “ab” i el “siege” .

L’únic requeriment que hem d’asumir és allotjar tot tràfic dels diferents entorns en subdominis d’un domini (p.e.example.com), on podem tenir int01.example.com, int02.example.com, test01.example.com, test02.example.com, pre.example.com, etc..

Anàlogament, en els entorns de producció, podem fer la mateixa discriminació diferenciant el tràfic pels paisos(es.example.com,fr.example.com), plataformes(m.example.com), tipus de contingut(static.example.com).

Els objectius que em vaig marcar van ser:

  • Baix cost econòmic: utilitzarem programari de codi obert des dels operatius fins als dimonis: CentOS, Nginx, Haproxy, Pacemaker, Corosync.
  • Alta disponibilitat de maquinari i programari: a nivell de maquinari ho montarem en una infraestructura virtualitzada amb varis nodes físics i implemtarem el pacemaker+corosync per a tenir alta disponibilitat en els dimonis.
  • Centralitzar la negociació SSL de tota la plataforma en un punt inicial i alliberar càrrega SSL del reste de la plataforma.
  • Permetre més flexibilitat i control en els balancejos de cadascún dels entorns: haproxy permet fa possible controlar en un pool de balanceig si un frontal hi està activat segons el seu nivell de càrrega o si te connexió contra la base de dades, per exemple.
  • Tenir una plataforma securitzada, requeriment essencial sobretot en entorns productius.
  • Els dimonis que utilitzarem serà Nginx, Haproxy, Pacemaker i Corosync, tot programari lliure on abarata el cost considerablement.

    En el següent esquema es veu clarament l’arquitectura de tot el montatge.

    fisicalLB

    El comento breument, no entraré en detall en tot, em centro en lo important d’aquest post. Els navegadors dels usuaris fan les peticions a les empreses que fan de CDN, després aquestes peticions van a parar en la nostra plataforma on filtrarem el tràfic amb un firewall. Un cop les peticions són filtrades van a parar un nginx. La finalitat del nginx es discriminar el tràfic segons el entorn i centralitzar totes les negociacions SSL del tràfic HTTPS. Seguidament, les peticions passen a un haproxy on aquest balanceja entre els diferents frontals de cadascun dels entorns.

    Per tal de securitzar-ho tot, definirem 3 VLAN’s, dmz, frontend i backend. Tot el tràfic és filtrat pel firewall, el tràfic sortint, el entrant i el tràfic entre zones. En la DMZ posarem 2 maquines virtuals CentOs on tindrem els serveis de Nginx i Haproxy en actiu-passiu utlitzant pacemaker-corosync. En el frontend tenim els frontals de cadascun dels entorns i en el backend tenim les dades, mysql, cassandras, mongos, nfs, cifs, etc….

    Per exemplificar-ho millor, ens centrarem només en un entorn, int01.example.com.

    Primerament en la DMZ montarem una 2 màquines virtuals on hi tindrem 2 ip flotants, una per el nginx i l’altre pel haproxy. Jo ho he implemetat en una infraestructura virtualitzada vmware però també es poden montar solucions més econòmiques com XenServer, KVM, etc… Evidentment, sigui quina sigui la solució de virtualització, hem d’assegurar-nos que les màquines virtuales sempre estàn corrent en màquines físiques diferents.

    Les peticions entraran en la ip flotant del nginx, el nginx té dues funcions, centralitzar la negociació SSL i discriminar el tràfic per entorns. En el tràfic HTTPS afegirem una capçalera HTTP “X-forwarded-proto: https”, on l’apache la recollirà i activarà la variable HTTPS emmascarant-li a la aplicació. El tipus de certificat que afegirem en el nginx serà un certificat signat per una entitat certificadora oficial on el Common-Name que utilitzarem serà de tipus wildcard, es a dir, per aquest exemple, “*.example.com”, això ens facilitza molt la gestió del certificats i ens dona molta facilitat de gestió. En entorns de producció ho dimensionem assignant els valors correctes de worker_processes i worker_connections tenint en compte la formula: max clients = worker_processes * worker_connections/4.

    Seguidament les peticions passen al haproxy on aquest balanceja entre els diferents frontals de cadascun del entorn. El haproxy ens permet estriar l’algorisme de balanceig, jo acostumo a utilitzar Round Robin. A més, podem prefixar els rangs de IPs dels frontals que hi hauran en cadascun dels entorns, així podem desplegar més frontals en el cas que sigui necessari sense haber de reinicilitzar el dimoni.
    Haproxy fara una petició a un php que hi ha en cadascun dels frontals on retorna un “OK” si no està swapejant, no té molta càrrega i té conectivitat en les bases de dades. Cal remarcar que haproxy també ens permet realitzar balancejos “sticky” tan en aplicacions JAVA com PHP, depenentment d’una capçalara HTTP, habitualment les capçaleres JSESSIONID i PHPSESSID respectivament. (en apps ASP teniem ASPSESSIONID). En entorns de producció fixarem la variable maxconn tenim en compte que cada petició consumeix 17kb.

    Finalment les peticions arriben als frontals on primerament passen pel varnish i les peticions que no siguin cachejables arribaran finalment al apache, crec que aquests dos dimonis es mereixen un post especial per ells dos, que ja tinc en el backlog del blog.

    En el següent diagrama clarifica tota l’arquitectura:

    Diagrama Lògic

    Diagrama Lògic

    Seguidament posaré les configuracions més importants, Nginx, Haproxy, Pacemaker:

    Parametres del Nginx

    ....
    upstream http-example-int01 {
        server lb2-vip.example.com:8080;
        keepalive 16;
    }
    server {
            listen lb1-vip.example.com:80;
        server_name int01.example.com ~^.*-int01\.example\.com$;
    
            location / {
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header Host $host;
                    proxy_pass http://http-example-int01/;
                    proxy_redirect off;
            }
    }
    server {
            listen lb1-vip.example.com:443 ssl;
        server_name int01.example.com ~^.*-int01\.example\.com$;
    
            ssl on;
            ssl_certificate /etc/nginx/ssl/crt/concat.pem;
            ssl_certificate_key /etc/nginx/ssl/key/example.key;
    
            location / {
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto https;
                    proxy_set_header Host $host;
                    proxy_pass http://http-example-int01/;
                    proxy_redirect off;
            }
    }
    ....
    

    Parametres del Haproxy

    ...
    frontend example-int01 lb2-vip.grpprod.com:8080
        default_backend example-int01
    backend  example-int01
            option forwardfor
            option httpchk GET /healthcheck.php
            http-check expect string OK
            server  web01 x.y.z.w:80 check inter 2000 fall 3
            server  web02 x.y.z.w:80 check inter 2000 fall 3
            server  web03 x.y.z.w:80 check inter 2000 fall 3
            server  web04 x.y.z.w:80 check inter 2000 fall 3
            server  web05 x.y.z.w:80 check inter 2000 fall 3
    ...
    

    Parametres del Apache

     ServerName int01.example.com
        DocumentRoot "/srv/www/example/fa-front/public"
    
       <Directory "/srv/www/example/fa-front/public">
          Options -Indexes FollowSymLinks
          AllowOverride None
          Allow from All
          Order Allow,Deny
          RewriteEngine On
          RewriteCond %{HTTP:X-Forwarded-Proto} https
          RewriteRule .* - [E=HTTPS:on]
    
          RewriteCond %{REQUEST_FILENAME} -s [OR]
          RewriteCond %{REQUEST_FILENAME} -l [OR]
          RewriteCond %{REQUEST_FILENAME} -d
          RewriteRule ^.*$ - [NC,L]
          RewriteRule ^.*$ index.php [NC,L]
    
       SetEnv APPLICATION_ENV int01
       DirectoryIndex index.php
    
       LogFormat "%v %{Host}i %h %l %u %t \"%r\" %>s %b %{User-agent}i" marc.int01
       CustomLog /var/log/httpd/cloud-example-front.log example
    
    

    Parametres del Pacemaker

    node balance01
    node balance02
    primitive nginx lsb:nginx \
            op monitor interval="1s" \
            meta target-role="Started
    primitive haproxy lsb:haproxy \
            op monitor interval="1s" \
            meta target-role="Started"
    primitive lb1-vip ocf:heartbeat:IPaddr2 \
            params ip="x.x.x.x" iflabel="nginx-vip" cidr_netmask="32" \
            op monitor interval="1s"
    primitive lb2-vip ocf:heartbeat:IPaddr2 \
            params ip="y.y.y.y" iflabel="haproxy-vip" cidr_netmask="32" \
            op monitor interval="1s"
    group haproxy_cluster lb2-vip haproxy \
            meta target-role="Started"
    group nginx_cluster lb1-vip  nginx \
            meta target-role="Started"
    property $id="cib-bootstrap-options" \
            dc-version="1.1.7-6.el6-148fccfd5985c5590cc601123c6c16e966b85d14" \
            cluster-infrastructure="openais" \
            expected-quorum-votes="2" \
            stonith-enabled="false" \
            last-lrm-refresh="1355137974" \
            no-quorum-policy="ignore"
    rsc_defaults $id="rsc-options" \
            resource-stickiness="100"
    

    blog personal amb domini .CAT propi, distribució i hospedatge per menys de 7€/any

    Avui és un dia trist professionalment parlant perquè el passat divendres en Tomas Nuñez, company de feina amb qui m’ho he passat molt bé durant els darrers 2 anys, va finalitzar el contracte i ha marxat al Canada a començar una nova experiència  aprofito el post per desitjar-li tota la bona sort en la seva nova aventura on estic convençut que s’ho passarà molt bé!

    A més, també cal remarcar que la motivació inicial de trastejar tot l’esmentat en aquest post va néixer sopant amb els ex-companys de la EGB, on un d’ells, l’Estanis Plantada, un geògraf viatger apassionat de la bicicleta vol fer montar-se un site per anar publicant les seves experiències.

    Dit això, vaig per feina, en aquest post us explicaré com publicar un blog (en el meu cas el que tenia en la RaspberryPi) en el “cloud” (Amazon) on l’hi traurem un millor rendiment per menys de 8€ el primer any, no entraré molt en detall tècnic, únicament el que és important és tenir recopilades TOTES les funcionalitats implementades amb un cost molt baix, alhora, recopilo els links de les diferents fonts que he anat coneixent i jugant. Per a cada punt teniu el link de les fonts consultades on he tret la informació.

      • Registre de domini

    En poc menys d’uns mesos els dominis .puntCAT han baixat molt el seu preu, vaig repassar els registradors i el més econòmic va ser CDMON que per 6.95€. Els amics de Google han llençat als EUA, Canada i ha algun país més el projecte GYBO, on pots conseguir gratis el primer any el registre de un 1 domini amb TLD del tipus .ORG .COM .CA .IN, després val 5$/mes.

      • Distribució i securització del blog gràcies a la CDN de CloudFlare

    Si volem minitzar el trafic entre el usuari final i el hosting que tindrem en Amazon, podem activar gratuitament la funcionalitat de CDN de Cloudfare, a més, també hi afegim un petit control de seguretat. L’unic inconvenient és que la versió gratuita no soporta la distribució de continguts utilitzant el protocol segur HTTPS.

    Muntem un node virtual en Amazon EC2 on hi posarem el blog (en el meu cas he migrat el blog de la RaspberryPi), aquí és on els tècnics posem més de la nostre part :). A més, cal remarcar que tindrem control total d’una maquina virtual en 1 dels CPD’s, molt útil per qualsevol tècnic que ho munti on ho aprofitarà per trastejar i implementar altres eines (ja podeu estar segurs). A destacar! tenim gratis fins a un volum de 30GB en Amazon S3! (podem muntar un owncloud i ja tenim més que GoogleDrive i Dropbox!)

    Doncs tot això ho tenim muntat en poc més d’unes hores on només hi hem invertit menys de 7€!