Suite à la publication de mon précédent billet (Créer un caching HTTP façon CDN), j’ai eu une petite discussion sur twitter avec Nicolas Hennion sur un comparatif des outils de mise en cache HTTP. Je me suis donc proposé de (re)faire un billet sur le sujet. Je m’attaque donc aux outils suivants : nginx, varnish, squid et apache (avec mod_cache/mod_proxy).
Avant propos
Le but n’étant pas de refaire une optimisation système et matérielle, je vais faire au plus simple pour le comparatif. Certains pourront considérer ma démarche simpliste mais bon, c’est la vie.
Architecture pour le comparatif
Ne voulant pas avoir trop de différences j’ai utilisé des machines virtuelles lancées sur des machines couillues. Une machine de créée en debian stable 64 bits, 2 vCPU pour 8 Go Ram et 1 Gbps en réseau, pour me faire un template et ensuite je l’ai clonée ainsi :
- cache-apache : machine de cache en apache
- cache-nginx : machine de cache en nginx
- cache-squid : machine de cache en squid
- cache-varnish : machine de cache en varnish
- source-apache : machine qui distribue les fichiers de tests en http pour le serveur apache
- source-nginx : machine qui distribue les fichiers de tests en http pour le serveur nginx
- source-squid : machine qui distribue les fichiers de tests en http pour le serveur squid
- source-varnish : machine qui distribue les fichiers de tests en http pour le serveur varnish
- siege : la machine qui servira à générer les tests
- temoin : machine qui distribue les fichiers de tests en http sans caching
Je mets un serveur “origin” (marque source-xxxxx) par serveur de cache afin qu’un test ne perturbe pas l’autre et ainsi pouvoir faire mes tests en parallèle.
Tous les serveurs seront sur le même réseau (pas de routage) afin de ne pas risquer d’avoir des ACL ou des problèmes de performance de routage entre elles.
Critères et méthodologie de comparaison
Les critères pris en compte sont les suivants
- ergonomie pour un déploiement de masse
- possibilité de configuration fine des vhosts/directory/…
- optimisation de la consommation des ressources fournies
- temps de réponses
- débits
- montée en charge
Afin de tester l’ensemble de ces critères, et parce que je n’ai pas envie de m’installer d’outils complémentaires (et potentiellement lourds) pour le report de la métrologie, je ferais un simple visuel sur le résultat de top sur chaque serveur (cache-xxxx et source-xxxx) pendant le test.
Les tests se feront à coup d’ab et de siege sur des fichiers de 1 ko, 10 ko, 100 ko, 1 Mo, 10 Mo, 100 Mo et 1 Go. De plus, tous se feront sans keepalive d’activé.
Machine de siege
On lui installe le nécessaire :
# aptitude install siege apache2-utils
Afin de me simplifier la vie, les noms des machines sont écrites dans le fichier /etc/hosts.
Machine source et témoin
On installe le nécessaire. N’y cherchant pas la performance, on installe un basique apache.
# aptitude install apache2
Ni on le configure, ni on l’optimise.
On lui prépare ensuite les fichiers de tests dans /var/www :
# cd /var/www # dd if=/dev/urandom of=1k bs=1k count=1 # dd if=/dev/urandom of=10k bs=1k count=10 # dd if=/dev/urandom of=100k bs=1k count=100 # dd if=/dev/urandom of=1m bs=1024k count=1 # dd if=/dev/urandom of=10m bs=1024k count=10 # dd if=/dev/urandom of=100m bs=1024k count=100 # dd if=/dev/urandom of=1g bs=1024k count=1000
On a donc bien nos fichiers de test :
# ls -lh total 1.1G -rw-r--r-- 1 root root 100K Sep 7 10:43 100k -rw-r--r-- 1 root root 100M Sep 7 10:44 100m -rw-r--r-- 1 root root 10K Sep 7 10:43 10k -rw-r--r-- 1 root root 10M Sep 7 10:44 10m -rw-r--r-- 1 root root 1000M Sep 7 10:48 1g -rw-r--r-- 1 root root 1.0K Sep 7 10:43 1k -rw-r--r-- 1 root root 1.0M Sep 7 10:44 1m -rw-r--r-- 1 root root 177 Sep 7 10:41 index.html
Caching façon apache
On lui installe apache:
# aptitude install apache2
On jongle avec les modules utiles et non-utiles :
# a2dismod auth_basic authn_file authz_default authz_groupfile hostz_user autoindex cgid dir env reqtimeout status # a2enmod disk_cache proxy_http
On modifie les paramètres prefork dans /etc/apache2/apache2.conf :
<IfModule mpm_prefork_module> StartServers 16 MinSpareServers 16 MaxSpareServers 10 MaxClients 250 MaxRequestsPerChild 0 </IfModule>
On paramètre ensuite le proxy et le caching. Pour cela on se crée un vhost dédié /etc/apache2/sites-available/crash :
<VirtualHost *:80> NameServer crash CacheRoot /opt CacheMaxFileSize 1500000 ProxyPass / http://crash/ ProxyPassReverse / http://source-apache/ </VirtualHost>
On l’active :
# a2ensite crash # /etc/init.d/apache2 restart
Il y a vraiment peu de paramètres que l’on peut configurer à ce niveau pour améliorer le fonctionnement. Une petite modification côté système est à faire dans /etc/security/limits.conf :
* - nofile 65535
Installer une usine à gaz pour une petite fonctionnalité qui est en plus ne dispose d’aucun paramétrage fin.
Caching façon nginx
On rajoute les mirroirs nginx :
deb http://nginx.org/packages/debian/ squeeze nginx deb-src http://nginx.org/packages/debian/ squeeze nginx
Puis :
# wget http://nginx.org/packages/keys/nginx_signing.key -O - | apt-key add - # aptitude update
On installe le nécessaire :
# aptitude install nginx
On configure le vhost en créant un fichier /etc/nginx/sites-available/crash :
server { listen 80; server_name crash; proxy_cache_key $scheme://$host$uri; location ~* / { proxy_hide_header "Vary"; add_header "Vary" "Accept-Encoding"; proxy_cache big; proxy_pass http://source-nginx; } }
Puis en l’activant :
# ln -s /etc/nginx/sites-available/crash /etc/nginx/sites-enable/ # /etc/init.d/nginx restart
Ensuite on remplace le fichier /etc/nginx/nginx.conf par le suivant :
user www-data; worker_processes 4; worker_rlimit_nofile 10000; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; timer_resolution 1ms; events { worker_connections 10000; multi_accept on; use epoll; accept_mutex_delay 1ms; }
http { include /etc/nginx/mime.types; client_body_temp_path /tmp 1 2; client_header_timeout 5s; client_body_timeout 5s; send_timeout 10m; connection_pool_size 128k; client_header_buffer_size 16k; large_client_header_buffers 1024 128k; request_pool_size 128k; keepalive_requests 1000; keepalive_timeout 10; client_max_body_size 10g; client_body_buffer_size 1m; client_body_in_single_buffer on; open_file_cache max=10000 inactive=300s; reset_timedout_connection on; gzip on; gzip_static on; gzip_min_length 1100; gzip_buffers 16 8k; gzip_comp_level 9; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_vary on; gzip_proxied any; output_buffers 1000 128k; postpone_output 1460; sendfile on; sendfile_max_chunk 256k; tcp_nopush on; tcp_nodelay on; server_tokens off; resolver 127.0.0.1; ignore_invalid_headers on; index index.html; add_header X-CDN "Served by myself"; proxy_cache_path /opt/disk/ levels=1:2 keys_zone=big:10m max_size=2G; proxy_temp_path /opt/temp/ 1 2; proxy_cache_valid 404 10m; proxy_cache_valid 400 501 502 503 504 1m; proxy_cache_valid any 4320m; proxy_cache_use_stale updating invalid_header error timeout http_404 http_500 http_502 http_503 http_504; proxy_next_upstream error timeout invalid_header http_404 http_500 http_502 http_503 http_504; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header Server Apache; proxy_set_header Connection Close; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_header Set-Cookie; proxy_pass_header User-Agent; proxy_set_header X-Accel-Buffering on; proxy_hide_header X-CDN; proxy_hide_header X-Server; proxy_intercept_errors off; proxy_ignore_client_abort on; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_read_timeout 60; proxy_buffer_size 128k; proxy_buffers 16384 128k; proxy_busy_buffers_size 256k; proxy_temp_file_write_size 128k; proxy_cache_min_uses 0; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
Une petite modification côté système est à faire dans /etc/security/limits.conf :
* - nofile 65535
A mon sens, c’est celui qui offre le plus de possibilités dans le paramétrage fin et aussi dans l’évolutivité. Il ne lui manque que peu (SSI en tête de liste) pour être parfait.
Caching façon squid
On commence par installer l’applicatif :
# aptitude install squid
Vous remarquerez que l’installe une version de la branche 2.x et non 3.x. La raison est simple : squid3 est encore largement en retrait au niveau fonctionnalité (et stabilité) par rapport à la précédente branche, que l’ancienne évolue toujours et qu’on cherche à comparer des produits à mettre en prod.
On a besoin de changer les droits sur le dossier /opt :
# chmod 777 /opt
On paramètre alors le caching en éditant /etc/squid/squid.conf :
acl all src all acl localnet src 192.168.0.0/16 acl Safe_ports port 80 acl CONNECT method CONNECT http_access deny !Safe_ports http_access allow localnet http_access deny all icp_access allow localnet icp_access deny all http_port 80 transparent refresh_pattern . 0 20% 4320 acl apache rep_header Server ^Apache broken_vary_encoding allow apache extension_methods REPORT MERGE MKACTIVITY CHECKOUT hosts_file /etc/hosts coredump_dir /var/spool/squid cache_peer source-squid parent 80 0 no-query originserver cache_dir aufs /opt 2000 16 256 tcp_recv_bufsize 131072 bytes maximum_object_size 1500000000 bytes
On n’oublie pas de modifier le fichier /etc/default/squid :
SQUID_MAXFD=10240
On pense encore à la petite modification côté système à faire dans etc/security/limits.conf en rajoutant :
* - nofile 65535
Squid est je pense celui qui a la configuration la moins claire. L’optimisation est très restreinte. Son avantage est l’implémentation complète de l’HTCP qui permet à l’architecture de caching de faire communiquer les noeuds entre eux, là où les autres demandes des petites astuces. Mono process, il ne profite pas des possibilités de la machine.
Caching façon varnish
On commence par l’installation :
# aptitude install varnish
On suit par la configuration en commencant par modifier les lignes suivantes dans /etc/default/varnish :
START=yes DAEMON_OPTS="-a :80 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -p thread_pools=4 \ -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,2G"
On enchaîne avec la configuration de /etc/varnish/default.vcl :
backend default { .host = "source-varnish"; .port = "80"; .connect_timeout = 1s; .first_byte_timeout = 5s; .between_bytes_timeout = 2s; } sub vcl_recv { return(lookup); } sub vcl_fetch { return(deliver); }
On rajoute la petite modification côté système dans etc/security/limits.conf :
* - nofile 65535
Varnish n’a pas une syntaxe super claire. De plus, le paramétrage fin s’arrête à définir les durées des objets, les latences et les actions à mener sur les HIT/MISS & co. Le seul intérêt que j’ai trouvé à varnish est l’implémentation complète des SSI (qui n’est que partielle sur nginx par ex).
Comparatif
siege
Siege fournit un test sur la durée et la qualité de réponse sur cette durée. Pour chaque fichier de test (remplacer XX par le nom du fichier), on lance les commandes suivantes :
siege -b -c 1 -t 1M http://crash/XX siege -b -c 10 -t 1M http://crash/XX siege -b -c 100 -t 1M http://crash/XX siege -b -c 1000 -t 1M http://crash/XX siege -b -c 10000 -t 1M http://crash/XX
Siege étant très gourmand en mémoire, on est obligé de restreinte les tests sur la VM utilisée. Le tableau de résultat est disponible directement au format PDF.
Notez les points suivants :
- les durées sont annoncées en secondes
- les débits sont en Mo/s
- les erreurs sont dûes soit à la partie cliente (siege) soit à la partie serveur et donc sont à mettre entre parenthèses
ab
ab fournit un test instantanné pour définir la qualité de réponse du serveur sur une charge pré définie. Pour chaque fichier de test (remplacer XX par le nom du fichier), on lance les commandes suivantes :
ab -n 1 -c 1 http://crash/XX ab -n 10 -c 10 http://crash/XX ab -n 100 -c 100 http://crash/XX ab -n 1000 -c 1000 http://crash/XX
Le tableau de résultat est disponible directement au format PDF.
Notez les points suivants :
- les durées sont annoncées en secondes
- global est la durée globale du test
- moyenne est le temps moyen rencontré
- max est le temps maximum de chargement de la page
- erreur est le nombre de retour non valide
Conclusion
varnish est une très bonne solution en soit mais il s’avère que le potentiel et les performance de nginx n’en ont pas fait l’un des serveurs HTTP de warez N°1 pour rien en son temps. Aujourd’hui, l’utiliser pour du caching a toutes ses raisons et bien plus encore.