Quelques outils pour charger un serveur web et surveiller son état

Outils de test de charge

  • ab :

Exemple de charge avec 500 clients concurrents, qui vont lancer une charge de 50000 requêtes au total.

ab -c 500 -n 50000 "https://mon_serveur/page.html"
  • siege

Siege à la différence d’ab lance un maximum de requêtes avec les clients concurrents dont il dispose sur une durée définie lorsque l’option -t est utilisiée. Si l’on utilise l’option -r, cela définit le nombre de requête par client

Dans cet exemple, lancement de 500 clients concurrents qui vont charger 5 fois, on aura 2500 requêtes lancées au total.

siege "https://mon_serveur/script.php" -c 500 -r 5

nb : attention l’option -t prévaut sur l’option -r.

  • jmeter

Avec jmeter, on commence à entrer dans le domaine du test fonctionnel car on peut créer des scénarios et autres. Il est un peu gros pour simplement tester des temps de réponses sur une requête.

  • gatling

Comme jmeter, mais une équipe française derrière.

Superviser le serveur ciblé

Les outils en ligne de commande

  • top : cpu, mémoire…
  • nmon : cpu, mémoire… (plus « visuel » que top)
  • lsof : les descripteurs de fichier
  • netstat : les sockets
  • df : espace disque général
  • du : espace disque par fichier
  • free : utilisation de la mémoire
  • mount -l : point de montage
  • sar : cpu, mémoire (package sysstat)
  • pidstat : process (package sysstat)
  • cat /proc/… : lecture des variables kernel en direct, le plus efficace pour comprendre d’où viennent les limites ou les valeurs par défaut
  • ulimit : visualisation des limites
  • sysctl -a : visualisation des variables kernel (plus rapide que /proc et en lien avec systemd)
  • monit : supervision générale avec affichage

Outils pour avoir des graphes

  • nagios : un classique (grosse artillerie mais efficace)
  • munin : le top pour faire des graphes (commence aussi à devenir une grosse artillerie)
  • m/monit : une couche http au dessus de monit (plus simple que les deux précédents mais sous licence payante)

 

Quelques liens pour comprendre certaines optimisations de serveur web

Le time-wait et pourquoi ne pas faire du recycle de socket:

https://vincent.bernat.im/fr/blog/2014-tcp-time-wait-state-linux

Gestion du backlog:

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

Gestion des limites

https://lzone.de/cheat-sheet/ulimit

https://unix.stackexchange.com/questions/345595/how-to-set-ulimits-on-service-with-systemd

Un petit script python pour tracer les limites

import platform

if 'linux' in platform.system().lower():
    import resource  # Linux only

    limit_nofile = resource.getrlimit(resource.RLIMIT_NOFILE)
    limit_nproc = resource.getrlimit(resource.RLIMIT_NPROC)

    print 'Max number of opened files allowed:', limit_nofile
    print 'Max number of processes allowed', limit_nproc

Modifier la limite de fichiers sur ubuntu 16.x pour nginx et fpm

Le système de gestion des limites a évolué et utiliser /etc/security/limits.conf n’est plus valide sur ubuntu 16 car le système de gestion de service est maintenant basé sur systemd.

Le plus frustrant c’est qu’à première vue rien a changé : dans /etc/init.d/ on retrouve nos scripts de lancement classique mais effectivement on observe un if systemd qui peux mettre la puce à l’oreille. En effet, l’installateur de paquet a fait son travail et a mis en place les fichiers de service compatible avec systemd. Il faut donc aller voir du côté de  /etc/systemd/system/multi-user.target.wants pour trouver nos services fpm et nginx.

# ll /etc/systemd/system/multi-user.target.wants
total 8
drwxr-xr-x 2 root root 4096 Jan 29 17:27 ./
drwxr-xr-x 13 root root 4096 Jun 13 2017 ../
[...]
lrwxrwxrwx 1 root root 33 Jun 15 2016 nginx.service -> /lib/systemd/system/nginx.service
lrwxrwxrwx 1 root root 38 Nov 29 2016 php7.0-fpm.service -> /lib/systemd/system/php7.0-fpm.service
[...]

Ensuite c’est assez simple, il suffit d’ajouter la limitation de fichiers dans le service nginx ou fpm:

LimitNOFILE=30000

Voici le service complet pour php7.0-fpm:

[Unit]
Description=The PHP 7.0 FastCGI Process Manager
After=network.target

[Service]
Type=notify
PIDFile=/run/php/php7.0-fpm.pid
ExecStartPre=/usr/lib/php/php7.0-fpm-checkconf
ExecStart=/usr/sbin/php-fpm7.0 --nodaemonize --fpm-config /etc/php/7.0/fpm/php-fpm.conf
ExecReload=/usr/lib/php/php7.0-fpm-checkconf
ExecReload=/bin/kill -USR2 $MAINPID
LimitNOFILE=30000

[Install]
WantedBy=multi-user.target

idem pour la limitation dans une session ssh. Le /etc/security/limits.conf n’a pas d’effet, j’ai donc forcé la limitation en ajoutant directement un ulimit dans le /etc/bash.bashrc :

ulimit -n 30000

Attention ceci s’appliquera à tous les utilisateurs, assurez-vous que c’est acceptable dans votre cas d’utilisation.

Mémos et liens utiles pour améliorer les performances d’nginx et php-fpm

Faut-il ou non mettre un cache ?

Il faut se poser la question de la donnée reçue en réponse de la requête : est-ce que la donnée doit être rafraîchie à chaque requête ou non. Cette question s’applique aussi bien a des données dynamiques que statiques, car une donnée statique peut être mise à jour par un gestionnaire de configuration à la volée, il faut alors savoir quelle est la fréquence d’update ou de changement de version du fichier. Dans l’autre sens, une donnée dynamique peut très bien s’avérer statique sur un an, imaginons une page de contact en php, dont l’html généré ne change qu’une fois l’an. Bref du bon sens et une bonne connaissance des contenus est nécessaire pour paramétrer au mieux son cache et un copié/collé d’une config trouvée sur le web n’aidera pas forcément.

A un niveau extrême, sur des cas d’usage à très forte volumétrie, la création d’un micro-cache sur un stream dynamique (php ou autre) est a étudier pour bénéficier de certaine optimisation nginx sur le remplissage de la socket TCP.

Gérer les buffers d’entrée et de sortie

https://www.nginx.com/resources/admin-guide/reverse-proxy/

Gardez à l’esprit qu’on est sur du TCP, donc le MTU et la gestion du buffer réseau va impacter le tout. De ce fait nginx a mis en place des directives pour optimiser tout ça :

sendfile on;
tcp_nopush on;
tcp_nodelay on;

Voici une explication claire de ce qui se passe derrière ces directives :

https://thoughts.t37.net/optimisations-nginx-bien-comprendre-sendfile-tcp-nodelay-et-tcp-nopush-2ab3f33432ca

La référence nginx pour du cache

https://www.nginx.com/blog/nginx-caching-guide/

Erreurs classiques sur nginx

https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/

De ce fait utilisez plutôt une variable (directive set) dans une directive location afin d’éviter la directive if et utilisez la directive proxy_cache_bypass pour ouvrir des trous dans le proxy et ainsi ne pas inclure dans le cache des contenus temps réels.

L’option de faire un micro-cache dédié au contenu temps-réel peut aussi être une option afin de réduire la latence liée au remplissage du buffer réseau.

Du cache en RAM, pour réduire les accès disques

https://angristan.fr/cache-module-pagespeed-ram-tmpfs/

Tmpfs ou Ramfs

tmpfs est meilleur que ramfs car plus récent mais on peut retomber sur des accès disques car tmpfs peut repartir dans le swap.

https://www.jamescoyle.net/knowledge/951-the-difference-between-a-tmpfs-and-ramfs-ram-disk

Une configuration

https://serversforhackers.com/c/nginx-caching

Cette configuration est une bonne base pour comprendre les principes et voir la différence  entre un cache de fichiers statiques et un cache fastcgi. Il reste à ajouter un micro-cache en mémoire, customiser les tailles de buffers et supprimer les if.

 

Limiter le trafic externe mais autoriser le trafic interne sur nginx

Le tuto nginx est plutôt « léger » sur l’explication concrète de cette configuration de limitation, voici donc quelques compléments d’explication :

geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/24 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;

server {
    location / {
        limit_req zone=req_zone burst=10 nodelay;

        # ...
    }
}

L’instruction geo créé un variable $limit qui aura pour valeur 0 ou 1 en fonction de l’adresse IP du client. Ici les 2 réseaux privés 10.0… et 192.168.. vont valoriser la variable $limit avec un 0, sinon $limit sera valorisée à 1.

L’instruction map va définir une variable $limit_key, sa valeur va dépendre de la valeur de la variable $limit. Si geo a identifié une IP publique, $limit contiendra 1, donc $limit_key sera valorisé à « $binary_remote_addr se qui va entraîner l’instruction limit_req_zone à s’appliquer sur l’IP du client. Si le client a une IP privée dans ce cas $limit_key sera valorisé à zéro et l’instruction limit_req_zone ne va pas s’exécuter.

La balise limit_req_zone applique une limitation à 5 requêtes par seconde si $limit_key est valorisé à 1.

Dans l’instruction limit_req, un burst (un bonus) de 10 est ajouté. Le nodelay va amener des réponses 503 à partir de la requête 16 si elles arrivent dans la même seconde. En supprimant le nodelay, le navigateur affichera une page qui se charge lentement.

Les liens vers le détail technique des commandes :

 

Générer une CSR pour une paire de clé/certificat existante via le terminal sur IOS

Cette note peut être utile si vous devez renouveler votre certificat de push ou de distribution sur Apple et que l’outil Keychain rejette la demande de création de CSR sur le certificat existant.

Le contournement est d’utiliser le terminal et les bonnes vieilles commandes classiques :

  • exporter la clé privé dans un fichier p12 : depuis l’outil keychain, faireun click droit sur la clé ou le certificat et sélectionner « exporter », on considère le p12 s’appellera ma_cle.p12
  • Basculer le p12 en pem:
    openssl pkcs12 -in ma_cle.p12 -out ma_cle.pem
  • Générer la csr:
    openssl req -new -key ma_cle.pem -out ma_cle.csr
  • et voila, il suffit d’importer la CSR sur apple developper.

Ajouter un certificat de serveur dans AWS IAM

La console IAM ne permet pas de gérer les certificats de serveur qui ont été insérés à la volée dans IAM via l’ELB. Il faut donc passer par l’API :

(une fois les pré-requis de l’API installés), exécutez:

aws iam upload-server-certificate --server-certificate-name mon.serveur.com.2017 --certificate-body file:///home/cve/ssl/serveur_crt.pem --certificate-chain file:///home/cve/ssl/cert_chain.pem --private-key file:///home/cve/ssl/serveur_key.pem

Ensuite vous retrouvez votre nouveau certificat dans la liste des certificats IAM sur la page de mise à jour du certificat de l’ELB.

Tracer la latence avec NGINX

Extracted from nginx blog, article from Rick Nelson · January 7, 2016


Using the NGINX Built-In Timing Variables

NGINX provides a number of built-in timing variables that you can include in log entries. All are measured in seconds with millisecond resolution.

  • $request_time – Full request time, starting when NGINX reads the first byte from the client and ending when NGINX sends the last byte of the response body
  • $upstream_connect_time – Time spent establishing a connection with an upstream server
  • $upstream_header_time – Time between establishing a connection to an upstream server and receiving the first byte of the response header
  • $upstream_response_time – Time between establishing a connection to an upstream server and receiving the last byte of the response body

Here is a sample log format called apm that includes these four NGINX timing variables along with some other useful information:

log_format apm '"$time_local" client=$remote_addr '
               'method=$request_method request="$request" '
               'request_length=$request_length '
               'status=$status bytes_sent=$bytes_sent '
               'body_bytes_sent=$body_bytes_sent '
               'referer=$http_referer '
               'user_agent="$http_user_agent" '
               'upstream_addr=$upstream_addr '
               'upstream_status=$upstream_status '
               'request_time=$request_time '
               'upstream_response_time=$upstream_response_time '
               'upstream_connect_time=$upstream_connect_time '
               'upstream_header_time=$upstream_header_time';

 

Using Application-Defined Timing Values

To drill down, we capture timings in the application itself and include them as response headers, which NGINX then captures in its access log. How granular you want to get is up to you.

To continue with our example, we have the application return the following response headers which record processing time for the indicated internal operations:

  • db_read_time – Database lookup
  • db_write_time – Database write
  • analysis_time – Data analysis
  • other_time – All other types of processing

NGINX captures the timing values from the response headers by creating corresponding variables, which it names by prepending the string $upstream_http_ to the header name (for example, $upstream_http_db_read_time corresponds to db_read_time). You can then include the variables in log entries just like standard NGINX variables.

Here is the previous sample log format extended to include the application header values:

log_format apm 'timestamp="$time_local" client=$remote_addr '
               'request="$request" request_length=$request_length '
               'bytes_sent=$bytes_sent '
               'body_bytes_sent=$body_bytes_sent '
               'referer=$http_referer '
               'user_agent="$http_user_agent" '
               'upstream_addr=$upstream_addr '
               'upstream_status=$upstream_status '
               'request_time=$request_time '
               'upstream_response_time=$upstream_response_time '
               'upstream_connect_time=$upstream_connect_time '
               'upstream_header_time=$upstream_header_time '
               'app_db_read_time=$upstream_http_db_read_time '
               'app_db_write_time=$upstream_http_db_write_time '
               'app_analysis_time=$upstream_http_analysis_time '
               'app_other_time=$upstream_http_other_time ';

Format d’image avec compression lossless

Sur le papier le meilleur format d’image avec compression lossless semble être FLIF:

http://flif.info/

FLIF a aussi l’avantage d’être libre de droit (dixit leur site).

Le format BPG est un peu moins bon au vue des comparatifs :

http://cloudinary.com/blog/flif_the_new_lossless_image_format_that_outperforms_png_webp_and_bpg

Il reste une bonne option bien que susceptible de tomber sous licence dans certains pays à cause du HEVC :

https://bellard.org/bpg/