Créer un caching HTTP façon CDN

C’est la mode au tout Cloud. Je ne m’étalerais pas sur mon avis très critique à cette mode commerciale. Cependant, le fonctionnement des sites en eux-mêmes changent peu et la mode du Cloud n’entache en rien le besoin de performance. C’est le service proposé par de nombreuses société de part de le monde avec des services de livraison de contenu (CDN) qui inclus :

  • distribution HTTP de contenus statiques
  • accélération HTTP de contenus statiques
  • streaming audio/vidéo live ou on-demand

Je vais donc me focaliser sur comment monter une plateforme minimale de livraison HTTP et d’accélération HTTP.

Avant propos et plateforme HTTP

L’accélération web consiste en la fourniture d’un service HTTP fiable, perfomant, et allégeant la charge sur la plateforme web du client : tout le contenu statique doit être fournit par la plateforme d’accélération ou au maximum, alors que les pages dynamiques sont services par la plateforme du client. Une plateforme d’accélération est simple à mettre en place. La problèmatique par contre, va résider principalement dans les performances (systèmes et réseaux) et dans les fonctionnalités avancées offertes aux clients. Le service doit être mutualisé avec un maximum de clients mais être également flexible et adapté à chacun. La distribution HTTP repose sur le même principe à la différence que l’on doit héberger le contenu. Il est important qu’une machine qui fait de la distribution ne fasse pas d’accélération et inversement. Cette restriction est importante car les optimisations divergent quelques peu. Je ne m’attarderais pas sur l’installation des systèmes en eux-même.

Le matériel

Les performances dépendent aussi bien des logiciels et de leurs configurations, que du matériel choisi. Selon les moyens, on choisira entre l’aggrégat d’interface en gigabit ou la mise en place de cartes 10 Gbps. Il est à noté que les temps d’accès sont différents entre de l’optique et du cuivre, à l’avantage de l’optique. De même, des SSD sont à privilégier pour le cache disque lors que des disques en 15k rpm ou 10k rpm peuvent suffire sur la partie distribution. Pourquoi je me refuse à prendre des appliances ?

  • limitées dans les fonctions additionnelles (ou sous licenses trop chères)
  • peu évolutives
  • performances largement en retrait par rapport à du home-made

Le logiciel

Il est intéressant de privilégier pour les noeuds de cache, des machines diskless, au système démarré en PXE et mis en Ramdisk. Si vous avez besoin d’un article sur ce point, laissez-moi savoir. L’avantage est de réduire les I/O disques lié au système. Côté mise en cache des objets, on se penchera sur un gros RAID0 de disques en SSD, le tout avec un formatage en XFS. Pourquoi pas en Ram ? simplement parce qu’il est facile de saturer les accès concurrents en Ram dans le cas d’un CDN et que les performances sont largement suffisantes avec une belle grappe de SSD. Côté service HTTP, on utilisera nginx, aussi bien pour la distribution que pour le caching.  Pourquoi pas d’autres ?

  • varnish en caching : il est moins extensible que nginx, un chouillat plus lourd et je le trouve clairement moins agréable à l’utilisation
  • squid en caching : monolithique et non multithreadé, on y perd en performance et en optimisation ; de plus sa configuration est trop lourde et ses performances sont au final en retrait
  • apache en caching ou en distribution : oui, pourquoi ne pas utiliser une gros usine à gaz polluante, archaïque et peu performante à la place de solution légère et performante ?

On n’écrit rien en local niveau log : on mettra en place un syslog distant et centralisé. L’attrait est double : réduire les I/O locales et pouvoir facilement générer les statistiques HTTP.

La plateforme HTTP

Celle-ci regroupe donc à minima (et hors volonté de redondances sur tout) :

  • un serveur PXE pour fournir les OS aux machines diskless (non détaillé ici) (pxe)
  • un serveur syslog et statistiques (stats)
  • un serveur de base de données pour le backoffice offrant aux clients ou à l’équipe les outils pour le déploiement des configurations par vhosts (non détaillé ici) (db)
  • les serveurs de monitoring et management (master)
  • les serveurs de caching (on parle d’edges) ; il y aura 2 niveaux, donc on détaillera en edge / source
  • les serveurs origins HTTP

Pour des raisons de disponibilité (et de proximité), on sera amené à dupliquer autant de fois qu’il le faut chaque élément sur des sites distants. La plateforme est duplicable à volonté. L’architecture est une pyramide inversée, à savoir à minima :

  • 4 serveurs edge qui attaquent
  • 2 serveurs source qui attaquent
  • la plateforme du client ou 2 serveurs origin

Les autres serveurs sont mis en parallèle de ce déploiement.

Le réseau

Les VIP en mode DSR seront gérées par des LVS déployés sur les masters. On peut aussi investir dans des cartes ACE. Sur la partie GSLB (load balancing géolocalisé) je vous conseille de vous référer à votre ingé réseau sur les possibilités de votre infra au niveau déclaration des entêtes & co. En effet, faire du GSLB en DNS pour la plateforme en elle-même est un peu moins efficace :

  • à cause des serveurs DNS des FAI qui ne respectent pas les TTL (Free par ex …)
  • à cause des utilisateurs passant par des serveurs DNS tiers (Google, OpenDNS) qui faussent le calcul de géolocalisation
  • du fait que le site mis en cache doit aussi l’appliquer dans sa zone DNS et que c’est parfois compliqué de motiver un client

La qualité du transit et des peerings est un point important pour assurer d’excellentes performances du CDN. On note :

  • X.X.A.0/24 le range d’IP public du datacenter A
  • X.X.B.0/24 le range d’IP public du datacenter B
  • 10.0.A.0/8 le range d’IP privé du datacenter A
  • 10.0.B.0/8 le range d’IP privé du datacenter B

Les zones privées communiquent entre elles.

Installation des serveurs origin

Matériel

Au niveau matériel il s’agit, dans mon cas, de serveurs Dell R510 avec 12 disques de 2 To (pour le moment), 24 Go Ram, interfaces en 10 Gbps optiques. On commence par déployer les packages dont on a besoin :

aptitude -y install nginx-extras bind9 ntp sysfsutils xfsprogs syslog-ng

Les serveurs origin se duplique automatiquement les fichiers du client grâce à une réplication en GlusterFS :

wget http://download.gluster.org/pub/gluster/glusterfs/LATEST/Debian/5.0.3/glusterfs_3.3.0-1_amd64.deb
dpkg -i glusterfs_3.3.0-1_amd64.deb

La partie concernant l’importation des fichiers clients n’est pas exploitées ici. Du simple FTP au serveur SFTP utilisant un backend en LDAP, le choix est large.

NTP

On modifie simplement les lignes server du fichier /etc/ntp.conf :

server 10.0.A.201
server 10.0.B.201

DNS

Le service ne sera utilisé qu’en local pour la résolution DNS pour les besoins du nginx. Pour cela, on édite le fichier /etc/bind/named.conf/options :

options {
 directory "/var/cache/bind";
 query-source address * port *;
 forwarders { 10.0.A.201; 10.0.B.201; };
 auth-nxdomain no;
 listen-on-v6 { none; };
 listen-on { 127.0.0.1; };
 allow-query { any; };
 allow-recursion { any; };
 version none;
 max-clients-per-query 0;
 clients-per-query 0;
 recursive-clients 10000;
 minimal-responses yes ;
};

OPTIMISATION SYSTEME

On s’attaque au scheduler disque en modifiant le fichier /etc/sysfs.conf :

block/sdb/queue/scheduler = noop

On pense à adapter la partie en gras en fonction de ses disques. Dans la même idée, on modifier le bootloader grub en modifiant /etc/default/grub :

GRUB_CMDLINE_LINUX="elevator=noop quiet"

puis en mettant à jour grub :

update-grub

Le filesystem de l’espace de cache a aussi son importance :

mkfs.xfs -f -l size=128m /dev/sda3

On adapte aussi le fichier /etc/fstab :

/dev/sdb1 /data/store/ xfs rw,nobarrier,largeio,noatime,nodiratime,logbufs=8,inode64 0 2

On organise ensuite la réplication entre les serveurs source. La réplication GlusterFS n’est à configurer que sur l’un des noeuds :

gluster volume create store replica 2 transport tcp origin1:/data/store origin2:/data/store
gluster volume start store
gluster volume set store performance.write-behind-window-size 1024
gluster volume set store cluster.self-heal-window-size 1024
gluster volume set store cluster.lookup-unhashed off
gluster volume set store performance.flush-behind on
gluster volume set store nfs.disable on
gluster volume set store cluster.self-heal-daemon on
gluster volume set store performance.cache-size 2147483648
gluster volume set store performance.io-thread-count 64
gluster volume set store feature.read-only off

Notez qu’on peut répliquer autant que l’on désire. On prépare ensuite le montage du volume répliquer pour travailler dessus (/etc/fstab) :

origin1:/store /data/tmp glusterfs defaults,_netdev 0 0

On peut laisser le nom du premier serveur partout : le client GlusterFS l’utilise juste pour créer la connexion mais utilise le serveur qu’il considère le meilleur (en temps de réponse) pour les accès lecture/écrite. La partie sysctl est importante (/etc/sysctl.conf) :

net.ipv4.conf.default.rp_filter = 0 
net.ipv4.conf.default.arp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.all.arp_filter = 0
net.core.rmem_default = 16777216
net.core.rmem_max = 16777216
net.core.wmem_default = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_mem = 4096 65536 16777216
net.ipv4.tcp_low_latency = 0
net.core.netdev_max_backlog = 30000
fs.file-max = 262144
kernel.shmmax = 16000000000
kernel.shmall = 16000000000
net.ipv4.tcp_abort_on_overflow = 1
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024 65535
vm.min_free_kbytes = 65536
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.eth0.arp_ignore = 1
net.ipv4.conf.eth1.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.eth0.arp_announce = 2
net.ipv4.conf.eth1.arp_announce = 2
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_timestamps = 0 
net.ipv4.tcp_sack = 0
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_keepalive_intvl = 1
net.ipv4.tcp_keepalive_probes = 1 
net.ipv4.ip_forward = 1
net.ipv4.conf.default.proxy_arp = 1
net.ipv4.conf.all.proxy_arp = 1
kernel.sysrq = 1
net.ipv4.conf.default.send_redirects = 1
net.ipv4.conf.all.send_redirects = 1
kernel.core_uses_pid=1
kernel.core_pattern=1
vm.dirty_background_ratio = 20
vm.dirty_ratio = 40
vm.swappiness = 1
vm.dirty_writeback_centisecs = 1500
fs.xfs.xfssyncd_centisecs = 360000
fs.xfs.xfsbufd_centisecs = 3000
net.ipv4.tcp_max_syn_backlog = 65536
net.core.optmem_max = 40960
net.ipv4.tcp_max_tw_buckets = 360000
net.ipv4.tcp_reordering = 5
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_rfc1337 = 0
net.core.somaxconn=262144
net.ipv4.tcp_ecn = 0
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_moderate_rcvbuf = 1

A adapter selon les interfaces réseaux et la Ram (partie en gras). On l’applique avec un simple :

sysctl -p

Il est important d’appliquer les limites correspondantes au système (/etc/security/limits.conf) :

*    soft    nofile    262144
*    hard    nofile    262144

Certaines modifications sont à appliquer au démarrage du système (/etc/rc.local) :

ifconfig eth0 txqueuelen 10000
ifconfig eth1 txqueuelen 10000
ifconfig eth0 mtu 9000
ifconfig eth1 mtu 9000
ethtool -K eth0 rx off tx off
ethtool -K eth1 rx off tx off

Distribution HTTP

On s’attaque donc à nginx. D’abord pour la partie globale :

{% highlight conf %} user www-data; worker_processes 2;  worker_rlimit_nofile 250000;

error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;

timer_resolution 1ms;

events {  worker_connections 102400;  use epoll;  multi_accept on;  accept_mutex_delay 1ms; }

http {  include /etc/nginx/mime.types;  default_type application/octet-stream;

 log_format access ‘[$time_local] $request_time “$request_method $scheme://$host$request_uri $server_protocol” $request_length $status $bytes_sent “$http_referer” $remote_addr “$http_user_agent”’;  access_log /data/log/access.log access;  # Client connection  client_body_temp_path /tmp 1 2;  client_header_timeout 5s;  client_body_timeout 5s;  send_timeout 10m;  connection_pool_size 256k;  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=250000 inactive=300s;   reset_timedout_connection on;

 # Compression  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;

 # Network optimizations  output_buffers 1000 128k;  postpone_output 1460;  sendfile on;   sendfile_max_chunk 256k;  tcp_nopush on;  tcp_nodelay on;  server_tokens off;

 # DNS  resolver 127.0.0.1;

 # HTTP Request  ignore_invalid_headers on;  index index.html;  add_header X-CDN “Served by myself”;  add_header Connection close;

 server_names_hash_max_size 5120;  server_names_hash_bucket_size 256;

upstream_fair_shm_size 1024k;

 # Includes  include /etc/nginx/conf.d/.conf;  include /etc/nginx/sites-enabled/; } {% endhighlight %}

Ensuite, voici le template par vhost (au format twig) que votre robot déploiera :

{% highlight conf %}

{{ serial }}

server {  listen :80;  server_name {{ frontends|join(‘ ‘) }}; { % raw %}  {% for option in default_options %}   {{- option.name }} {{ option.value }};  {% endfor %}  {% for location in locations %}   {% for rule_expert in location.rules_expert %}   # {{ location.name }}   location ~ {{ rule_expert.pattern }} {    {% for option in location.options %}     {{- option.name }} {{ option.value }};    {% endfor %} root /data/tmp/vhosts/{{ vhost }}.nginx_backend/;   }   {% endfor %}  {% endfor %} { % endraw %}  error_page 404 /404.html;  error_page 500 /500.html;  error_page 502 /502.html;   error_page 503 /503.html;  error_page 504 /504.html;  location = /404.html { root /var/www/nginx-default; }  location = /500.html { root /var/www/nginx-default; }  location = /502.html { root /var/www/nginx-default; }  location = /503.html { root /var/www/nginx-default; }  location = /504.html { root /var/www/nginx-default; } } {% endhighlight %}

Installation des serveurs source

Matériel

Au niveau matériel il s’agit, dans mon cas, de serveurs Dell R510 avec 12 disques de 2 To, 24 Go Ram, interfaces en 10 Gbps optiques. On commence par déployer les packages dont on a besoin :

aptitude -y install nginx-extras bind9 ntp sysfsutils xfsprogs syslog-ng

Les serveurs source se duplique automatiquement les fichiers mis en cache grâce à une réplication en GlusterFS :

wget http://download.gluster.org/pub/gluster/glusterfs/LATEST/Debian/5.0.3/glusterfs_3.3.0-1_amd64.deb
dpkg -i glusterfs_3.3.0-1_amd64.deb

NTP

On modifie simplement les lignes server du fichier /etc/ntp.conf :

server 10.0.A.201
server 10.0.B.201

DNS

Le service ne sera utilisé qu’en local pour la résolution DNS pour les besoins du nginx. Pour cela, on édite le fichier /etc/bind/named.conf/options :

options {
 directory "/var/cache/bind";
 query-source address * port *;
 forwarders { 10.0.A.201; 10.0.B.201; };
 auth-nxdomain no;
 listen-on-v6 { none; };
 listen-on { 127.0.0.1; };
 allow-query { any; };
 allow-recursion { any; };
 version none;
 max-clients-per-query 0;
 clients-per-query 0;
 recursive-clients 10000;
 minimal-responses yes ;
};

OPTIMISATION SYSTEME

On s’attaque au scheduler disque en modifiant le fichier /etc/sysfs.conf :

block/sdb/queue/scheduler = noop

On pense à adapter la partie en gras en fonction de ses disques. Dans la même idée, on modifier le bootloader grub en modifiant /etc/default/grub :

GRUB_CMDLINE_LINUX="elevator=noop quiet"

puis en mettant à jour grub :

update-grub

Le filesystem de l’espace de cache a aussi son importance :

mkfs.xfs -f -l size=128m /dev/sda3

On adapte aussi le fichier /etc/fstab :

/dev/sdb1 /data/cache/ xfs rw,nobarrier,largeio,noatime,nodiratime,logbufs=8,inode64 0 2

On organise ensuite la réplication entre les serveurs source. La réplication GlusterFS n’est à configurer que sur l’un des noeuds :

gluster volume create cache replica 2 transport tcp source1:/data/cache source2:/data/cache
gluster volume start cache
gluster volume set cache performance.write-behind-window-size 131072
gluster volume set cache cluster.self-heal-window-size 1024
gluster volume set cache cluster.lookup-unhashed off
gluster volume set cache performance.flush-behind on
gluster volume set cache nfs.disable on
gluster volume set cache cluster.self-heal-daemon on
gluster volume set cache performance.cache-size 2147483648
gluster volume set cache performance.io-thread-count 64
gluster volume set cache feature.read-only off

Notez qu’on peut répliquer autant que l’on désire. On prépare ensuite le montage du volume répliquer pour travailler dessus (/etc/fstab) :

source1:/cache /data/tmp glusterfs defaults,_netdev 0 0

On peut laisser le nom du premier serveur partout : le client GlusterFS l’utilise juste pour créer la connexion mais utilise le serveur qu’il considère le meilleur (en temps de réponse) pour les accès lecture/écrite. La partie sysctl est importante (/etc/sysctl.conf) :

net.ipv4.conf.default.rp_filter = 0 
net.ipv4.conf.default.arp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.all.arp_filter = 0
net.core.rmem_default = 16777216
net.core.rmem_max = 16777216
net.core.wmem_default = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_mem = 4096 65536 16777216
net.ipv4.tcp_low_latency = 0
net.core.netdev_max_backlog = 30000
fs.file-max = 262144
kernel.shmmax = 16000000000
kernel.shmall = 16000000000
net.ipv4.tcp_abort_on_overflow = 1
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024 65535
vm.min_free_kbytes = 65536
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.eth0.arp_ignore = 1
net.ipv4.conf.eth1.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.eth0.arp_announce = 2
net.ipv4.conf.eth1.arp_announce = 2
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_timestamps = 0 
net.ipv4.tcp_sack = 0
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_keepalive_intvl = 1
net.ipv4.tcp_keepalive_probes = 1 
net.ipv4.ip_forward = 1
net.ipv4.conf.default.proxy_arp = 1
net.ipv4.conf.all.proxy_arp = 1
kernel.sysrq = 1
net.ipv4.conf.default.send_redirects = 1
net.ipv4.conf.all.send_redirects = 1
kernel.core_uses_pid=1
kernel.core_pattern=1
vm.dirty_background_ratio = 20
vm.dirty_ratio = 40
vm.swappiness = 1
vm.dirty_writeback_centisecs = 1500
fs.xfs.xfssyncd_centisecs = 360000
fs.xfs.xfsbufd_centisecs = 3000
net.ipv4.tcp_max_syn_backlog = 65536
net.core.optmem_max = 40960
net.ipv4.tcp_max_tw_buckets = 360000
net.ipv4.tcp_reordering = 5
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_rfc1337 = 0
net.core.somaxconn=262144
net.ipv4.tcp_ecn = 0
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_moderate_rcvbuf = 1

A adapter selon les interfaces réseaux et la Ram (partie en gras). On l’applique avec un simple :

sysctl -p

Il est important d’appliquer les limites correspondantes au système (/etc/security/limits.conf) :

*    soft    nofile    262144
*    hard    nofile    262144

Certaines modifications sont à appliquer au démarrage du système (/etc/rc.local) :

ifconfig eth0 txqueuelen 10000
ifconfig eth1 txqueuelen 10000
ifconfig eth0 mtu 9000
ifconfig eth1 mtu 9000
ethtool -K eth0 rx off tx off
ethtool -K eth1 rx off tx off

CACHING

On s’attaque donc à nginx. D’abord pour la partie globale :

{% highlight conf %} user www-data; worker_processes 2;  worker_rlimit_nofile 2500004;

error_log /var/log/nginx/error.log error; pid /var/run/nginx.pid;

timer_resolution 1ms;

events {  worker_connections 102400;  use epoll;  multi_accept on;  accept_mutex_delay 1ms; }

http {  include /etc/nginx/mime.types;  default_type application/octet-stream;

 log_format access ‘[$time_local] $request_time “$request_method $scheme://$host$request_uri $server_protocol” $request_length $upstream_cache_status $status $proxy_host $upstream_addr $bytes_sent “$http_referer” $remote_addr “$http_user_agent”’;  #access_log /data/log/access.log access; access_log off;  # Client connection  client_body_temp_path /tmp 1 2;  client_header_timeout 5s;  client_body_timeout 5s;  send_timeout 10m;  connection_pool_size 256k;  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=250000 inactive=300s;   reset_timedout_connection on;

 # Compression  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;

 # Network optimizations  output_buffers 1000 128k;  postpone_output 1460;  sendfile on;   sendfile_max_chunk 256k;  tcp_nopush on;  tcp_nodelay on;  server_tokens off;

 # DNS  resolver 127.0.0.1;

 # HTTP Request  ignore_invalid_headers on;  index index.html;  add_header X-CDN “Served by myself”;  add_header Connection close;

 server_names_hash_max_size 5120;  server_names_hash_bucket_size 256;

upstream_fair_shm_size 1024k; # Proxy proxy_cache_path /data/tmp/disk/ levels=1:2 keys_zone=big:1000m max_size=16000G; proxy_temp_path /data/tmp/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;  # Includes  include /etc/nginx/conf.d/.conf;  include /etc/nginx/sites-enabled/; } {% endhighlight %}

Ensuite, voici le template par vhost (au format twig) que votre robot déploiera :

{% highlight conf %}

{{ serial }}

proxy_cache_path /data/tmp/disk/{{ upstream }} levels=1:2 keys_zone={{ upstream }}:1000m inactive=4320m max_size=16000G; upstream {{ upstream }}.nginx_backend { { % raw %} {% for backend in backends %} server {{ backend.name }}{% for key, value in backend.options %} {{ key }}={{ value }}{% endfor %}; {% endfor %} { % endraw %} fair; } server {  listen :80;  server_name {{ frontends|join(‘ ‘) }}; { % raw %}  {% for option in default_options %}   {{- option.name }} {{ option.value }};  {% endfor %}  proxy_no_cache $cookie_nocache $arg_nocache $arg_comment;  proxy_no_cache $http_pragma $http_authorization;  {% for location in locations %}   {% for rule_expert in location.rules_expert %}   # {{ location.name }}   location ~ {{ rule_expert.pattern }} {    {% for option in location.options %}     {{- option.name }} {{ option.value }};    {% endfor %}    {% for header in location.del_headers -%}     proxy_hide_header “{{ header.name }}”;    {% endfor %}    {% for header in location.mod_headers %}     proxy_hide_header “{{ header.name }}”;     add_header “{{ header.name }}” “{{ header.value }}”;    {% endfor %}    proxy_cache {{ upstream }};    proxy_pass http://{{ upstream }}.nginx_backend;   }   {% endfor %}  {% endfor %} { % endraw %}  error_page 404 /404.html;  error_page 500 /500.html;  error_page 502 /502.html;   error_page 503 /503.html;  error_page 504 /504.html;  location = /404.html { root /var/www/nginx-default; }  location = /500.html { root /var/www/nginx-default; }  location = /502.html { root /var/www/nginx-default; }  location = /503.html { root /var/www/nginx-default; }  location = /504.html { root /var/www/nginx-default; } } {% endhighlight %}

Installation des serveurs edge

Matériel

Au niveau matériel il s’agit, dans mon cas, de serveurs avec les matériels suivants :

  • 2 CPU Intel E5620 (la plateforme date un peu)
  • 96 Go Ram
  • 6 x 100 Go en SSD
  • interfaces réseaux optiques 10 Gbps

Ce qui suit est à faire sur l’image PXE ou sur le système local si vous en avez mis un. On commence par déployer les packages dont on a besoin :

aptitude -y install nginx-extras bind9 ntp sysfsutils xfsprogs syslog-ng

NTP

On modifie simplement les lignes server du fichier /etc/ntp.conf :

server 10.0.A.201
server 10.0.B.201

DNS

Le service ne sera utilisé qu’en local pour la résolution DNS pour les besoins du nginx. Pour cela, on édite le fichier /etc/bind/named.conf/options :

options {
 directory "/var/cache/bind";
 query-source address * port *;
 forwarders { 10.0.A.201; 10.0.B.201; };
 auth-nxdomain no;
 listen-on-v6 { none; };
 listen-on { 127.0.0.1; };
 allow-query { any; };
 allow-recursion { any; };
 version none;
 max-clients-per-query 0;
 clients-per-query 0;
 recursive-clients 10000;
 minimal-responses yes ;
};

OPTIMISATION SYSTEME

On s’attaque au scheduler disque en modifiant le fichier /etc/sysfs.conf :

block/sda/queue/scheduler = noop

On pense à adapter la partie en gras en fonction de ses disques. Dans la même idée, on modifier le bootloader grub en modifiant /etc/default/grub :

GRUB_CMDLINE_LINUX="elevator=noop quiet"

puis en mettant à jour* grub* :

update-grub

Le filesystem de l’espace de cache a aussi son importance :

mkfs.xfs -f -l size=128m /dev/sda3

On adapte aussi le fichier /etc/fstab :

/dev/sda3 /data/cache/ xfs rw,nobarrier,largeio,noatime,nodiratime,logbufs=8,inode64 0 2

La partie sysctl est importante (/etc/sysctl.conf) :

net.ipv4.conf.default.rp_filter = 0 
net.ipv4.conf.default.arp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.all.arp_filter = 0
net.core.rmem_default = 16777216
net.core.rmem_max = 16777216
net.core.wmem_default = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_mem = 4096 65536 16777216
net.ipv4.tcp_low_latency = 1
net.core.netdev_max_backlog = 30000
fs.file-max = 262144
kernel.shmmax = 16000000000
kernel.shmall = 16000000000
net.ipv4.tcp_abort_on_overflow = 1
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024 65535
vm.min_free_kbytes = 65536
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.eth0.arp_ignore = 1
net.ipv4.conf.eth1.arp_ignore = 1
net.ipv4.conf.eth2.arp_ignore = 1
net.ipv4.conf.eth3.arp_ignore = 1
net.ipv4.conf.eth4.arp_ignore = 1
net.ipv4.conf.eth5.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.eth0.arp_announce = 2
net.ipv4.conf.eth1.arp_announce = 2
net.ipv4.conf.eth2.arp_announce = 2
net.ipv4.conf.eth3.arp_announce = 2
net.ipv4.conf.eth4.arp_announce = 2
net.ipv4.conf.eth5.arp_announce = 2
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_timestamps = 0 
net.ipv4.tcp_sack = 0
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_keepalive_intvl = 1
net.ipv4.tcp_keepalive_probes = 1 
net.ipv4.ip_forward = 1
net.ipv4.conf.default.proxy_arp = 1
net.ipv4.conf.all.proxy_arp = 1
kernel.sysrq = 1
net.ipv4.conf.default.send_redirects = 1
net.ipv4.conf.all.send_redirects = 1
kernel.core_uses_pid=1
kernel.core_pattern=1
vm.dirty_background_ratio = 20
vm.dirty_ratio = 40
vm.swappiness = 1
vm.dirty_writeback_centisecs = 1500
fs.xfs.xfssyncd_centisecs = 360000
fs.xfs.xfsbufd_centisecs = 3000
net.ipv4.tcp_max_syn_backlog = 65536
net.core.optmem_max = 40960
net.ipv4.tcp_max_tw_buckets = 360000
net.ipv4.tcp_reordering = 5
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_rfc1337 = 0
net.core.somaxconn=262144
net.ipv4.tcp_ecn = 0
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_moderate_rcvbuf = 1

A adapter selon les interfaces réseaux et la Ram (partie en gras). On l’applique avec un simple :

sysctl -p

Il est important d’appliquer les limites correspondantes au système (/etc/security/limits.conf) :

*    soft    nofile    262144
*    hard    nofile    262144

Certaines modifications sont à appliquer au démarrage du système (/etc/rc.local) :

ifconfig eth0 txqueuelen 10000
ifconfig eth1 txqueuelen 10000
ifconfig eth2 txqueuelen 10000
ifconfig eth3 txqueuelen 10000
ifconfig eth4 txqueuelen 10000
ifconfig eth5 txqueuelen 10000
ifconfig eth0 mtu 9000
ifconfig eth1 mtu 9000
ifconfig eth2 mtu 9000
ifconfig eth3 mtu 9000
ifconfig eth4 mtu 9000
ifconfig eth5 mtu 9000
ethtool -K eth0 rx off tx off
ethtool -K eth1 rx off tx off
ethtool -K eth2 rx off tx off
ethtool -K eth3 rx off tx off
ethtool -K eth4 rx off tx off
ethtool -K eth5 rx off tx off

CACHING

Afin d’optimiser le nombre de socket par machine en fonction des ressources, on affecte plusieurs IP en alias à l’interface publique. De même, on pense à rajouter les IP des VIP à la loopback (/etc/network/interfaces).

auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
 address X.X.A.10
 netmask 255.255.255.0
 gateway X.X.A.1
 post-up ifconfig eth0:0 X.X.A.11/32
 post-up ifconfig eth0:1 X.X.A.12/32
 post-up ifconfig eth0:2 X.X.A.13/32
 post-up ifconfig eth0:3 X.X.A.14/32
 post-up ifconfig eth0:4 X.X.A.15/32
 post-up ifconfig eth0:5 X.X.A.16/32
 post-up ifconfig eth0:6 X.X.A.17/32
 post-up ifconfig eth0:7 X.X.A.18/32
 post-up ifconfig eth0:8 X.X.A.19/32
auto lo:0
iface lo:0 inet static 
 address X.X.A.248
 netmask 255.255.255.255
auto lo:1
iface lo:1 inet static
 address X.X.A.249
 netmask 255.255.255.255

Ensuite, on s’attaque à nginx. D’abord pour la partie globale :

{% highlight conf %} user www-data; worker_processes 32;  worker_rlimit_nofile 262144;

error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;

timer_resolution 1ms;

syslog local5 nginx;

events {  worker_connections 262144;  use epoll;  multi_accept on;  accept_mutex_delay 1ms; }

http {  include /etc/nginx/mime.types;  default_type application/octet-stream;

 log_format access ‘[$time_local] $request_time “$request_method $scheme://$host$request_uri $server_protocol” $request_length $upstream_cache_status $status $proxy_host $upstream_addr $bytes_sent “$http_referer” $remote_addr “$http_user_agent”’;  access_log syslog access;

 # Client connection  aio off;  client_body_temp_path /tmp 1 2;  client_header_timeout 5s;  client_body_timeout 5s;  send_timeout 10m;  connection_pool_size 256k;  client_header_buffer_size 16k;  large_client_header_buffers 1024 256k;   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=1000 inactive=3600s;   reset_timedout_connection on;

 # Compression  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;

 # Network optimizations  output_buffers 1000 128k;  postpone_output 1460;  sendfile on;   sendfile_max_chunk 256k;  tcp_nopush on;  tcp_nodelay on;  server_tokens off;

 # DNS  resolver 127.0.0.1;

 # HTTP Request  ignore_invalid_headers on;  index index.html;  add_header X-CDN “Served by myself”;  add_header Connection close;

 # Proxy  proxy_cache_path /data/cache/disk levels=1:2 keys_zone=big:4000m inactive=4320m max_size=500G;  proxy_temp_path /data/cache/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 on;  proxy_ignore_client_abort on;  proxy_connect_timeout 10;  proxy_send_timeout 10;  proxy_read_timeout 10;  proxy_buffer_size 128k;  proxy_buffers 65536 128k;  proxy_busy_buffers_size 256k;  proxy_temp_file_write_size 128k;

 server_names_hash_max_size 5120;  server_names_hash_bucket_size 256;

upstream_fair_shm_size 1024k;

 # Includes  include /etc/nginx/conf.d/.conf;  include /etc/nginx/sites-enabled/; } {% endhighlight %}

Ensuite, voici le template par* vhost* (au format twig) que votre robot déploiera :

{% highlight conf %}

{{ serial }}

proxy_cache_path /data/cache/disk/{{ upstream }} levels=1:2 keys_zone={{ upstream }}:1000m inactive=4320m max_size=400G; upstream {{ upstream }}.nginx_backend {  server X.X.A.101 max_fails=1 fail_timeout=1;   server X.X.B.101 max_fails=1 fail_timeout=1;  fair; } server {  listen :80;  server_name {{ frontends|join(‘ ‘) }}; { % raw %}  {% for option in default_options %}   {{- option.name }} {{ option.value }};  {% endfor %}  proxy_no_cache $cookie_nocache $arg_nocache $arg_comment;  proxy_no_cache $http_pragma $http_authorization;  {% for location in locations %}   {% for rule_expert in location.rules_expert %}   # {{ location.name }}   location ~ {{ rule_expert.pattern }} {    {% for option in location.options %}     {{- option.name }} {{ option.value }};    {% endfor %}    {% for header in location.del_headers %}     proxy_hide_header “{{ header.name }}”;    {% endfor %}    {% for header in location.mod_headers %}     proxy_hide_header “{{ header.name }}”;     add_header “{{ header.name }}” “{{ header.value }}”;    {% endfor %}    proxy_cache {{ upstream }};    proxy_pass http://{{ upstream }}.nginx_backend;   }   {% endfor %}  {% endfor %} { % endraw %}  error_page 404 /404.html;  error_page 500 /500.html;  error_page 502 /502.html;   error_page 503 /503.html;  error_page 504 /504.html;  location = /404.html { root /var/www/nginx-default; }  location = /500.html { root /var/www/nginx-default; }  location = /502.html { root /var/www/nginx-default; }  location = /503.html { root /var/www/nginx-default; }  location = /504.html { root /var/www/nginx-default; } } {% endhighlight %}

SYSLOG

On envoie les logs nginx directement sur le syslog centralisé, en modifiant le fichier /etc/syslog-ng/syslog-ng.conf :

destination d_remote { tcp("10.0.A.202", port(514)); };
filter f_nginx { facility(local5); };
log { source(s_src); filter(f_nginx); destination(d_remote); };

Installation d’un serveur stats

Matériel

Pour la partie matériel, j’ai pris un HP G7 avec des disques 15k rpm mis en RAID 10. Au niveau réseau, les interfaces gigabit suffisent amplement.

Packages

On installe les packages nécessaires :

aptitude -y install bind9 awstats jdresolve syslog-ng geoip-bin groip-database ntp

NTP

On modifie simplement les lignes server du fichier /etc/ntp.conf :

server 10.0.A.201
server 10.0.B.201

DNS

Le service ne sera utilisé qu’en local pour la résolution DNS des IP utilisateurs au moment de l’écriture des logs (ce qui évitera de le faire a posteriori pendant le calcul des statistiques). Pour cela, on édite le fichier /etc/bind/named.conf/options :

options {
 directory "/var/cache/bind";
 query-source address * port *
 forwarders { 10.0.A.201; 10.0.B.201; };
 auth-nxdomain no;
 listen-on-v6 { none; };
 listen-on { 127.0.0.1; };
 allow-query { any; };
 allow-recursion { any; };
 version none;
 max-clients-per-query 0;
 clients-per-query 0;
 recursive-clients 10000;
 minimal-responses yes ;
};

SYSLOG

On centralise donc l’arrivée des logs nginx via syslog-ng. Pour cela, on édite simplement le fichier /etc/syslog-ng/syslog-ng.conf :

source s_net {
 udp(ip(0.0.0.0) port(514));
 tcp(ip(0.0.0.0) port(514) max-connections(512));
};
destination d_nginx { program("/usr/bin/jdresolve -a - | /usr/bin/php /opt/cdn/bin/dispatch.php"); };
log { source(s_net); destination(d_nginx); };

jdresolve est une application java permettant la résolution DNS à la volée. On le fait à ce moment plutôt que sur le nginx pour éviter de rallonger la durée de traitement de la requête HTTP. Idem, il est fait avant le calcul des statistiques pour ne pas rallonger le traitement. En effet, awstats est une véritable limace quand il s’agit de résoudre la partie DNS. Le script dispatch.php permet de dispatcher la ligne dans un fichier spécifique à son vhost.

foreach (explode("\\n", file_get_contents('php://stdin')) as $log) {
 if (empty($log)) continue;
 if (preg_match('%.*".* http://([^/]*)/.* .*".* ([^\\s]+).nginx_backend.*".*" .* ".*"%i', $log, $match) === FALSE) {
  die("Invalid log format on line $id.");
 }
 if (isset($match[1])) {
  $dir = '/mnt/nfs/log/'.date('Y/m/d');
  @mkdir($dir, 0777, true);
  $match[0] = substr($match[0], strpos($match[0], '['));
  file_put_contents($dir.'/'.$match[2].'.log', $match[0]."\\n", FILE_APPEND);
 } else if (isset($match[0]) && sizeof($match[0])) {
  file_put_contents("/tmp/trash.log", $match[0]."\\n", FILE_APPEND);
 }
}

Pensez à modifier le chemin de dépôt pour les logs (en gras).

Statistiques

La partie des statistiques repose sur awstats. Un script permet de collecter les informations en base pour chaque vhost et la génération en dynamique de sa configuration en cas de besoin. Je ne vais pas pouvoir vous fournir le script. Cependant, voici le modèle pour les fichiers de configuration d’awstats.

LogFile="###LOG###"
LogType=W
LogFormat = "%time1 %extra1 %methodurl %extra2 %extra3 %code %extra4 %extra5 %bytesd %refererquot %host %uaquot"
LogSeparator=" "
SiteDomain="###VHOST###"
HostAliases="###FQDN###"
DNSLookup=0
DirData="/var/lib/awstats"
DirCgi="/cgi-bin"
DirIcons="/awstats-icon"
AllowToUpdateStatsFromBrowser=0
AllowFullYearView=2
EnableLockForUpdate=0
DNSStaticCacheFile="dnscache.txt"
DNSLastUpdateCacheFile="dnscachelastupdate.txt"
SkipDNSLookupFor=""
AllowAccessFromWebToAuthenticatedUsersOnly=0
AllowAccessFromWebToFollowingAuthenticatedUsers=""
AllowAccessFromWebToFollowingIPAddresses=""
CreateDirDataIfNotExists=0
BuildHistoryFormat=text
BuildReportFormat=html
SaveDatabaseFilesWithPermissionsForEveryone=0
PurgeLogFile=0
ArchiveLogRecords=0
KeepBackupOfHistoricFiles=0
DefaultFile="index.html"
SkipHosts="REGEX[^X\\.X\\.A\\.] REGEX[^X\\.X\\.B\\.]"
SkipUserAgents=""
SkipFiles=""
SkipReferrersBlackList=""
OnlyHosts=""
OnlyUserAgents=""
OnlyFiles=""
NotPageList=""
ValidHTTPCodes="200 206 300 301 302 303 304 305 307"
ValidSMTPCodes="1 250"
AuthenticatedUsersNotCaseSensitive=0
URLNotCaseSensitive=0
URLWithAnchor=0
URLQuerySeparators="?;"
URLWithQuery=0
URLWithQueryWithOnlyFollowingParameters=""
URLWithQueryWithoutFollowingParameters=""
URLReferrerWithQuery=0
WarningMessages=1
ErrorMessages=""
DebugMessages=0
NbOfLinesForCorruptedLog=50
WrapperScript=""
DecodeUA=0
MiscTrackerUrl="/js/awstats_misc_tracker.js"
LevelForBrowsersDetection=2 # 0 disables Browsers detection.
 # 2 reduces AWStats speed by 2%
 # allphones reduces AWStats speed by 5%
LevelForOSDetection=2 # 0 disables OS detection.
 # 2 reduces AWStats speed by 3%
LevelForRefererAnalyze=2 # 0 disables Origin detection.
 # 2 reduces AWStats speed by 14%
LevelForRobotsDetection=2 # 0 disables Robots detection.
 # 2 reduces AWStats speed by 2.5%
LevelForSearchEnginesDetection=2 # 0 disables Search engines detection.
 # 2 reduces AWStats speed by 9%
LevelForKeywordsDetection=2 # 0 disables Keyphrases/Keywords detection.
 # 2 reduces AWStats speed by 1%
LevelForFileTypesDetection=2 # 0 disables File types detection.
 # 2 reduces AWStats speed by 1%
LevelForWormsDetection=0 # 0 disables Worms detection.
 # 2 reduces AWStats speed by 15%
UseFramesWhenCGI=1
DetailedReportsOnNewWindows=1
Expires=0
MaxRowsInHTMLOutput=1000
Lang="fr"
DirLang="/usr/share/awstats/lang"
ShowMenu=1 
ShowSummary=UVPHB
ShowMonthStats=UVPHB
ShowDaysOfMonthStats=VPHB
ShowDaysOfWeekStats=PHB
ShowHoursStats=PHB
ShowDomainsStats=PHB
ShowHostsStats=PHBL
ShowAuthenticatedUsers=0
ShowRobotsStats=HBL
ShowWormsStats=0
ShowEMailSenders=0
ShowEMailReceivers=0
ShowSessionsStats=1
ShowPagesStats=PBEX
ShowFileTypesStats=HB
ShowFileSizesStats=0 
ShowOSStats=1
ShowBrowsersStats=1
ShowScreenSizeStats=0
ShowOriginStats=PH
ShowKeyphrasesStats=0
ShowKeywordsStats=0
ShowMiscStats=0
ShowHTTPErrorsStats=1
ShowSMTPErrorsStats=0
ShowClusterStats=0
AddDataArrayMonthStats=1
AddDataArrayShowDaysOfMonthStats=1
AddDataArrayShowDaysOfWeekStats=1
AddDataArrayShowHoursStats=1
IncludeInternalLinksInOriginSection=0
MaxNbOfDomain = 10
MinHitDomain = 1
MaxNbOfHostsShown = 10
MinHitHost = 1
MaxNbOfLoginShown = 10
MinHitLogin = 1
MaxNbOfRobotShown = 10
MinHitRobot = 1
MaxNbOfPageShown = 10
MinHitFile = 1
MaxNbOfOsShown = 10
MinHitOs = 1
MaxNbOfBrowsersShown = 10
MinHitBrowser = 1
MaxNbOfScreenSizesShown = 5
MinHitScreenSize = 1
MaxNbOfWindowSizesShown = 5
MinHitWindowSize = 1
MaxNbOfRefererShown = 10
MinHitRefer = 1
MaxNbOfKeyphrasesShown = 10
MinHitKeyphrase = 1
MaxNbOfKeywordsShown = 10
MinHitKeyword = 1
MaxNbOfEMailsShown = 20
MinHitEMail = 1
FirstDayOfWeek=1
ShowFlagLinks=""
ShowLinksOnUrl=1
UseHTTPSLinkForUrl=""
MaxLengthOfShownURL=64
HTMLHeadSection=""
HTMLEndSection=""
BarWidth = 260
BarHeight = 90
StyleSheet=""
color_Background="FFFFFF" # Background color for main page (Default = "FFFFFF")
color_TableBGTitle="CCCCDD" # Background color for table title (Default = "CCCCDD")
color_TableTitle="000000" # Table title font color (Default = "000000")
color_TableBG="CCCCDD" # Background color for table (Default = "CCCCDD")
color_TableRowTitle="FFFFFF" # Table row title font color (Default = "FFFFFF")
color_TableBGRowTitle="ECECEC" # Background color for row title (Default = "ECECEC")
color_TableBorder="ECECEC" # Table border color (Default = "ECECEC")
color_text="000000" # Color of text (Default = "000000")
color_textpercent="606060" # Color of text for percent values (Default = "606060")
color_titletext="000000" # Color of text title within colored Title Rows (Default = "000000")
color_weekend="EAEAEA" # Color for week-end days (Default = "EAEAEA")
color_link="0011BB" # Color of HTML links (Default = "0011BB")
color_hover="605040" # Color of HTML on-mouseover links (Default = "605040") 
color_u="FFAA66" # Background color for number of unique visitors (Default = "FFAA66")
color_v="F4F090" # Background color for number of visites (Default = "F4F090")
color_p="4477DD" # Background color for number of pages (Default = "4477DD")
color_h="66DDEE" # Background color for number of hits (Default = "66DDEE")
color_k="2EA495" # Background color for number of bytes (Default = "2EA495")
color_s="8888DD" # Background color for number of search (Default = "8888DD")
color_e="CEC2E8" # Background color for number of entry pages (Default = "CEC2E8")
color_x="C1B2E2" # Background color for number of exit pages (Default = "C1B2E2")
#LoadPlugin="hashfiles"
#LoadPlugin="geoip GEOIP_STANDARD /usr/share/GeoIP/GeoIP.dat"
ExtraTrackedRowsLimit=500
ExtraSectionName1="Hit cache status"
ExtraSectionCodeFilter1="" 
ExtraSectionCondition1=""
ExtraSectionFirstColumnTitle1="Cache Status"
ExtraSectionFirstColumnValues1="extra3,(.*)"
ExtraSectionFirstColumnFormat1="%s" 
ExtraSectionStatTypes1=H 
ExtraSectionAddAverageRow1=0 
ExtraSectionAddSumRow1=1 
MaxNbOfExtra1=100 
MinHitExtra1=1

Il est intéressant que le script gérant ces statistiques précalcule aussi les pages statiques HTML pour éviter des chargements trop long lors des consultations.

Installation d’un serveur master

Materiel

Au niveau matériel, on recycle ce qu’on a. Il n’y a pas besoin de performances réelles sauf pour la base de données donc le choix est libre.

Packages

On installe les packages principaux :

aptitude -y install ldirectord ipvsadm bind9 ntp

Pour la partie backoffice et outils de cron développés en interne, je vous laisse le choix. Je ne peux pas vous fournir mon code pour le moment. Pour autant, une bonne partie de la magie se passe là concernant le déploiement automatique, la customisation par site et le monitoring.

DNS

Le serveur DNS serveur de forwarders aux autres serveurs DNS. Lui utilisera ceux d’OpenDNS tant qu’à faire. Pour cela, on édite le fichier /etc/bind/named.conf/options :

options {
 directory "/var/cache/bind";
 query-source address * port *;
 forwarders { 208.67.222.222; 208.67.220.220; };
 auth-nxdomain no;
 listen-on-v6 { none; };
 listen-on { 127.0.0.1; 10.0.A.201; };
 allow-transfer { 127.0.0.1; 10.0.0.0/8; };
 allow-query { any; };
 allow-recursion { any; };
 version none;
 max-clients-per-query 0;
 clients-per-query 0;
 recursive-clients 10000;
 minimal-responses yes ;
};

Il servira aussi la zone DNS dédiée au CDN. On préconisera au client (même si l’on perd un peu en performance) de faire des CNAME vers ces enregistrement. Cela évitera d’avoir à le prévenir si l’on fait des changements. Si les changements ne sont pas prévu, que l’on gère les zones clientes en interne, ou que l’on préfère les performances, on se refusera de faire des CNAME.

Load-balancing

On édite le fichier /etc/network/interfaces et on rajoute à l’interface publique (eth0 ici) :

post-up ifconfig eth0:0 X.X.A.248/32
post-up ifconfig eth0:0 X.X.A.249/32

On utilise donc ldirector/ipvsadm. Dans /etc/default/ldirectord on définit le chemin de la configuration :

CONFIG_FILE=/etc/ldirectord.conf

Puis, on modifie le fichier correspondant :

checktimeout=2
negotiatetimeout=2
checkinterval=10
autoreload=yes
logfile="l0"
quiescent=yes
emailalert=support@domaine.tld
virtual=X.X.A.248:http
 real=X.X.A.10:http gate
 real=X.X.A.11:http gate
 real=X.X.A.12:http gate
 real=X.X.A.13:http gate
 real=X.X.A.14:http gate
 real=X.X.A.15:http gate
 real=X.X.A.16:http gate
 real=X.X.A.17:http gate
 real=X.X.A.18:http gate
 real=X.X.A.19:http gate
 real=X.X.A.20:http gate
 real=X.X.A.21:http gate
 real=X.X.A.22:http gate
 real=X.X.A.23:http gate
 real=X.X.A.24:http gate
 real=X.X.A.25:http gate
 real=X.X.A.26:http gate
 real=X.X.A.27:http gate
 real=X.X.A.28:http gate
 real=X.X.A.29:http gate
 service=http
 scheduler=sed
 protocol=connect
 persistent=5
virtual=X.X.A.249:http
 real=X.X.A.10:http gate
 real=X.X.A.11:http gate
 real=X.X.A.12:http gate
 real=X.X.A.13:http gate
 real=X.X.A.14:http gate
 real=X.X.A.15:http gate
 real=X.X.A.16:http gate
 real=X.X.A.17:http gate
 real=X.X.A.18:http gate
 real=X.X.A.19:http gate
 real=X.X.A.20:http gate
 real=X.X.A.21:http gate
 real=X.X.A.22:http gate
 real=X.X.A.23:http gate
 real=X.X.A.24:http gate
 real=X.X.A.25:http gate
 real=X.X.A.26:http gate
 real=X.X.A.27:http gate
 real=X.X.A.28:http gate
 real=X.X.A.29:http gate
 service=http
 scheduler=sed
 protocol=connect
 persistent=5

Pour information sur cette configuration :

  • gate permet le fonctionnement en DSR du load balancing
  • persistent définit la durée du sticky pour la session (ici 5 secondes)
  • autoreload déclenche le rechargement de la configuration à chaque modification du fichier
  • emailalert nous préviendra par email de tout changement d’état dans la VIP

Plus qu’à lancer relancer l’interface réseau puis le service et voilà la VIP prête.

NTP

On veut que tous les serveurs soit synchro niveau temps. Autant avoir un serveur de référence à proximité. On a donc installé le package. Aucune modification de base à faire.

Vus : 3210
Publié par Francois Aichelbaum : 171