quentin.dufour.io/_posts/2024-02-06-fosdem.md
Quentin Dufour 352f692239
Some checks failed
continuous-integration/drone/push Build is failing
Reword
2024-02-07 11:46:10 +01:00

29 KiB

layout title date status sitemap category description
post Réflexions suite au FOSDEM 2024 2024-02-06 published true divers Notes du FOSDEM 2024

Le 3 et 4 février 2024 j'étais à Bruxelles pour le FOSDEM, une conférence pour les développeur-euses de logiciel libre. Ouverte à tout le monde, la conférence est monstrueuse : plus de 800 présentations, sur plus de 80 tracks, avec plusieurs dizaines de milliers de participant-es.

Cette année je présentais Aerogramme, un serveur IMAP développé au sein de Deuxfleurs. La présentation était axée sur les fonctionnalités de robustesse aux pannes dudit logiciel. Vous pouvez retrouver toutes les informations sur cette présentation sur la page dédiée de la conférence, dont une captation vidéo : [Servers] Aerogramme, a multi-region IMAP server. Plutôt que de vous présenter ce que j'ai vu, je vais plutôt me focaliser sur les réflexions que ça a généré.

Je commence par les emails, mais le gros du billet est autour de la conception de système d'exploitation distribués.

Emails

Ça faisait 10 ans qu'il n'y avait pas eu de track email au FOSDEM. D'où le nom "modern email" cette année, comme pour lancer un nouvel élan. C'était une expérimentation qui a été très fructueuse : les développeur-euses ont répondu présent.

La journée a commencé par la dimension "protocole" des emails : pourquoi il faut abandonner STARTTLS, les subtilités d'IMAP, et l'intégration d'Unicode dans les emails. Globalement, c'est très convaincant qu'il ne faut vraiment pas utiliser STARTTLS, et encore moins l'implémenter, c'est un nid à problème. Rien de nouveau lors de la présentation sur IMAP, deux points qui m'ont bien plu : on peut pas lexer IMAP, knowledge is disappearing et la subtilité des literals dans IMAP.

C'est emersion (Simon) qui a d'abord essayé d'écrire un lexer+parser pour IMAP, et ça ne marche pas. Les lexer+parser, c'est vraiment la façon élégante, qu'on t'apprend quand tu fais de la théorie des langages, mais ça oblige de concevoir ton langage avec des contraintes, pour pas qu'il soit ambigu. IMAP a été conçu de façon très organique, et ne répond pas à ces critères. Du coup c'est moins élégant, moins efficace, mais on utilise un parser combinator. D'ailleurs j'en étais venu à la même conclusion pour les emails (c'est à dire IMF/MIME).

En effet les savoirs disparaissent. On a pu voir ça sur Cobol, on a le cas pour les emails. Ça vient heurter des croyances, du progrès linéaire sur tous les aspects, y compris sur les savoirs. Cette critique n'est pas nouvelle, mais elle est toujours bonne à rappeler : les connaissances, le savoir, ça s'entretient.

Ensuite, dans IMAP, il y a cette idée qu'il y a besoin de coordination entre client et serveur. C'est à dire qu'en tant que client, j'ai besoin de surveiller ce que le serveur m'envoie, pour savoir dans quel état je suis. Et donc un des exemples, c'est la gestion des literals.

Et puis sur la question d'unicode, le speaker était fier de dire que c'était la première RFC qui simplifiait les protocoles plutôt que de les complexifier. En effet, plein de trucs qui étaient compliqués car demandait des encodings spéciaux, disparaissent dès lors que tu supportes UTF-8 : tu annonces ton support, et tu envoies de l'UTF-8 brut. Le présentateur revenait souvent sur cette importance de la simplification : plutôt que d'essayer de gérer les erreurs silencieusement, de réécrire les messages, etc. il a été choisi de simplement refuser l'envoi si un email UTF-8 tentait d'être délivré à un serveur qui ne le supporte pas, en vu de simplifier le debug, et avec l'idée que c'est toujours plus simple d'implémenter ce support que de coder des workarounds.

S'en est suivi un ensemble de présentation sur JMAP, d'abord par Fastmail, qui sont ceux qui l'ont proposé et le poussent. Ils expliquent à quel point le protocol est formidable comparé à IMAP. Mais aujourd'hui, il n'y a aucun client de référence pour pousser l'adoption de JMAP. Fastmail a bien un très bon client, mais ne veulent pas le rendre open source / libre. Soit disant JMAP est simple, mais en réalité en terme de fonctionnalités, il correspond à un IMAP avec un très grand nombre d'extensions : je ne pense donc pas tenter d'implémentation avant d'avoir toutes les extensions dans IMAP. Et une fois ces extensions dans IMAP, la différence en terme d'expérience n'est probablement pas si grande. Pire, ils se comparent toujours à IMAP, mais leur vrai concurrent c'est EAS (Exchange Active Sync), le protocole de Microsoft, présent dans tous les iPhone et Android depuis le début. Lui aussi est basé sur HTTPS, lui aussi supporte d'un seul tenant réception + envoi d'email, calendrier + contact. Sauf que lui possède plein d'excellents clients et des intégrations natives partout.

Il y a cependant quelques travaux sur JMAP. D'abord le client Android ltt.rs qui semble faire référence, des gens aussi qui développent des bibliothèques pour porter d'anciens protocoles vers JMAP, et puis Apache James semble supporter JMAP également. D'ailleurs Apache James, pur produit de l'écosystème Java, semble être un acteur assez sérieux dans le monde de l'email, je ne serais pas surpris qu'on en entende d'avantage parler dans les années à venir. Linagora semble avoir des déploiements importants, particulièrement dans le système de santé français. Impressionant. Pour revenir à JMAP, je pense donc définitivement qu'il va falloir à un moment qu'il se positionne face à Exchange Active Sync, d'ailleurs il y avait une présentation de Grommunio sur une intégration complète d'Exchange dans leur groupware C++ / PHP, qui démontrait bien encore la pertinence de EAS. Si JMAP a bien un argument en sa faveur, c'est l'absence de brevets logiciels, brevets qui plannent au dessus de Exchange, mais brevets logiciels qui ne semblent pas avoir droit de cité en France, d'où VLC d'ailleurs...

Je n'ai pas assisté à toutes les présentations de la room email, je pense potentiellement les rattraper en VOD plus tard. Pas de révélations sensationnelles, content de voir que les emails intéressent des gens, je pense toujours que EAS est plus pertinent que JMAP, et qu'une bonne implémentation d'IMAP c'est un bon point de départ...

Système

La session sur les microkernel+unikernel commençait par un positioning talk d'Alex. Alex s'intéresse à l'infrastructure logiciel de Deuxfleurs, et ce talk avait entre autre pour objectif de collecter des retours, des idées, et stimuler les discussions pour dépasser des problèmes qu'on rencontre. Nous y voilà donc...

Le corps de sa proposition, selon moi, c'est qu'il note une inadéquation entre, d'une part, le modèle mental qu'on se fait des différents composants de notre infrastructure, et d'autre part, les interfaces qui sont à nos dispositions qui ne sont pas adaptées.

L'enjeu est donc de définir les concepts mobilisés par notre modèle mental, et les problèmes que nous posent les interfaces actuelles.

Alex note que notre modèle ressemble beaucoup au design des microkernels : des processus indépendants qui communiquent entre eux via des IPC (InterProcessus Communication).

Selon moi, on peut très bien envisager aussi notre modèle comme le modèle d'acteur d'Erlang aussi. Et aussi l'écosystème microservice actuel. En fait, chaque approche va focaliser sur un aspect différent du problème. L'approche microkernel ne s'est pas intéressée au réseau, mais elle a beaucoup travaillé la question des permissions et de la sécurité à travers ce système de processus échangeant par messages. Je pense par exemple à l'object-capability model de sel4. Elle a essaimé aussi dans des OS traditionnels, je pense par exemple à Capsicum dans FreeBSD. L'approche d'Erlang s'intéresse quant à elle au réseau et à la robustesse : elle est nativement distribuée et les processus sont sans état. Quant à la robustesse, Erlang a toute une réflexion sur "laisser les processus crasher plutôt que de récupérer les erreurs", avec un système de surveillance et redémarrage, dont les vertus sont expliqués dans The Zen of Erlang. Enfin l'écosystème microservice s'est intéressée au chemin de migration, comment graduellement aller vers ce modèle processus/messages, comment l'intégrer à des workflow de développement.

Sur la question des problèmes des interfaces actuelles, Alex note qu'il y a conflit pour accéder à des ressources partagées. Par exemple, le système, le daemon docker, et d'autres daemons se "battent" pour accéder à netfilter. Bien sûr, ils ont chacun leur table, mais un rechargement complet de ce dernier peut arriver, et c'est là qu'on voit que la gestion réseau n'a pas été pensée pour plusieurs processus. De la même manière, on a ce problème pour le système de fichier : par défaut, et malgré les permissions, c'est un seul espace de nom qui est partagé avec tout le monde. Et puis enfin, les interfaces sont conçues pour la façon de faire de l'ordinateur des années 1980 : un seul ordinateur, avec un seul processeur, des bandes magnétiques pour le stockage, etc. Tout ça n'est, d'une part, pas forcément la meilleure façon d'utiliser le matériel actuellement, et d'autre part, empêche de considérer certains usages à travers le réseau. Typiquement, la norme POSIX, d'ailleurs sur certains aspects très dure à implémenter et à utiliser correctement, a tendance a sédimenter l'écosystème.

De cette inadéquation "modèle mental" et "interfaces disponibles", j'en tire deux problèmes : 1) un mauvais usage des ressources à disposition surtout dans le cas d'un cluster, et 2) des problèmes de correctness, c'est à dire que c'est très difficile de raisonner correctement sur un système, et donc de ne pas avoir de bugs.

Alex propose donc une architecture qui soit composée d'un orchestrateur microkernel qui schedulerait seulement des machines virtuelles, d'abord des machines Linux embarquant un seul processus pour la rétro-compatibilité avec un schéma Docker, et pour le futur, des machines virtuelles unikernel, où le logiciel étant mélangé au système d'exploitation, seul les fonctionnalités du système d'exploitation utilisée par le logiciel sont embarquées. Chaque processus (linux VM ou unikernel) communiqueraient ensemble alors via des API bas niveaux comme de l'IP ou le protocole VirtIO du noyau Linux. Je pense qu'une idée très forte qu'on trouve dans la présentation de Alex, c'est l'isolation des ressources : c'est la critique qu'il fait aux conteneur Linux, de mal isoler. Et je pense que le choix d'un hyperviseur avec des machines virtuelles est motivée dans cette optique d'isolation des ressources.

La question de l'isolation des ressources est également abordée par Lennart Poettering dans son billet systemd for Administrators, Part XVIII :

An important facet of modern computing is resource management: if you run more than one program on a single machine you want to assign the available resources to them enforcing particular policies. This is particularly crucial [...] for large installations such as cloud setups, where resources are plenty, but the number of programs/services/containers on a single node is drastically higher.

Traditionally, on Linux only one policy was really available: all processes got about the same CPU time, or IO bandwith, modulated a bit via the process nice value. This approach is very simple and covered the various uses for Linux quite well for a long time. However, it has drawbacks: not all all processes deserve to be even, and services involving lots of processes (think: Apache with a lot of CGI workers) this way would get more resources than services whith very few (think: syslog).

Je pense que c'est important de mentionner ça, parce que dans un monde où on est matrixé par la sécurité, l'isolation des ressources est aussi très désirable en terme de stabilité des performances et pour éviter des bugs.

Alex conclut son talk en se demande comment mobiliser l'existant pour aboutir vers une infrastructures dont les interfaces seraient plus en adéquation avec notre modèle mental, que ce soit du côté des microkernels, des entrées/sorties, ou encore des frameworks & OS existants.

Lors de la session question/réponse, il y a une intervention qui m'a bien plus : "comment pensez-vous gérer la network transparency?". Alex a répondu que toutes nos applications communiquent déjà à travers le réseau.

J'ai le sentiment que la question n'était pas exactement là, mais plus comment on connecte des processus entre eux, bref, une question de bus, de service discovery, de service mesh. Par exemple dans la documentation de Erlang, on peut lire :

A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other. Each such runtime system is called a node. Message passing between processes at different nodes, as well as links and monitors, are transparent when pids are used. Registered names, however, are local to each node. This means that the node must be specified as well when sending messages, and so on, using registered names. The distribution mechanism is implemented using TCP/IP sockets. How to implement an alternative carrier is described in the ERTS User's Guide.

Il me semble important aussi de lire les réflexions qui ont eu lieu dans le monde du microservice : que ce soit istio, Consul Connect, ou le Dapr de Microsoft. Actuellement, Deuxfleurs utilise un modèle basé sur le service discovery via DNS. Il a plusieurs faiblesses : pas de contrôle de permission, pas de gestion correcte des crashs - les services ne refont pas toujours de résolution quand un service crash et gardent une IP obsolète, pas de gestion du chiffrement, etc. L'article Microservices: The Journey So Far and Challenges Ahead remet bien ces problèmes et explique pourquoi l'industrie s'est déplacée vers les Service Mesh. Et puis, il me semble important d'aller creuser un peu plus loin aussi : du côté de dbus, du côté de Greybus, ou même du côté de Binder dans Android. Et puis de pousser encore un peu plus loin, et d'aller voir les Enterprise Service Bus et les Message Brokers. Ainsi on couvre - à ma connaissance - l'ensemble des approches pour échanger des messages.

Discussion

Voilà, c'est la fin de mes notes commentées du talk, maintenant j'arrête d'écrire en italique pour donner mon avis. Je trouve le sujet d'interroger notre modèle mental au regard des interfaces utilisés très pertinent, je pense que penser l'isolation des ressources à travers un système de processus qui communiquent entre eux est très fécond.En fait ça vient construire toute une réflexion sur 1) comment on gère ces processus et 2) comment on gère ces communications. Et quant on regarde ce qu'on fait à travers cette analyse, ça sent toujours la bidouille, on est toujours en train de "lutter contre le système".

Par contre, pour moi la solution n'est pas "un microkernel", un "unikernel" ou un "hyperviseur". Aujourd'hui, quand on pense aux microkernels, on pense surtout à isoler les drivers, des composants bas niveau du système, etc. Soit des choses aujourd'hui qui ne nous posent pas de problème : Linux juste marche dans notre cas. N'en faisons pas un faux problème juste parce qu'on trouve son design peu élégant : il compense ça par une énorme communauté de gens très compétents et des processus de développements qui fonctionnent. De toute manière, quand il s'agit de tourner sur des vrais ordinateurs, l'enjeu ce sont les drivers, et ils n'existent pas sur les microkernels actuels. Enfin, c'est un monstre d'I/O et de performances, avec des gens qui ont étudié ses différents schedulers (processus, I/O, etc), qui a de nombreux outils de debug, d'observation, qui n'ont pas d'égale ailleurs.

Selon moi, dans le débat, on confond le noyau Linux, avec la Linux Programming Interface, et plus particulièrement POSIX ou les Single UNIX Specification. On a cette idée que Linux isole mal parce qu'il expose une surface d'attaque trop grande à travers ses appels systèmes (pour POSIX/SUS). Il faudrait donc un hyperviseur avec des machines virtuelles, ou la version optimisée : un unikernel, qui expose une surface beaucoup plus petite et plus simple, pour résoudre ce problème. Mais pourquoi ? Si le problème c'est l'interface, c'est cette interface qu'il faut réduire ! Et c'est super facile, ça s'appelle seccomp. Dans sa version originelle, seccomp ne permet que read, write, exit et sigreturn sur des descripteurs de fichier déjà ouvert. In fine, c'est une surface d'attaque bien moins grande, qui essaient d'émuler des périphériques obscures. Et puis quand bien même on trouverait ça encore trop, pourquoi ne pas envisager de patcher Linux pour enlever ce qui ne convient pas ? Ça me semble bien moins ambitieux que de repartir sur un système complètement différent.

Alors, on pourrait toujours avancer que des outils comme Firecraker ne gardent que le strict minimum, et que donc, il ne s'agit plus que de choisir si notre interface est VirtIO ou un stream sur un file descriptor Linux. Que c'est bonnet blanc et blanc bonnet. Mais ce que j'observe, c'est que dans le monde des hyperviseurs, on fait surtout des boites noires. Alors on rationalise : on déploie plein de boites noires qu'on gère de manière identique, mais on a déplacé beaucoup des problèmes dans ces boites noires. Avec VirtIO, on a du réseau et du stockage, mais on n'a toujours pas de bus de communication. Notre boite noire devient un seul processus, mais à l'intérieur, il y a plusieurs processus en réalité. On a donc dupliqué plein de choses : des schedulers de processus, mais aussi des scheduler d'I/O. Et puis un scheduler ça observe son environnement, ça fait des suppositions, et ça prend des décisions. Quid des interférences entre ces schedulers ? Je sais d'expérience que ça arrive. Et puis on a une API de bas-niveau, certe simple, mais qui embarque avec elle très peu d'informations sur le contexte, elle est très peu descriptive, on perd en expressivité. Et donc on donne moins de latitude au scheduler bas-niveau. Un exemple : Linux utilise la RAM de deux façons, directement pour les applications, et pour du cache. Certaines applications utilisent astucieusement cette capacité de cache, comme LMDB. Mais du point de vue de l'hyperviseur, il n'y a pas de différence entre le cache - récupérable - et la mémoire vraiment utilisée. On peut donc se retrouver à prendre des décisions sous-optimales. Et puis, enfin, en faisant des silos, on se prive de la mutualisation de certaines tâches. Pour provoquer : pourquoi on a un problème avec Electron (le fait de faire tourner un moteur Javascript par application) mais pas avec les unikernel ?

Enfin, il me semble nécessaire de faire une critique des unikernels du point de vue de la "loi de Conway" : les logiciels reflètent l'organisation sociale des gens qui l'ont produit. Et les interfaces logicielles sont la représentation des frontières entre les groupes. Quand on fait de l'unikernel, l'interface correspond à ce qu'on appelle de l'IaaS. Or il ressort lors de mes échanges que le bon niveau est plutôt du côté de ce qu'on appelle PaaS / Serverless. C'est d'ailleurs ce qu'ont confirmé les deux sessions suivantes : Genezio utilise des unikernels mais ne les expose pas à ses clients directement, elle leur propose du PaaS. Bunny est un builder sur le modèle de Docker pour packager des applications Unikernel, mais en réalité la plupart des gens veulent surtout faire un git push et que leur code se déploie seul.

Autrement dit, les technologies changent (de PHP au Serverless Typescript), mais il semble se dégager une constante : il est avisé de placer la frontière, l'interface, entre les développeur-euses qui encodent un métier dans leur langage de programmation d'une part, et ceux qui encodent une abstraction du matériel d'autre part. Cette abstraction doit permettre aux premiers d'exprimer leurs intentions, et au second d'y ajouter des contraintes qui permettent une utilisation efficiente du matériel. L'API S3 est un bon exemple : on peut stocker un fichier binaire de taille arbitraire, faire plein de choses avec, il très facile à servir, et en même temps, les contraintes sur l'API nous permettent de faire du géo-distribué ce que ne permet pas POSIX. A mon sens, ce modèle fonctionne très bien avec la philosophie microkernel : par défaut on n'a pas confiance dans le processus et on a un système standardisé avec une vérification systématique des permissions, avec une granularité aussi fine que voulu, pour accéder aux ressources.

Certaines personnes lors de la présentation envisageaient les unikernels comme des "libOS". Et c'est peut-être là, selon moi, que se trouve une issue intéressante au concept : on pourrait enlever/désactiver certains composants de Linux, et faire nos propres composants microkernel à partir d'une libOS. Par exemple, tirer une stack QUIC d'une libOS, la wrap dans notre propre système de permission, et l'intégrer à notre système microkernel like.

Proposition

Mon système idéal pourrait se résumer à : un Linux, dont on ne garderait que les descripteurs de fichier, qui formeraient la base d'un système de capability systématique.

Plus spécifiquement un noyau Linux qu'on aurait potentiellement compilé, placé dans une partition EFI, potentiellement sans initramfs, ni système de fichier, avec un système d'initialisation qui soit un binaire de notre cru. Il serait en charge de lancer un ensemble hardcodé de process (possiblement en se forkant) : un daemon de membership pour s'interconnecter avec les autres serveurs de la grappe, connaitre leur adresse réseau et leurs services, un daemon de message bus - aussi appelé service mesh - qui se chargerait du routage des messages, des contrôles de permission, du chiffrement, etc., un daemon d'orchestration, qui serait en charge de démarrer les processus sur le noeud local avec les paramètres d'isolation seccomp et cgroup (à minima), et de passer les descripteurs de fichier dans le process et enfin un daemon de debug avec un ssh qui permette de se connecter et inspecter le système.

Tout le système serait construit sur les descripteurs de fichier, un processus n'aurait pas accès à l'interface de programmation de Linux (hormis les quelques appels systèmes pour manipuler des descripteurs de fichier déjà ouverts, grâce à seccomp toujours). Si un processus veut accéder à l'heure, à un timer, ou tout autre chose, il doit récupérer un descripteur de fichier. Je pense que ce système est particulièrement efficace : avec poll, puis epoll, et maintenant io_uring, de nombreuses ressources sont exposables via un descripteur de fichier maintenant. Ça veut aussi dire, que dans plein de cas, on peut éviter d'ajouter des surcouches : un daemon peut setup une connexion TLS avec kTLS et passer le descripteur au process, qui alors fait du TLS sans s'en rendre compte, et de manière bien plus efficiente que notre système de reverse proxy actuel. Pour des cas spécifiques qui ne sont pas directement supportés par le noyau Linux, alors le descripteur de fichier serait une unix domain socket ou un pipe vers le daemon parent qui se chargerait du travail et des vérifications. Certains processus plus privilégiés pourraient donc avoir accès à quelques syscalls du domaine qu'ils gèreraient. On aurait par exemple un processus qui aurait accès aux syscalls de netfilter, avec qui ont communiquerait à l'aide de UNIX Sockets, pour demander des configurations. Il vérifierait alors les permissions, serait conçu pour gérer correctement cet objet partagé, et appliquerait les modifications.

Quelques précisions : quand on fork + exec avec Linux, les file descriptors ouverts sont passés au processus enfant. Mais se pose la question quand le processus est démarré : comment peut-il obtenir de nouveaux descripteurs ? À travers un appel système comme pidfd_getfd ou via SCM_RIGHTS. Maintenant on peut imaginer que l'init crée un canal de communication entre notre daemon de message et notre orchestrateur au démarrage. Lorsque l'orchestrateur veut démarrer un nouveau processus, il demande - en suivant la description du service - au message bus les descripteurs de fichiers demandés - comme écouter sur un port, ouvrir une connexion TCP, etc. Le message bus les renvoie à l'orchestrateur, qui démarre le service en passant ces descripteurs de fichier. Dans le cas où le service enfant veut intéragir directement avec le message bus, par exemple parce qu'il est amené à ouvrir des connexions dans son cycle de vie, l'orchestrateur peut demander au message bus d'ouvrir une paire de descripteur de fichier vers lui-même avec des restrictions de permissions indiquées par l'orchestrateur, et passer ce descripteur à l'enfant.

La rétro-compatibilité peut alors s'envisager avec des outils comme gVisor. Le système de fichier pourrait être émulé, ainsi que beaucoup d'autres aspects. Ce dont on aurait vraiment besoin, comme le réseau par exemple, serait alors intercepté, et les appels convertit pour intéragir avec le message bus. Mais de manière générale, tout ce qui permet d'intercepter les syscalls est envisageable. Ainsi un LD_PRELOAD avant une libc est envisageable aussi, voire une libc spécifique, possiblement il y a des choses à creuser du côté de emscripten.

On pourrait alors imaginer un service de serverless/lambda function sur ce modèle. En gros le principe d'un système de ce genre, c'est de charger le code de l'utilisateur qu'en cas de besoin. C'est un peu la version cloud de inetd si vous préférez. Donc on aurait un daemon web qui écouterait. Lors de la réception d'une requête HTTP, il scannerait le champs Host de la requête et son URL. En fonction de cette dernière, il demanderait à l'orchestrateur de démarrer le service adéquat, et de connecter ses descripteurs de fichier pour gérer la requête/réponse. Là encore, si l'utilisateur veut que son service puisse accéder à des ressources autres, il devra les déclarer au moment de publier son code, et ne pourra pas outrepasser ce qui lui est permis.

Bref, on aurait un système avec une approche robuste de l'isolation, construit sur des briques qui ont fait leurs preuves, des possibilités d'observations et de debugs importantes, qui reste flexible : il serait construit sur des streams, un modèle de sécurité reconnu, et la capacité d'offrir des APIs de (très) haut niveau.

Conclusion

J'ai vu deux trois autres choses au FOSDEM. En vrac : j'ai bien aimé la présentation de Garage par Alex, j'ai beaucoup apprécié aussi le lightning talk de Emily Omier Project websites that don't suck : très pertinent, très bien exécuté. Beaucoup de monde, pas eu le temps de faire la bamboche, il est temps d'aller me reposer.