La cuisine de Gandi

Accueil > Hébergement > 10 ans de Gandi: retour d'expérience

10 ans de Gandi: retour d'expérience

À l'occasion des dix ans de Gandi, l'idée un peu folle de donner, sur dix jours, 55000 domaines, a posé une question pratique. Comment, en ouvrant les vannes d'une telle opération, maintenir une qualité irréprochable sur le site ? L'ambiance festive aurait aussi bien pu se transformer en cauchemar pour nos clients si ils avaient été privés d'accès à leurs interfaces de gestion.

La décision fût donc prise d'héberger l'événement sur un site dédié. Occasion rêvée pour se mettre un peu à la place de nos clients, et utiliser notre infrastructure d'hébergement. Règles du jeu : on utilise exclusivement les outils fournis au client, on prépare une archi qui monte en charge facilement, ça ne doit pas coûter un bras, ça peut illustrer notre flexibilité légendaire.


Keep it simple, stupid

De la décision à l'implémentation, on avait une semaine. L'idée charmante de monter un site "cloudish" à base de technos modernes, et peu maîtrisées, est donc oubliée d'office. Pour être honnête, vu les délais, le développeur désigné d'office se voit choisir la techno : "là où t'es à l'aise, tu as une semaine". Ça sera PHP/mysql, ce qui n'est pas du goût de tout le monde ! Mais permettra de sortir le site testé et dans les temps.

Pour tenir une charge correcte, plusieurs serveurs seront nécessaires.  Premier rappel de nos imperfections, Gandi ne fournit pas de load balancing ! Qu'importe, on ira donc sur un round robin DNS, à petit TTL pour pouvoir sortir facilement un frontal cassé de la production.

Notre distribution pour l'occasion, sera une ubuntu 9.10 - parce qu'elle est assez à jour, et qu'elle utilise un kernel 2.6.27.


Multiplier les petits serveurs

La meilleur façon de monter en charge sur notre infrastructure est d'isoler fonctionnellement l'architecture en nombreux petits serveurs. Là où nous assurons un minimum de "scalabilité" verticale (vous pouvez augmenter dynamiquement la RAM, le CPU alloués à un serveur), c'est à l'architecture de prévoir la scalabilité "horizontale".

Ainsi, on peut facilement monter les ressources là où les bottlenecks se font sentir, et ajouter/ou déplacer des parts d'un serveur vers l'autre lorsque la puissance allouée le demande. Les avantages des "nombreux petits serveurs" sont multiples :

  • chaque serveur bénéficie au minimum d'un core CPU en burst (oui, un core entier, même sur une part : c'est nouveau)
  • la redondance est assurée, et vos parts s'étalent un peu aléatoirement sur des centaine de serveurs physiques
  • particulièrement en environnement virtualisé, la performance mémoire est meilleure sous 1G de RAM
  • si vous avez 4 serveurs d'une part, au lieu d'un gros de 4 parts, ils montent à 8x4 parts dynamiquement, ou 24x4 parts en un reboot, tout ça sans toucher à l'archi - là ou le gros s'arrête à 8 sans reboot, ou 24
  • les ressources sont faciles à déplacer vers les serveurs qui en ont besoin.

Nous partons sur l'infrastructure simpliste :

  • 24 (!) serveurs d'une part, pour gérer le web et PHP : 10 en anglais, 10 en français, et 2 + 2 pour l'IPv6
  • 2 serveurs de 4 parts, des memcached répliqués pour soulager la DB et gérer les sessions
  • 1 serveur mysql de 4 parts, qui contiendra les coupons pré-générés (ils tiennent en RAM, la base doit théoriquement s'ennuyer)

Après quelques belles surcharges et un bout de code revu, la base de donnée sera finalement épargnée au maximum par memcached (voir chapitre « un code léger… »)

Soit 36 parts, pour une durée de dix jours : environ 140 euros.

C'est un admin qui s'usera les doigts sur notre site pour créer, et configurer les 24 serveurs en parallèle. Visiblement, la sortie de l'API hosting ou une fonction dans l'interface web seraient les bienvenus. (Merci à cssh pour l'occasion).


Sécuriser (un peu) les machines

Une distribution par défaut mérite toujours quelques retouches. Exporter mysql sur le réseau « public » du hosting gandi nous ennuyait un peu. À grands coups de netstat, fermer les services inutiles qui écoutent sur un port public. Avec l'aide de tcpwrappers (hosts.allow, hosts.deny), on ferme toutes les interfaces « privées » (sshd, mysql réservé aux machines frontales du web).

Restera à bien faire attention au code php et à nos queries mysql : le meilleur moyen d'éviter les injections est encore de binder tous les params après un prepare(). En plus, ça décharge la DB lorsqu'on effectue plusieurs execute.

Un détail important : notre site autorisant l'envoi de mail à un destinataire « arbitraire », il était critique de limiter au maximum son exploitation potentielle par un spammer malin : restrictions du nombre d'envoi par coupon, au minimum, et monitoring.


Prévoir l'environnement de dev et le déploiement

Partager les données entre les sites, c'est ajouter un "single point of failure" et un point de contention à l'archi. Nous optons donc pour un déploiement "local" à chaque serveur du contenu du site. Un site de développement et de "staging" sur une VM sert à tester et développer les mises à jour. Un script et quelques rsync permettent de déployer l'ensemble sur tous les frontaux de l'architecture. On a dit simple !


Surveiller ses ressources

Quelques minutes avant l'opération, pour prévenir plutôt que guérir, la totalité des machines virtuelles sont augmentées d'une à deux parts. En utilisant l'interface des stats, dès le premier jour, on peut constater l'ennui total qui habitait les machines virtuelles:

Cpu sur un front FR Interface réseau sur un front FR

Il aurait été malin, à ce moment précis, de revenir à une part par machine. Ou d'exploiter gandi "autoflex", ou encore, vu le type de charge observée, de préparer des flexs programmés pour les heures de lâcher des coupons ! Malheureusement, beaucoup de monde sur beaucoup de ponts, on a raté l'occasion de vous faire cette belle démonstration (écono-techno-(éco ?)logique).


Un code léger vaut mieux que mille CPU costauds

Même si nous avions à notre disposition plusieurs milliers de CPU et beaucoup de teras de RAM, mardi fut suffisamment chaotique pour qu'on en reparle ici : après un lundi très bien tenu en charge, le plan d'exécution de notre unique SELECT COUNT changea brutalement et devint terriblement plus lent (300ms). Avec foi, nous avions pensé que ce query « unique », sur une table présente complètement en mémoire, ne poserait pas de problème : il était donc exécuté sur toutes les pages du site. La concurrence d'accès avec les UPDATEs des coupons finit par avoir raison de la base qui, malgré des stats systèmes proches de l'idle, entra discrètement en contention de lock.

La réaction habituelle est d'ajouter des parts pour monter en charge. C'est une solution temporaire acceptable, mais ça ne suffit pas !

Un analyse système, une remise en question du code, et une utilisation salvatrice de memcached permit de retrouver des performances acceptables. Une modification des queries utilisés aurait peut-être pu faire l'affaire également.

Une moralité à cet épisode: le code, les indexes, l'architecture, (…) sont les garants de votre montée en charge, et si ils sont « cpu friendly » vous épargneront, sinon une catastrophe, au moins un achat de parts inutiles.

En plus, comme on dit ici en plaisantant, c'est écolo.


Quelques chiffres


  • 36 parts, mais on aurait pu tenir sur moins (snif)
  • 5% de CPU en peak
  • 4000 requêtes par frontal dans la première minute de chaque heure de pointe (environ 1400 req/seconde au total)
  • un minimum de 11 secondes pour donner 1000 coupons
  • pour le même nombre de coupons, un maximum de 40 minutes, pendant l'incident.