Compare commits
44 commits
Author | SHA1 | Date | |
---|---|---|---|
df0ccf3f2a | |||
5a16c308ef | |||
dc2bcb8ee6 | |||
ead8fea7c7 | |||
2fa6fd460d | |||
c0e8df4741 | |||
fab99bab6d | |||
7bde101e33 | |||
1000bad6d0 | |||
dde8f03192 | |||
5a03d5db77 | |||
3dcb9e3111 | |||
fa4fe1767a | |||
a80ab85347 | |||
776c598d90 | |||
20e4db41af | |||
0cee5415d0 | |||
ac9ebc772c | |||
c19a079ee5 | |||
e9ee85300d | |||
4b248805e8 | |||
122f97a345 | |||
c647db6b43 | |||
9e9dcdc3e4 | |||
0f2fffec3f | |||
41de375e02 | |||
9aed5043e5 | |||
5d26250be4 | |||
cf8788c462 | |||
281cac4d39 | |||
65847768df | |||
c3904d2727 | |||
3ecc275eaa | |||
258992d576 | |||
20c08bbc46 | |||
352f692239 | |||
5d324d1209 | |||
a4a1abd5af | |||
34f1599a43 | |||
df56b93761 | |||
ceaac4d3d4 | |||
0682291435 | |||
b4100af036 | |||
7e5d1b5805 |
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: ruby
|
41
Gemfile.lock
|
@ -1,21 +1,24 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
bigdecimal (3.1.8)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby (1.3.4)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.15.5)
|
||||
ffi (1.17.0-x86_64-linux-gnu)
|
||||
forwardable-extended (2.6.0)
|
||||
google-protobuf (3.22.0-x86_64-linux)
|
||||
google-protobuf (4.28.1-x86_64-linux)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.12.0)
|
||||
i18n (1.14.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.3.2)
|
||||
jekyll (4.3.3)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
|
@ -42,28 +45,28 @@ GEM
|
|||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.8.0)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (5.0.1)
|
||||
racc (1.6.2)
|
||||
rake (13.0.6)
|
||||
public_suffix (6.0.1)
|
||||
racc (1.8.1)
|
||||
rake (13.2.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
redcarpet (3.6.0)
|
||||
rexml (3.2.5)
|
||||
rouge (4.1.0)
|
||||
rexml (3.3.7)
|
||||
rouge (4.3.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass-embedded (1.58.3)
|
||||
google-protobuf (~> 3.21)
|
||||
rake (>= 10.0.0)
|
||||
sass-embedded (1.78.0)
|
||||
google-protobuf (~> 4.27)
|
||||
rake (>= 13)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
unicode-display_width (2.4.2)
|
||||
unicode-display_width (2.5.0)
|
||||
webrick (1.8.1)
|
||||
|
||||
PLATFORMS
|
||||
|
@ -78,4 +81,4 @@ DEPENDENCIES
|
|||
webrick (~> 1.7)
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.26
|
||||
2.5.9
|
||||
|
|
15
_cv/main.tex
|
@ -6,7 +6,7 @@
|
|||
|
||||
\firstname{Quentin}
|
||||
\familyname{Dufour}
|
||||
\title{\large System developer at Astrolabe CAE}
|
||||
\title{\large Freelance system developer}
|
||||
\address{27 La Sauvageais}{35580 Goven}
|
||||
%\mobile{+33 6 76 79 99 88}
|
||||
\email{quentin@dufour.io}
|
||||
|
@ -35,19 +35,14 @@
|
|||
%\cventry{June 2012}{High School Diploma}{Lycée René Descartes}{Cournon d'Auvergne}{France}{Scientific High School Diploma with distinction.}
|
||||
|
||||
\section{Experience}
|
||||
\cventry{Now\\Oct. 2022}{Astrolabe CAE}{Cooperative}{Rennes}{France}{
|
||||
NLnet / NGI Assure Grant - Working on Aerogramme, an encrypted IMAP-compatible MDA.
|
||||
\cventry{Now\\Jan. 2020}{Deuxfleurs}{Association}{Rennes}{France}{
|
||||
\underline{2023} NLnet / NGI Assure Grant - Working on Aerogramme, an encrypted IMAP-compatible MDA.
|
||||
\newline{}
|
||||
\underline{2022} NGI Pointer Grant - Working on Garage, a geo-distributed S3-compatible object store.
|
||||
}
|
||||
|
||||
\cventry{Now\\Oct. 2022}{Rapsodie}{Startup}{Paris}{France}{
|
||||
Designing and developping the cloud infrastructure (Kubernetes, Python, FastAPI, Apache Beam, GCP).
|
||||
}
|
||||
|
||||
|
||||
\cventry{Sep. 2022\\Sep. 2021}{Deuxfleurs}{Association}{Rennes}{France}{
|
||||
NGI Pointer Grant - Working on Garage, a geo-distributed S3-compatible object store.
|
||||
}
|
||||
|
||||
\cventry{Aug. 2017\\Feb. 2017}{French National Cybersecurity Agency (ANSSI)}{Internship}{Paris}{France}{
|
||||
Migration and improvement of hardened Linux containers (LXC).
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>{{ site.name }}</title>
|
||||
<title>{{ page.title | default: "Accueil" }} | {{ site.name }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="author" content="{{ site.author }}">
|
||||
<meta name="description" content="{{ site.name }}. {{ site.description }}">
|
||||
|
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
slug: online-upgrade-fedora
|
||||
status: published
|
||||
sitemap: true
|
||||
title: Upgrade Fedora online
|
||||
title: Online Fedora Upgrade
|
||||
description: How to efficiently destroy your distribution
|
||||
disqus: false
|
||||
category: operation
|
||||
|
|
131
_posts/2024-02-06-fosdem.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
layout: post
|
||||
title: Réflexions suite au FOSDEM 2024
|
||||
date: 2024-02-06
|
||||
status: published
|
||||
sitemap: true
|
||||
category: divers
|
||||
description: 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](https://aerogramme.deuxfleurs.fr/), 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](https://fosdem.org/2024/schedule/event/fosdem-2024-2642--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](https://grommunio.com/) 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](https://www.lemonde.fr/planete/article/2005/02/17/michel-rocard-ferraille-contre-le-brevet-logiciel_398497_3244.html), d'où [VLC](https://www.videolan.org/) 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](https://adnab.me/).
|
||||
Alex s'intéresse à l'infrastructure logiciel de [Deuxfleurs](https://deuxfleurs.fr/), 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](https://en.wikipedia.org/wiki/Object-capability_model) de [sel4](https://sel4.systems/). Elle a essaimé aussi dans des OS traditionnels, je pense par exemple à [Capsicum](https://www.cl.cam.ac.uk/research/security/capsicum/papers/2010usenix-login-capsicum.pdf) 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](https://ferd.ca/the-zen-of-erlang.html). 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](https://0pointer.net/blog/projects/resources.html) :*
|
||||
|
||||
> 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](https://www.erlang.org/doc/reference_manual/distributed.html), 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](https://istio.io/), [Consul Connect](https://developer.hashicorp.com/consul/docs/connect), ou le [Dapr](https://dapr.io/) 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](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8354433) 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](https://fr.wikipedia.org/wiki/D-Bus), du côté de [Greybus](https://lwn.net/Articles/715955/), ou même du côté de [Binder](https://elinux.org/Android_Binder) dans Android. Et puis de pousser encore un peu plus loin, et d'aller voir les [Enterprise Service Bus](https://fr.wikipedia.org/wiki/Enterprise_service_bus) et les [Message Brokers](https://en.wikipedia.org/wiki/Message_broker). 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](https://fr.wikipedia.org/wiki/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](https://fr.wikipedia.org/wiki/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](https://www.pandasecurity.com/en/mediacenter/venom-the-security-vulnerability-in-your-floppy-drive/). 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](https://firecracker-microvm.github.io/) ne gardent que le strict minimum, et que donc, il ne s'agit plus que de choisir si notre interface est [VirtIO](https://fr.wikipedia.org/wiki/Virtio) ou un stream sur [un file descriptor](https://en.wikipedia.org/wiki/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"](https://en.wikipedia.org/wiki/Conway%27s_law) : 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](https://fr.wikipedia.org/wiki/Infrastructure_as_a_service). Or il ressort lors de mes échanges que le bon niveau est plutôt du côté de ce qu'on appelle [PaaS](https://fr.wikipedia.org/wiki/Platform_as_a_service) / [Serverless](https://fr.wikipedia.org/wiki/Informatique_sans_serveur). C'est d'ailleurs ce qu'ont confirmé les deux sessions suivantes : [Genezio](https://genezio.com/) utilise des unikernels mais ne les expose pas à ses clients directement, elle leur propose du PaaS. [Bunny](https://fosdem.org/2024/schedule/event/fosdem-2024-3456-a-modular-approach-to-effortless-and-dependency-aware-unikernel-building/) 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](https://docs.kernel.org/networking/tls.html) 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](https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html) ou via [SCM_RIGHTS](https://man7.org/linux/man-pages/man7/unix.7.html). 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](https://gvisor.dev/). 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](https://fr.wikipedia.org/wiki/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.
|
||||
|
||||
L'idée que je présente est partiellement discutée dans un papier intitulé [A capability inspired low level security model based on modern Linux kernels](http://arkanis.de/projects/2013-01%20Capabilities/A%20capability%20inspired%20low%20level%20security%20model%20based%20on%20modern%20Linux%20kernels.pdf). Ma proposition se différencie dans le sens où, pour contourner certaines limitations des descripteurs de fichier, j'envisage des daemons en espace utilisateur qui se chargeraient de réduire le scope. Il est bon de noter, en plus de Capsicum, le cas de [Cloud ABI](https://lwn.net/Articles/674770/). Pour moi, Capsicum, à vouloir moduler ce que fait le descripteur de fichier se trompe : ça doit rester un stream simple, l'important c'est qui il y a au bout. Le projet que j'envisage est justement de définir ces composants (orchestrateur, message bus, etc.) dans un monde où on a que des descripteurs de fichier. CloudABI se concentre uniquement sur la rétro-compatibilité : comment s'assurer que les processus existants, supposant un POSIX/interface de programmation Linux vont bien se comporter et ne vont pas crasher/silencieusement downgrade vers des comportements indésirables (par exemple pour la cryptographie et la génération d'aléatoire). C'est intéressant mais orthogonal.
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
J'ai vu deux trois autres choses au FOSDEM. En vrac : j'ai bien aimé [la présentation de Garage](https://fosdem.org/2024/schedule/event/fosdem-2024-3009-advances-in-garage-the-low-tech-storage-platform-for-geo-distributed-clusters/) par Alex, j'ai beaucoup apprécié aussi le lightning talk de Emily Omier [Project websites that don't suck](https://fosdem.org/2024/schedule/event/fosdem-2024-3154-project-websites-that-don-t-suck/) : très pertinent, très bien exécuté. Plein de monde, de food trucks, de stands, de présentations, je n'ai pas essayé de tout faire : j'ai essayé de me focaliser seulement sur ces deux points. Si vous avez un billet de blog sur votre FOSDEM, je peux le référencer en bas de celui-ci.
|
57
_posts/2024-04-27-capa-web-deuxfleurs.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
layout: post
|
||||
title: Quelle est la capacité de Deuxfleurs ?
|
||||
date: 2024-04-27
|
||||
status: published
|
||||
sitemap: true
|
||||
category: operation
|
||||
description: Évaluer la capacité d'hébergement de sites webs statiques de Deuxfleurs.
|
||||
---
|
||||
|
||||
De part son côté atypique (de vieux PC de bureau reconvertis en serveurs derrière des connexions FTTH grands publics avec beaucoup de logiciels maisons - tricot, garage, etc.), les usager-es de Deuxfleurs ne savent pas trop quoi attendre en terme de performance. De mon point de vue d'opérateur, c'est dur également d'évaluer les capacités de Deuxfleurs, à part en disant qu'on a pensé notre solution pour mutualiser les usages, et donc que peu de machines puissent servir à beaucoup de monde.
|
||||
|
||||
Commençons par quelques faits : au 27 avril 2024, Deuxfleurs a 8 serveurs (3 à Orsay, 2 à Lilles, 3 à Bruxelles).
|
||||
Il n'y a que 2 serveurs / 8 qui reçoivent les requetes : en effet, notre configuration IPv4 ne permet pas d'avoir plus d'un répartiteur de charge HTTP par zone géographique, et notre zone géographique belge est encore en chantier. Chaque serveur est connecté en ethernet 1Gb/sec et on a 300Mbps+ entre Proximus et Free (en gros c'est une approximation de notre bande passante sur Internet, même si ça varie en fonction des destinations et du moment observé bien entendu...). Avec 2 répartiteurs de charge, on estime donc à 600Mb/s notre bande passante sur le réseau (2x 300Mb/s), bien entendu après il faut du logiciel qui puisse gérer ça.
|
||||
|
||||
Chaque serveur est à peu prêt identique : un ordinateur de bureau milieu de gamme de 2013. Typiquement, en terme de processeur on a du [Intel(R) Pentium(R) CPU G3420](https://ark.intel.com/content/www/fr/fr/ark/products/77775/intel-pentium-processor-g3420-3m-cache-3-20-ghz.html) et entre 8Go et 16Go de RAM par serveur.
|
||||
Au total, Nomad, un de nos outils de gestion, rapporte un total de 78Go de RAM et 16 CPU (répartis en 8 machines physiques donc).
|
||||
|
||||
En terme de stockage, on a 4To de stockage à Lille, 1.5To à Bruxelles, 3To à Orsay. Ça fait seulement 1.5To utilisable par Garage, car on requiert une duplication sur 3 sites pour la robustesse, et Bruxelles n'a que 1.5To (c'est normal, c'est la "zone" en chantier aujourd'hui).
|
||||
|
||||
Avec notre politique de 200Mo/site max, ça fait quand même déjà une capacité de 7 500 sites webs. On monte à 30 000 sites webs si on considère 50Mo (la taille réservée à la création du site, avant l'augmentation du quota). Si on passe à Bruxelles à 3To pour "rattraper" les autres sites, on double le nombre de sites web hébergeables. De plus, aujourd'hui on trouve des disques durs 2.5" à 4To, ce qui veut dire qu'on pourrait passer entre 8To et 15To par zone géographique sans changer radicalement notre infrastructure (même boitier, meme enveloppe de consommation electrique, etc.). Dans ce cas hypothétique, on dépasse les 100 000 sites webs hébergeables. Bien sûr à chaque fois, c'est en supposant qu'on héberge que des sites webs : mais on peut diviser par 2 les chiffres, et se dire qu'on alloue le reste aux autres services, et ça reste étourdissant !
|
||||
|
||||
Se pose maintenant la question de la montée en charge, combien de requêtes/secondes on peut traiter. Aujourd'hui on a un ~10 req/sec continu (majoritairement du à Matrix et Sogo, des protocoles de chat & email respectivement au dessus de HTTP qui font du polling, c'est très en dessous côté hébergement web garage) qui n'ébranle pas franchement nos serveurs. On va donc faire du *scalability testing* pour voir jusqu'où on peut monter. Il faut savoir que le pire cas pour nous, c'est quand un site web devient populaire d'un seul coup, et que tout le monde s'y connecte en même temps avec un cache froid, par exemple parce qu'un tweet devient viral ou des notifications push sur mobile envoyés par une application. On va donc prendre ce cas pour notre test.
|
||||
|
||||
*Notez que pour les notifications mobiles, plutôt que de déployer d'avantages de serveurs pour gérer le pic de trafic, il est plus avisé d'échelonner leur envoi auprès des utilisateurs. C'est à dire envoyer un premier lot de notifications à 10% des utilisateurs, attendre 20 minutes, envoyer le 2nd lot, etc. Et oui, c'est aussi bête que ça parfois...*
|
||||
|
||||
Notre cobaye sera ce propre blog, et plus exactement sa page d'accueil et ses 8 ressources à charger. J'utilise l'outil [k6](https://k6.io/) pour ce test qui n'a pas vocation à être exhaustif, pour info voici le script de test :
|
||||
|
||||
```js
|
||||
import http from 'k6/http';
|
||||
|
||||
export default function () {
|
||||
http.get('https://quentin.dufour.io');
|
||||
http.get('https://quentin.dufour.io/assets/css/style.css');
|
||||
http.get('https://quentin.dufour.io/assets/css/typo.css');
|
||||
http.get('https://quentin.dufour.io/assets/images/favicon.ico');
|
||||
http.get('https://quentin.dufour.io/assets/fonts/MerriweatherRegularLatin.woff2');
|
||||
http.get('https://quentin.dufour.io/assets/fonts/MerriweatherBoldLatin.woff2');
|
||||
http.get('https://quentin.dufour.io/assets/fonts/MerriweatherItalicLatin.woff2');
|
||||
http.get('https://quentin.dufour.io/assets/fonts/Symbola.ornements.woff2');
|
||||
}
|
||||
```
|
||||
|
||||
[→ Accéder au rapport complet ←](https://quentin.dufour.io/k6/qdu-100vu.html)
|
||||
|
||||
Je n'ai volontairement pas poussé l'infrastructure au maximum, mais on tient sans problème 100 utilisateurs en instantané, ce qui fait ~800req/sec et 8Mo/sec de transfert de données. Sur 1 minute, ça fait 6 000 utilisateurs. Quand on était en page d'accueil sur Hacker News, on a vu que les visites s'étalaient en réalité plutot sur 1h ou 2h, meme si le trafic n'était pas réparti de manière homogene, mais plus sous la forme d'une gaussienne. Sans faire les calculs, je dirais qu'en cas de "coup de projecteur", on peut tenir les ~10 000 utilisateurs sans trop de soucis. En informatique, on pense souvent en terme d'ordres de grandeurs. À mon avis, l'ordre de grandeur suivant, un burst de ~100 000 utilisateurs est imaginables sans remettre en question notre architecture mais avec des améliorations à différents endroits. Par contre l'ordre de grandeur suivant, 1 million, nous forcerait à repenser en profondeur notre système. Toute cette réflexion reste encadrée pour moi par deux articles très importants, [C10K](http://www.kegel.com/c10k.html) et [C10M](https://highscalability.com/the-secret-to-10-million-concurrent-connections-the-kernel-i/). Le premier date de 2003, et constate que les serveurs industriels ont la capacité de supporter 10 000 connexions simultanés, et réfléchit à comment concevoir du logiciel qui permette d'exploiter ces capacités. On pourrait dire que C10M, c'est le même constat entre 10 et 20 ans plus tard, mais que cette fois-ci on est passé à 10 millions de connexions, là encore en remettant encore à plat le logiciel qu'on conçoit. Alors bien sûr, ici on parle de connexions qui ne font pas grand chose, mais quand même, c'est pertinent de savoir où on se situe, surtout en 2024 où on écrit encore des logiciels qui ont du mal à gérer 4 ou 5 connexions simultanées...
|
||||
|
||||
Jusqu'ici je parle de trafic soudain, mais pour de nombreux sites, c'est un flux continu. Dans ce cas, les visites sont beaucoup plus espacées dans le temps. Mettons un site web qui s'adresse aux particuliers, d'expérience il verra une large partie de ses visites se faire entre 7h et 9h le matin, lors de la pause méridienne, disons entre 12h et 14h puis surtout le soir, de 18h à 22h, soit au total quand même 8 heures. À 6 000 utilisateurs par minutes parfaitement répartis, ça nous fait 2.9 millions de visiteurs par jours (on suppose que consulter les pages suivantes est négligeable passé la première requête). Bien sûr, mon modèle est très naïf là, et trop imprécis pour affirmer quelque chose de définitif. Mais disons que, dès lors que le trafic se lisse sur la journée, on a pas trop de mal à gérer 1 million d'utilisateurs par jour. Bon, il ne faut pas perdre de vu que ce budget de 1 million par jour, il est à partager entre tout le monde ! Mais là encore, on a les statistiques de notre côté : des abonnements Twitch aux marchandises sur Amazon en passant par la taille des instances Mastodon, on va avoir une [longue traîne](https://fr.wikipedia.org/wiki/Longue_tra%C3%AEne) : un ou quelques sites webs avec beaucoup de visites, et très rapidement toute une myriade de petits sites avec très peu de visites par jour, qui se fondent dans l'épaisseur du trait. On peut donc dimensionner pour quelques gros sites et le reste suivra.
|
||||
|
||||
*Jusqu'ici je n'ai pas évoqué le cas d'utilisateurs malveillants, qui auraient pour objectif de réaliser une attaque par déni de service. Il existe différents types d'attaque par déni de service, certaines sont du "brute force" : un concours de celui qui aura le plus de ressources. De par l'approche de Deuxfleurs, c'est évidemment un concours que l'association n'a pas vocation à mener, et donc les attaques par déni de service sont un risque à prendre en compte. À noter que parfois, dans certaines limites, les fournisseurs d'accès internet peuvent agir pour bloquer le trafic malveillant seulement - ou tout le trafic - pendant l'attaque.*
|
||||
|
||||
Mon test n'est pas du tout exhaustif ou représentatif de tout le web statique - mais c'est le mieux qu'on ait et on devra s'en contenter pour le moment. Par exemple, un site qui serait beaucou plus lourd (image, audio) pourrait avoir un comportement différent (on atteindrait peut-être une limite de bande passante - à tester), et c'est juste un exemple parmis des milliers de situations possibles (HTTP 2 vs HTTP 1.1 ? Versions & Cipher de TLS ? etc.).
|
||||
|
||||
À noter aussi qu'on n'a pas passé beaucoup de temps à penser l'optimisation des ressources de Deuxfleurs, on pourrait probablement avoir des gains avec de petites modifications. Par exemple en utilisant les spécificités d'IPv6 qui permettent de mettre plusieurs load balancers par zones géographique, ou en favorisant le chiffrement ChaCha20-Poly1305 qui est plus rapide que AES sur les CPU qui n'ont pas d'accélération matérielle comme certains de nos serveurs. Côté matériel, on pourrait s'assurer qu'on a du lien gigabit partout (et donc faire la chasse au 100Mb qui trainerait). En réalité je n'ai pas vraiment réfléchi plus que ça au sujet, je suis sûr qu'en se creusant les méninges, on trouverait des choses.
|
||||
|
||||
Et pour terminer, une conclusion provocante : avec 10 req/sec, rarement plus de 5Mb/s de trafic sortant, et 250 sites webs hébergés, c'est respectivement 1% du budget requête, 2% de la bande passante, et 3% du budget stockage qui est actuellement utilisé sur Deuxfleurs.
|
||||
|
227
_posts/2024-06-09-cacerts.md
Normal file
|
@ -0,0 +1,227 @@
|
|||
---
|
||||
layout: post
|
||||
title: TLS dans un conteneur statique
|
||||
date: 2024-06-09
|
||||
status: published
|
||||
sitemap: true
|
||||
category: developpement
|
||||
description: Comment gérer TLS dans un conteneur statique
|
||||
---
|
||||
|
||||
Cet article est motivé par un problème rencontré sur l'interface Guichet de Deuxfleurs.
|
||||
Au moment d'intéragir avec l'API S3, on a cette erreur suivante :
|
||||
|
||||
```
|
||||
Impossible d'effectuer la modification.
|
||||
Put "https://garage.deuxfleurs.fr/bottin-pictures/d1e3607f-4b9c-45fa-9e11-ddf8eef48676-thumb": tls: failed to verify certificate: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
Pour comprendre le problème, partons de cet exemple basique en Go :
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_, err := http.Get("https://deuxfleurs.fr")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
```
|
||||
|
||||
Ce bloc de code fait une requête HTTPS vers deuxfleurs.fr et log l'erreur si il y en a une, sinon il affiche `Success`.
|
||||
|
||||
On va le compiler en statique pour ne pas dépendre de la lib C locale :
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 go build main.go
|
||||
```
|
||||
|
||||
Ce qui nous donne bien un executable statique :
|
||||
|
||||
```
|
||||
$ file main
|
||||
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=ctUIsYsrR2BtpR58vqRU/TI93T6hZlDxMBNqsplsv/QYD-xJaEDyWB0QaX6tSS/cDyvoEdvE3kZpdq8yCs3, with debug_info, not stripped
|
||||
```
|
||||
|
||||
Si on le fait tourner en local, tout se passe bien :
|
||||
|
||||
```
|
||||
2024/06/09 15:54:59 Success
|
||||
```
|
||||
|
||||
Maintenant, puisqu'on nous avons un binaire statique qui ne dépend de rien, on va vouloir créer un conteneur Docker qui ne contient que ce fichier (alors que souvent on embarque une distribution de base comme Debian ou Alpine). Pour se faire, on écrit un Dockerfile avec deux étapes : une qui a les outils pour le build, et une qui ne contient que notre binaire :
|
||||
|
||||
```Dockerfile
|
||||
FROM golang as builder
|
||||
COPY main.go .
|
||||
RUN CGO_ENABLED=0 go build -o /main main.go
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /main /main
|
||||
CMD [ "/main" ]
|
||||
```
|
||||
|
||||
On construit & lance le conteneur et... on reproduit l'erreur !
|
||||
|
||||
```
|
||||
$ docker build -t tls-static-go .
|
||||
$ docker run --rm -it tls-static-go
|
||||
2024/06/09 14:02:37 Get "https://deuxfleurs.fr": tls: failed to verify certificate: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
Pour comprendre la différence de comportement entre l'intérieur du conteneur et l'extérieur, on peut faire appel à `strace` et voir les fichiers que notre binaire essaie d'ouvrir :
|
||||
|
||||
```bash
|
||||
$ strace -fe openat ./main
|
||||
...
|
||||
[pid 9066] openat(AT_FDCWD, "/etc/ssl/certs/ca-certificates.crt", O_RDONLY|O_CLOEXEC) = 7
|
||||
[pid 9066] openat(AT_FDCWD, "/etc/ssl/certs", O_RDONLY|O_CLOEXEC) = 7
|
||||
[pid 9066] openat(AT_FDCWD, "/etc/ssl/certs/ca-bundle.crt", O_RDONLY|O_CLOEXEC) = 7
|
||||
...
|
||||
```
|
||||
|
||||
En général ce fichier est fourni par les distributions, qui à ma connaissance majoritairement se basent sur le travail de Mozilla.
|
||||
On peut en lire plus à propos de la gestion de ces racines de confiance sur la page dédiée du wiki de Mozilla : [CA/FAQ](https://wiki.mozilla.org/CA/FAQ)
|
||||
|
||||
Une façon de faire est donc de copier le fichier de notre builder dans notre conteneur final :
|
||||
|
||||
```diff
|
||||
--- Dockerfile.old 2024-06-09 16:13:18.457415988 +0200
|
||||
+++ Dockerfile 2024-06-09 16:12:59.494182932 +0200
|
||||
@@ -4,5 +4,6 @@
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /main /main
|
||||
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
CMD [ "/main" ]
|
||||
```
|
||||
|
||||
On rebuild, on relance et... ça marche !
|
||||
|
||||
```
|
||||
$ docker build -t tls-static-go-2 .
|
||||
$ docker run --rm -it tls-static-go-2
|
||||
2024/06/09 14:14:30 Success
|
||||
```
|
||||
|
||||
Pour visualiser le contenu d'une image docker, on peut utiliser l'outil [dive](https://github.com/wagoodman/dive) :
|
||||
|
||||
![Dive](/assets/images/posts/dive.png)
|
||||
|
||||
À gauche, puis à droite, les commandes sont :
|
||||
|
||||
```bash
|
||||
dive tls-static-go
|
||||
dive tls-static-go-2
|
||||
```
|
||||
|
||||
On voit bien l'ajout du chemin `/etc/ssl/certs/ca-certificates.crt` à droite.
|
||||
Maintenant, le problème avec les `Dockerfile`, c'est que ce ne sont pas du tout des builds reproductibles ni précis : on ne sait pas quelles versions ou dépendances on embarque. Donc on veut plutôt construire nos conteneurs avec NixOS pour plus de contrôle.
|
||||
|
||||
Pour faciliter le packaging Nix, on va générer un fichier `go.mod` :
|
||||
|
||||
```bash
|
||||
go mod init tls-static-go
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
On peut ensuite créer un fichier `flake.nix` qui est notre équivalent – mais plus précis – de notre Dockerfile :
|
||||
|
||||
```nix
|
||||
{
|
||||
description = "TLS Static Golang";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/master";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
# On configure "le dépôt Nix"
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
};
|
||||
|
||||
# On utilise le builder Go intégré à Nix pour construire notre app
|
||||
tls-static = pkgs.buildGoModule {
|
||||
pname = "tls-static";
|
||||
version = "0.1.0";
|
||||
src = ./.;
|
||||
vendorHash = null;
|
||||
CGO_ENABLED = 0;
|
||||
};
|
||||
|
||||
# On construit une image Docker qui ne contient que l'app qu'on a build.
|
||||
container = pkgs.dockerTools.buildImage {
|
||||
name = "superboum/tls-static-go";
|
||||
copyToRoot = tls-static;
|
||||
config = {
|
||||
Cmd = [ "/bin/tls-static" ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
# Par défaut, sous Linux amd64, on construit le conteneur
|
||||
packages.x86_64-linux.default = container;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Et ensuite on peut construire / charger / lancer le conteneur Docker :
|
||||
|
||||
```
|
||||
$ nix build
|
||||
$ docker load <result
|
||||
Loaded image: superboum/tls-static-go:zvsb5pwz25irhr90x10kpfhgsph5is1s
|
||||
$ docker run --rm -it superboum/tls-static-go:zvsb5pwz25irhr90x10kpfhgsph5is1s
|
||||
2024/06/09 14:37:57 Get "https://deuxfleurs.fr": tls: failed to verify certificate: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
Et de nouveau la même erreur, on jette un coup d'oeil avec dive :
|
||||
|
||||
![Dive 2](/assets/images/posts/dive-2.png)
|
||||
|
||||
On voit qu'on a quelques dépendances nix de tirées (à propos des fuseaux horaires par exemple) mais rien lié aux certificats.
|
||||
On va donc injecter là aussi ces certificats dans le conteneur, et pour ce faire on réalise la modification suivante :
|
||||
|
||||
```diff
|
||||
--- flake.nix.old 2024-06-10 09:42:48.871184290 +0200
|
||||
+++ flake.nix 2024-06-10 09:41:48.011959988 +0200
|
||||
@@ -24,7 +24,10 @@
|
||||
# On construit une image Docker qui ne contient que l'app qu'on a build.
|
||||
container = pkgs.dockerTools.buildImage {
|
||||
name = "superboum/tls-static-go";
|
||||
- copyToRoot = tls-static;
|
||||
+ copyToRoot = pkgs.buildEnv {
|
||||
+ name = "tls-static-env";
|
||||
+ paths = [ tls-static pkgs.cacert ];
|
||||
+ };
|
||||
config = {
|
||||
Cmd = [ "/bin/tls-static" ];
|
||||
};
|
||||
```
|
||||
|
||||
Autrement dit, on a créé notre système de fichiers racine en fusionnant le contenu de notre build `tls-static` avec celui du paquet NixOS `cacert`.
|
||||
|
||||
On peut ensuite rebuild / load / run le conteneur avec... succès !
|
||||
|
||||
```
|
||||
$ nix build
|
||||
$ docker load <result
|
||||
Loaded image: superboum/tls-static-go:02bmar6x0q1gi8b6j5ys6j1l84mn5vwh
|
||||
$ docker run --rm -it superboum/tls-static-go:02bmar6x0q1gi8b6j5ys6j1l84mn5vwh
|
||||
2024/06/09 14:45:06 Success
|
||||
```
|
||||
|
||||
Quant à Dive, on voit bien que le bundle de certificat a été correctement injecté au bon endroit :
|
||||
|
||||
![Dive 3](/assets/images/posts/dive-3.png)
|
||||
|
||||
Et voilà, vous avez une image Docker fonctionnelle, minimaliste, reproductible, avec des dépendances correctement déclarées, et maintenable.
|
107
_posts/2024-07-31-img-processor.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
layout: post
|
||||
title: Pensées sur les CDN d'images
|
||||
date: 2024-07-31
|
||||
status: published
|
||||
sitemap: true
|
||||
category: developpement
|
||||
description: Pensées sur les CDN d'images
|
||||
---
|
||||
|
||||
Pour accélérer le chargement d'un site web,
|
||||
réduire la quantité de données transférées,
|
||||
et livrer un format d'image adapté aux appareils utilisés,
|
||||
il est d'usage d'avoir recourt à des services qu'on
|
||||
appelle souvent "image CDN".
|
||||
|
||||
Ces services "de CDN d'images" réalisent, en interne l'encodage
|
||||
à la volée d'une image source vers un format, une qualité, et une résolution spécifique spécifiées dans l'URL.
|
||||
Ces services intègrent possiblement une politique de cache des images générées.
|
||||
|
||||
## État de l'art
|
||||
|
||||
Dans ce domaine, on peut recenser de nombreux acteurs SaaS comme
|
||||
[Netlify Image CDN](https://docs.netlify.com/image-cdn/overview/),
|
||||
[KeyCDN Image Processing](https://www.keycdn.com/image-processing),
|
||||
[Cloudflare Images](https://www.cloudflare.com/fr-fr/developer-platform/cloudflare-images/)
|
||||
ou encore [Akamai Image & Video Manager](https://www.akamai.com/products/image-and-video-manager?image-manager-demo=perceptual-quality).
|
||||
Il existe aussi des solutions à héberger soi-même, comme [imgproxy](https://imgproxy.net/), [imaginary](https://github.com/h2non/imaginary),
|
||||
[thumbor](https://github.com/thumbor/thumbor), [pilbox](https://github.com/agschwender/pilbox), [imageproxy](https://github.com/willnorris/imageproxy) ou encore
|
||||
[picfit](https://github.com/thoas/picfit).
|
||||
Enfin, on peut construire ce genre de services via des bibliothèques dédiées comme [sharp](https://sharp.pixelplumbing.com/) en NodeJS,
|
||||
qui se base sur la bibliothèque C [libvips](https://www.libvips.org/) qui a des bindings dans la plupart des langages.
|
||||
|
||||
## Défis techniques
|
||||
|
||||
Pour tout service informatique se pose des questions de deux ordres : fonctionnel et opérationnel.
|
||||
Le périmètre fonctionnel est bien défini, pour preuve l'homogénéité de fonctionnement de ces services.
|
||||
On peut au besoin se baser sur [l'image API 3.0](https://iiif.io/api/image/3.0/#4-image-requests) de l'IIF si on veut.
|
||||
|
||||
L'aspect opérationnel quant à lui revêt des défis non triviaux, spécifiquement quant on a une approche *computing within limits*.
|
||||
En effet, la conversion d'une image n'est pas une opération négligeable en terme de consommation de CPU & RAM.
|
||||
À celà s'ajoute deux pré-requis particulièrement fort liés à l'aspect "à la volée" du service :
|
||||
1) la conversion doit être réalisée de manière "intéractive" et 2) l'arrivée des requêtes n'est pas prédictible ou uniformément dispersée.
|
||||
|
||||
On peut avoir un premier aperçu des enjeux liés à ce service à travers un benchmark, réalisé vers 2019 - il y a 5 ans à l'écriture de ce billet - par [un dévelopeur](https://gist.github.com/DarthSim) d'une de ces solutions, et intitulé [imgproxy vs alternatives benchmark](https://gist.github.com/DarthSim/9d971d2859f3714a29cf8ce094b3fc55). Le test consiste à redimensionner une image JPEG de 29Mo pour une résolution de 7360x4912 (typiquement une photo prise par un appareil photo réflexe) vers une résolution de 500x500, toujours en JPEG. Le benchmark semble être configuré avec 4 requêtes en parallèle. imgproxy, thumbor, et imaginary se démarquent particulièrement des autres logiciels par leurs bonnes performances : environ 10 images par secondes, entre 200Mo et 400Mo de mémoire vive consommées, autour de 500ms de processing par image.
|
||||
|
||||
Ces chiffres sont loins d'être anodins : étant donné la nature du test, il est raisonable de penser que l'image se trouve dans le cache en mémoire vive.
|
||||
Les 500ms de processing sont donc dus uniquement aux accès mémoires et à la logique de redimenssionnement, et non à l'attente d'entrées-sorties.
|
||||
Autrement dit, la conversion d'une seule image génère un pic de CPU à 100% pendant 500ms.
|
||||
|
||||
Par contre, ce test ne nous dit rien des formats d'images plus récents comme AVIF, HEIC ou même WebP.
|
||||
Si ces formats génèrent des fichiers de plus petites tailles, ils sont aussi connus pour demander d'avantage de ressources CPU.
|
||||
En pratique, cela risque d'amplifier encore le temps d'encodage, particulièrement si l'image générée a une haute résolution.
|
||||
|
||||
Enfin, le domaine des tests de performance est grand. Ce "benchmark" tombe sous le coup du "test de charge" :
|
||||
on envoie 4 requêtes parallèles en continu et on observe comment le système se comporte.
|
||||
Mais quid d'un "stress test", qui dépasse les limites du système, et qui nous permet de voir comment ce dernier se comporte, et comment il *recover* ?
|
||||
|
||||
En effet, que ce soit par maladresse ou par malveillance, il est certain qu'un tel système basé sur des "traitements à la volée"
|
||||
fera rapidement face à des charges de travail qu'il ne pourra pas traiter en temps acceptable (supposons 5 secondes).
|
||||
Que ce soit des images très hautes résolutions de la voute céleste, une grille de miniatures générant 60 images en parallèle, un pic de trafic soudain sur un site web suite à un partage sur les réseaux sociaux, ou quelqu'un de malveillant générant des requêtes volontairement intensives en ressource.
|
||||
|
||||
## Failure mode
|
||||
|
||||
À mon sens, il n'existe aucune autre solution que la conception d'un failure mode.
|
||||
Lorsque qu'une trop grande charge de travail est envoyée au service, ce dernier passe en *failure mode* le temps d'absorber la charge.
|
||||
Une fois la charge absorbée, le service *recover* et repasse dans son mode normal.
|
||||
Ce *failure mode* doit forcément être très efficace, sinon il ne sert à rien.
|
||||
|
||||
On peut d'abord envisager un mode de fonctionnement très direct pour notre *failure mode* : envoyer un code d'erreur HTTP, comme le standard `503 service unavailable` ou le non-standard `529 service overloaded`.
|
||||
|
||||
Plus ambitieux, on peut envoyer une image placeholder à la place, sans directive de cache bien entendu, ce qui permettrait de donner une indication visuelle plus claire aux internautes, et potentiellement de moins casser le site web. Cette image placeholder serait pré-calculée au démarrage du service pour tous les formats supportés (JPEG, HEIC, etc.) et stockée en mémoire vive.
|
||||
|
||||
Se pose encore la question de la taille : si on envoie une taille différente de celle attendue, on peut "casser" le rendu du site. À contrario, générer une image à la bonne taille à la volée demande des calculs, bien que si on complète avec une couleur uniforme, ces calculs puissent possiblement être triviaux en fonction du format considéré.
|
||||
|
||||
Enfin, le problème majeur, c'est que les images sont intégrées de pleins de manières différentes à travers un site web, parfois mélangées avec des filtres : comment s'assurer que notre placeholder sera correctement reçu et compris ?
|
||||
|
||||
*Dans le cadre du développement d'une première itération, la solution des codes d'erreur semble préférable.*
|
||||
|
||||
## Files d'attente
|
||||
|
||||
Reste maintenant à définir comment on bascule dans ce *failure mode*. Et pour se faire, on va partir de conceptions single-thread et multi-thread naïves pour comprendre comment elles échouent. En single-thread, lorsque plusieurs requêtes seront reçues, elles vont s'accumuler soit dans le noyau, soit dans le runtime (eg. nodejs) et une seule sera processée (car on suppose un processus CPU bound sans IO). Les requêtes vont donc s'accumuler, quelques unes vont être process, mais la plupart vont timeout. En multi-thread, on va progresser sur la conversion de plusieurs requêtes en parallèle mais très lentement à chaque fois, au point qu'on va aussi timeout probablement. Dans le cas du multi-thread, on risque aussi d'épuiser les ressources du serveur.
|
||||
|
||||
À la place, on va placer les traitements d'image dans un ou plusieurs fils dédiés mais toujours un nombre inférieur à notre nombre de CPU, pour garder un serveur réactif. Lorsqu'on veut réaliser un taitement, on place notre requête dans une file d'attente. Lorsqu'un fil a fini son traitement, il prend un nouveau *job* dans cette file d'attente. Cette file d'attente est bornée, elle peut donc être pleine, auquel cas on passe dans le *failure mode* tant qu'elle ne s'est pas vidée. Ici, on a formulé notre problème selon [un modèle académique](https://fr.wikipedia.org/wiki/Th%C3%A9orie_des_files_d%27attente) bien connu, et surlequel on peut envisager itérer.
|
||||
|
||||
Une des questions qui se pose est bien entendu "quelle est la bonne borne pour la file d'attente" ? On peut commencer par mettre des valeurs statiques, qui seraient configurées de manière empirique en fonction du type de déploiement. On peut être tenté ensuite de calculer aussi combien de temps va prendre la file d'attente à être traitée, en fonction du type de job (format, taille de l'image, etc.) et des performances passées : ça semble compliqué et hasardeux. À la place, on peut imaginer une gestion inspirée de [CoDel](https://en.wikipedia.org/wiki/CoDel) : une file d'attente est utile si elle permet d'absorber des *burst* sur une courte période, sinon elle est néfaste. On peut donc définir cette courte période : par exemple 5 secondes. Si durant cette période, la file d'attente n'a jamais été vide ou presque (mettons qu'aucune image n'a été traitée en moins de 500ms), alors on est en sur-capacité, on doit passer en *failure mode* et "drop" certains traitements. Il y aurait quelques ajustements à réaliser pour que ça fonctionne - par exemple imposer un temps de traitement maximal par image, ici ce serait 500ms aussi.
|
||||
|
||||
*Dans le cadre du développement d'une première itération, on peut se contenter d'une valeur statique.*
|
||||
|
||||
## Cache
|
||||
|
||||
Bien entendu, un tel système s'entend aussi avec un cache, qui pose son lot de questions : comment on le garbage collect ? est-ce qu'on met une taille maximale à ce dernier ? qu'est-ce qu'on fait si on la dépasse ? On peut voir aussi des synergies entre notre système de fil d'attente et de cache : on pourrait imaginer une seconde file d'attente avec une plus longue période (mettons 2 heures), encaissant donc de plus gros bursts, qui fonctionnerait de manière asynchrone pour hydrater le cache. Les éléments qui ne peuvent pas être ajoutés à la file d'attente synchrone pourraient être ajoutés à la 2nde file d'attente. Ça fonctionnerait particulièrement bien avec les galeries : si il est impossible de générer 60 miniatures au chargement de la page, ces miniatures pourraient être générées en asynchrone pour plus tard.
|
||||
|
||||
Idéalement, le cache serait imputé par utilisateur-ice, directement dans leur bucket. L'expiration des objets seraient réalisée via le système de [Lifecyle](https://docs.aws.amazon.com/AmazonS3/latest/userguide/intro-lifecycle-rules.html) de S3 (non-implémenté dans Garage à ce jour). Avec les lifecycles, il est trivial d'implémenter un pseudo FIFO en expirant tous les objets X jours après leur création, mais moins évident de faire un LRU ou LFU. Sans considérer les lifecycles ni l'imputation par bucket, on peut imaginer une stratégie différente. En utilisant un seul bucket (par instance), on définirait un nombre fixé de "slots", par exemple 1 000, correspondant à une clé `cache0` à `cache999`. Un mapping entre la clé de cache et l'URL de l'image (son identifiant, sa taille, etc.) est maintenu en mémoire et est régulièrement flush, c'est l'index. Ce dernier contient aussi la date de dernier accès, et toute autre information utile/importante pour la stratégie d'eviction du cache. Il se peut que la clé de cache et l'index se désynchronise, afin d'éviter d'envoyer une donnée "corrompue", on vérifie que l'ETag stocké dans l'index correspond à celui de l'objet. Afin d'éviter une explosion du stockage, on met aussi une borne supérieure sur la taille de ce qui peut être stocké dans le cache. Par exemple, avec une borne à 5Mo et 1000 fichiers, notre cache ne dépassera pas 5Go. Enfin, on peut suivre l'efficacité de notre cache en trackant des métriques bien connus sur ce dernier (cache hit, cache miss, etc.).
|
||||
|
||||
*Si on pourrait être tenté dans une première itération de ne pas utiliser S3 pour le cache mais le filesystem ou la mémoire vive, je pense que c'est une erreur. Si le CDN se reschedule sur un autre noeud, on perd le cache, et on risque de passer trop souvent dans le failure mode inutilement, créant du désagrément et de l'incompréhension pour rien auprès des utilisateur-ices.*
|
||||
|
||||
*On peut aussi être tenté d'utiliser des outils de caching existants plutôt que de ré-implémenter notre propre politique de cache. D'abord ça n'est pas évident que ce soit possible dans notre cas d'usage où on a besoin de stocker dans S3. Ensuite, ça nous rendrait impossible l'implémentation ultérieure de l'imputation du stockage à l'utilisateur final.*
|
||||
|
||||
## Conclusion
|
||||
|
||||
Dans ce billet de blog, on a vu que la conversion et redimensionnement des images à la volée consommait beaucoup de ressources CPU & RAM.
|
||||
De ce fait, c'est un défi à mettre en oeuvre dans un environnement contraint en ressources (computing within limits).
|
||||
En s'autorisant un *failure mode*, on peut cependant s'assurer d'une certaine résilience du système face à des pics de charge trop importants, et donc assurer la viabilité d'un tel service. La théorie des fil d'attentes et CoDel sont un exemple de comment & quand basculer entre le *normal mode* et le *failure mode*.
|
||||
Enfin, un système de cache bien conçu permettrait une réduction significative de l'utilisation CPU+RAM pour un coût supplémentaire en stockage modique.
|
||||
Idéalement, le coût supplémentaire en stockage serait imputé à l'utilisateur ; on peut aussi envisager utiliser le cache pour un traitement asynchrone des images, comme la génération d'un grand nombre de miniatures qui ne peut pas être fait de manière synchrone en environnement contraint.
|
||||
|
147
_posts/2024-08-10-fast-ci-build-with-nix.md
Normal file
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
layout: post
|
||||
title: Fast CI builds
|
||||
date: 2024-08-10
|
||||
status: published
|
||||
sitemap: true
|
||||
category: operation
|
||||
description: Fast CI builds
|
||||
---
|
||||
|
||||
Historically, in the good old Jenkins days,
|
||||
a CI build would occure in a workspace
|
||||
that was kept across build. So your previous artifacts
|
||||
could be re-used if they did not change (for example, `make` would detect
|
||||
that some files did not change since that last build and thus did not recompile them).
|
||||
Also it was assumed that all dependencies were directly installed on the machine.
|
||||
Both of these properties allowed for very fast and efficient builds: only
|
||||
what changed needed to be rebuilt.
|
||||
|
||||
This approach had many shortcomings: stale cache would break builds (or wrongly make it work),
|
||||
improper dependency tracking would make building on a new machine very hard, etc.
|
||||
In the end, developers stop trusting the CI that remain broken, bugs start cripling the project and are not noticed,
|
||||
and finally the codebase becomes unmaintainable.
|
||||
|
||||
To avoid these problems, developers started to use a new generation of CI relying on VM (like Travis CI)
|
||||
or containers (like Drone). All builds start with a fresh environment, often a well-known distribution like Ubuntu.
|
||||
Then, for each builds, all the dependencies are installed and the build is done from scratch.
|
||||
Such approach greatly helped developers better track their dependencies and make sure that building their project from scratch remains possible.
|
||||
However, build times skyrocketted. You can wait more than 10 minutes before running a command that would actually check your code.
|
||||
And as recommended by many people[^1][^2][^3] the whole build cycle (lint, build, test) shoud remains below 10 minutes to be useful.
|
||||
|
||||
To speed-up the CI, various optimizations have been explored. CI sometimes propose some sort of caching API, and when it does not, an object store like S3 can be used.
|
||||
This cache is used either by directly copying the dependency folder[^4] (for example the `target/` folder for Rust or the `node_modules/` for Node.JS), or through dedicated tools like `sccache`[^5]. In this scenario, fetching/updating the cache involves a non negligible amount of filesystem+network I/O. Another approach relies on providing your own build image that will often be cached on workers. This image can contain your toolchain (for example Rust + Cargo + Clippy + etc.), but also your project dependencies (by copying your `Cargo.toml` or `package.json` file) and pre-fetching/compiling them. This approach still involves some maintenance burden: image must be rebuilt and published each time a dependency is changed, it's project specific, it can easily break, you still do not track correctly your dependencies, etc.
|
||||
|
||||
**Can we cache without making our builds fragile?**
|
||||
|
||||
## Nix to the rescue
|
||||
|
||||
Following our short discussions, the question that surface is wether or not we can cache efficiently without making our build fragile. Ideally, our project would be split in parts compiled in strict isolation, dependencies between parts would be stricly tracked, cache would be kept locally, and new job would only focus on rebuilding the changed components (avoiding steps like restoring cache & co).
|
||||
|
||||
That's what Nix can do, at least in a theory. A SaaS CI ecosystem start developping around it with solutions like [Garnix](https://garnix.io/) or [Hercules CI](https://hercules-ci.com/).
|
||||
|
||||
But personnaly, I am more interested in FOSS solutions, and thus existing solutions like [Hydra](https://github.com/NixOS/hydra) or [Typhon](https://typhon-ci.org/) seem more baroque. Worse, often a CI system based on Docker is already deployed in your organization (like [Woodpecker](https://woodpecker-ci.org/), [Gitlab Runner](https://docs.gitlab.com/runner/), [Forgejo Actions](https://forgejo.org/docs/v1.20/user/actions/), etc.), and so you didn't really have a choice here: you must use what's already there.
|
||||
|
||||
## The Docker way
|
||||
|
||||
In the following, I will describe a docker deployment that should be generic enough to be adapted to any Docker-based CI system. It's inspired by my own experience[^6] and a blog post by Kevin Cox[^7].
|
||||
|
||||
First, we will spawn a unique `nix-daemon` on the worker, outside of the CI system:
|
||||
|
||||
```bash
|
||||
docker run -i \
|
||||
-v nix:/nix \
|
||||
--privileged \
|
||||
nixpkgs/nix:nixos-22.05 \
|
||||
nix-daemon
|
||||
```
|
||||
|
||||
Then we will mount this `nix` volume as read-only in our jobs. The job will be able to access the store to run the programs it needs. It can add new things to the store by scheduling builds in the daemon through a dedicated UNIX socket. This approach is called *Multi-user Nix: trusted building*[^8].
|
||||
|
||||
```bash
|
||||
docker run -it --rm \
|
||||
-e "NIX_REMOTE=unix:///mnt/nix/var/nix/daemon-socket/socket?root=/mnt" \
|
||||
-e "NIX_CONFIG=extra-experimental-features = nix-command flakes" \
|
||||
-v nix:/mnt/nix:ro \
|
||||
-v `pwd`:/workspace \
|
||||
-w /workspace \
|
||||
nixpkgs/nix:nixos-24.05 \
|
||||
nix build .#
|
||||
```
|
||||
|
||||
Note how the nix daemon and the nix interactive instance have a different version. It's possible as, in the interactive instance, we did not mount the daemon store on the default path (`/nix`) but on another one (`/mnt/nix`) and instructed it to use it in the `NIX_REMOTE` environment variable. This point is important as it enables you to decouple the lifecycle of your worker daemons from the one of your projects, which drastically ease maintenance.
|
||||
|
||||
## A woodpecker integration
|
||||
|
||||
Basically, you want to run your nix-daemon next to your woodpecker agent, for example in a docker-compose. Then, you need to pass specific parameters to your woodpecker agent such that our volume and environment variables are automatically injected to all your builds:
|
||||
|
||||
```yml
|
||||
version: '3.4'
|
||||
services:
|
||||
nix-daemon:
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
restart: always
|
||||
command: nix-daemon
|
||||
privileged: true
|
||||
volumes:
|
||||
- "nix:/nix"
|
||||
|
||||
woodpecker-runner:
|
||||
image: woodpeckerci/woodpecker-agent:v2.4.1
|
||||
restart: always
|
||||
environment:
|
||||
# -- our NixOS / CI specific env
|
||||
- WOODPECKER_BACKEND_DOCKER_VOLUMES=woodpecker_nix:/mnt/nix:ro
|
||||
- WOODPECKER_ENVIRONMENT=NIX_REMOTE:unix:///mnt/nix/var/nix/daemon-socket/socket?root=/mnt,NIX_CONFIG:extra-experimental-features = nix-command flakes
|
||||
# -- change these for each agent
|
||||
- WOODPECKER_HOSTNAME=i_forgot_to_change_my_runner_name
|
||||
- WOODPECKER_AGENT_SECRET=xxxx
|
||||
# -- should not need change
|
||||
- WOODPECKER_SERVER=woodpecker.example:1111
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
|
||||
volumes:
|
||||
nix:
|
||||
```
|
||||
|
||||
Note that the volume is named `woodpeck_nix` and not `nix` in the woodpacker agent configuration (`WOODPECKER_BACKEND_DOCKER_VOLUMES` environment declaration). It's because our `docker-compose.yml` is in a `woodpecker` folder and docker compose prefixes the created volumes with the name of the deployment, by default the parent folder name. The prefix is not needed elsewhere, as elsewhere, the resolution is dynamically done by compose. But the `WOODPECKER_BACKEND_DOCKER_VOLUMES` declaration is not part of compose, it will be used later by woodpecker when interacting directly with the Docker API.
|
||||
|
||||
Then, in your project `.woodpecker.yml`, you can seemlessly use nix and enjoy efficient and quick caching:
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- name: build
|
||||
image: nixpkgs/nix:nixos-24.05
|
||||
commands:
|
||||
- nix build .#
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Anyone having access to your CI will have a read access to your nix store.
|
||||
People will also be able to store data in your `/nix/store`.
|
||||
|
||||
Finally, if I remember correctly, there are some attacks to alter the content of a derivation (such that a content in `/nix/store` is not the product of the hashed derivation). In other words, it's mainly a single-tenant solution.
|
||||
|
||||
So a great evolution would be a multi-tenant system, either by improving the nix-daemon isolation, or by running one nix-daemon per-project or per-user/per-organization. Today, none of these solutions is possible.
|
||||
|
||||
Another limitation is garbage collection: if the nix-daemon can do some garbage collection, none of its policy is interesting for a CI. Mainly, if you activate it, it will ditch everything as it is connected to "no root path" from its point of view. A LRU cache policy would be a great addition. At least, you can manually trigger a garbage collection once your disk is full...
|
||||
|
||||
---
|
||||
|
||||
[^1]: [How long should your CI take](https://graphite.dev/blog/how-long-should-ci-take). *Various industry resources suggest an ideal CI time of around 10 minutes for completing a full build, test, and analysis cycle. As Kent Beck, author of Extreme Programming, said, “A build that takes longer than ten minutes will be used much less often, missing the opportunity for feedback. A shorter build doesn’t give you time to drink your coffee.”*
|
||||
|
||||
[^2]: [Measure and Improve Your CI Speed with Semaphore](https://semaphoreci.com/blog/2017/03/16/measure-and-improve-your-ci-speed.html). *We’re convinced that having a build slower than 10 minutes is not proper continuous integration. When a build takes longer than 10 minutes, we waste too much precious time and energy waiting, or context switching back and forth. We merge rarely, making every deploy more risky. Refactoring is hard to do well.*
|
||||
|
||||
[^3]: [Continuous Integration Certification](https://martinfowler.com/bliki/ContinuousIntegrationCertification.html). *Finally he asks if, when the build fails, it’s usually back to green within ten minutes. With that last question only a few hands remain. Those are the people who pass his certification test.*
|
||||
|
||||
[^4]: [Rust CI Cache](https://blog.arriven.wtf/posts/rust-ci-cache/). *We can cache the build artifacts by caching the target directory of our workspace.*
|
||||
|
||||
[^5]: [My ideal Rust workflow](https://fasterthanli.me/articles/my-ideal-rust-workflow). *The basic idea behind sccache, at least in the way I have it set up, it's that it's invoked instead of rustc, and takes all the inputs (including compilation flags, certain environment variables, source files, etc.) and generates a hash. Then it just uses that hash as a cache key, using in this case an S3 bucket in us-east-1 as storage.*
|
||||
|
||||
[^6]: I [tried writing a CI](https://git.deuxfleurs.fr/Deuxfleurs/albatros/src/commit/373c1f8d76b11a5638b2a4aa753417c67f0c2e13/hcl/nixcache/builder.hcl) on top of Nomad that would wrap a dockerized NixOS, and also deployed [a Woodpecker/Drone CI NixOS runner](https://git.deuxfleurs.fr/Deuxfleurs/nixcfg/src/commit/ca01149e165b3ad1c9549735caa658efda380cd3/cluster/prod/app/woodpecker-ci/integration/docker-compose.yml).
|
||||
|
||||
[^7]: [Nix Build Caching Inside Docker Containers](https://kevincox.ca/2022/01/02/nix-in-docker-caching). *I wanted to see if I could cache dependencies without uploading, downloading or copying them around for each job.*
|
||||
|
||||
[^8]: [Untrusted CI: Using Nix to get automatic trusted caching of untrusted builds](https://www.tweag.io/blog/2019-11-21-untrusted-ci/). *This means that untrusted contributors can upload a “build recipe” to a privileged Nix daemon which takes care of running the build as an unprivileged user in a sandboxed context, and of persisting the build output to the local Nix store afterward.*
|
|
@ -62,5 +62,5 @@
|
|||
@font-face {
|
||||
font-family: 'Symbola';
|
||||
font-display: swap;
|
||||
src: local('Symbola'), url(/assets/fonts/Symbola.ttf) format('truetype');
|
||||
src: local('Symbola'), url(/assets/fonts/Symbola.ornements.woff2) format('woff2');
|
||||
}
|
||||
|
|
BIN
assets/fonts/Symbola.ornements.woff2
Normal file
BIN
assets/images/pages/aerostat.jpg
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
assets/images/pages/crieur.jpg
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
assets/images/pages/foule.jpg
Normal file
After Width: | Height: | Size: 265 KiB |
BIN
assets/images/pages/imprimerie.jpg
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
assets/images/pages/peintres.jpg
Normal file
After Width: | Height: | Size: 880 KiB |
BIN
assets/images/pages/think.jpg
Normal file
After Width: | Height: | Size: 287 KiB |
BIN
assets/images/pages/vesoul.jpg
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
assets/images/posts/dive-2.png
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
assets/images/posts/dive-3.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
assets/images/posts/dive.png
Normal file
After Width: | Height: | Size: 140 KiB |
|
@ -1,25 +0,0 @@
|
|||
---
|
||||
title: Cours
|
||||
profile: false
|
||||
---
|
||||
|
||||
### Développement logiciel pour le Cloud (TLC)
|
||||
|
||||
Slides:
|
||||
|
||||
* [Slides 1: Introduction to cloud computing](tlc-course-1.pdf)
|
||||
* [Slides 2: Microservices](tlc-course-2.pdf)
|
||||
* [Slides 3: Analytics](tlc-course-3.pdf)
|
||||
* [Slides 4: Data Management](tlc-course-4.pdf)
|
||||
* [Slide 4.1 : DHT (Pastry)](tlc-course-4.1.pdf)
|
||||
* [Slide 4.2 : NoSQL (Dynamo DB)](tlc-course-4.2.pdf)
|
||||
|
||||
Travaux dirigés :
|
||||
|
||||
* [TD1 : Base de données NoSQL : Google Datastore](td-datastore.pdf)
|
||||
|
||||
Lab:
|
||||
|
||||
* [Lab 1: Découverte d'un PaaS : Google AppEngine](tlc-tp1.pdf)
|
||||
* [Lab 2: Découverte d'un orchestrateur de conteneurs : Kubernetes](tlc-tp2.pdf)
|
||||
* [Lab 3: Découverte d'un IaaS : AWS EC2](tlc-tp3.pdf)
|
|
@ -1,50 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Small web IDE</title>
|
||||
<meta charset="utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Small web IDE</h1>
|
||||
<script src="https://unpkg.com/aws4-tiny@1.0.0/dist/aws4.js"></script>
|
||||
<script>
|
||||
let aki = localStorage.getItem('accessKeyId');
|
||||
if (aki === null) {
|
||||
aki = window.prompt("What is your Garage Access Key ID?");
|
||||
localStorage.setItem('accessKeyId', aki);
|
||||
}
|
||||
|
||||
let sak = localStorage.getItem('secretAccessKey');
|
||||
if (sak === null) {
|
||||
sak = window.prompt("What is you Garage Secret Access Key?");
|
||||
localStorage.setItem("secretAccessKey", sak);
|
||||
}
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: aki,
|
||||
secretAccessKey: sak,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
region: 'garage',
|
||||
service: 's3',
|
||||
host: 'garage.deuxfleurs.fr',
|
||||
path: '/quentin.dufour.io/?list-type=2',
|
||||
headers: {
|
||||
'x-amz-content-sha256': 'UNSIGNED-PAYLOAD',
|
||||
},
|
||||
};
|
||||
|
||||
const req = aws4.sign(opts, credentials);
|
||||
console.log(req)
|
||||
async function ListObjectsV2() {
|
||||
const res = await fetch('https://garage.deuxfleurs.fr/quentin.dufour.io/?list-type=2', req);
|
||||
const body = await res.text()
|
||||
console.log("body raw", body)
|
||||
const objList = new window.DOMParser().parseFromString(body, "text/xml")
|
||||
console.log("parsed body", objList)
|
||||
}
|
||||
ListObjectsV2()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet href="./feed.xsl" type="text/xsl"?>
|
||||
|
||||
<!-- https://support.google.com/podcast-publishers/answer/9889544?hl=fr -->
|
||||
<rss version="2.0"
|
||||
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel>
|
||||
<itunes:owner>
|
||||
<itunes:email>thomas.gauledoin@deuxfleurs.fr</itunes:email>
|
||||
</itunes:owner>
|
||||
<itunes:author>Tom Goldoin</itunes:author>
|
||||
|
||||
<title>Un bout du net</title>
|
||||
<description>
|
||||
Internet : réseau créé pour des militaires, des chercheurs ou des hippies ?
|
||||
Dès ses origines, Internet a été porté par la vision des individus qui le façonnait.
|
||||
Et si on revenait sur ces temps forts ?
|
||||
</description>
|
||||
<language>fr-fr</language>
|
||||
<item>
|
||||
<title>La naissance du cloud</title>
|
||||
<description>Le cloud est partout aujourd'hui mais toujours aussi incompréhensible. Pour y voir plus clair, remontons à ses origines chez Amazon en 2003.</description>
|
||||
<pubDate>Thu, 24 Nov 2022 12:00:00 GMT</pubDate>
|
||||
<enclosure url="https://quentin.dufour.io/podcast/origine-cloud.mp3" type="audio/mpeg" length=""/>
|
||||
<itunes:duration>4:59</itunes:duration>
|
||||
<guid isPermalink="false">origine-cloud</guid>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://natclark.com/tutorials/xslt-style-rss-feed/ -->
|
||||
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
|
||||
<xsl:template match="/">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title><xsl:value-of select="/rss/channel/title"/></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge,chrome=1" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css" />
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><xsl:value-of select="/rss/channel/title"/></h1>
|
||||
<p><em><xsl:value-of select="/rss/channel/description"/></em></p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<xsl:for-each select="/rss/channel/item">
|
||||
<article>
|
||||
<h3><xsl:value-of select="title"/></h3>
|
||||
<p><xsl:value-of select="description"/></p>
|
||||
<audio controls="1" >
|
||||
<xsl:attribute name="src">
|
||||
<xsl:value-of select="enclosure/@url"/>
|
||||
</xsl:attribute>
|
||||
</audio>
|
||||
</article>
|
||||
</xsl:for-each>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
https://thinkproduct.org/2022/10/09/aws-story-internets-os/
|
||||
https://mediatemple.net/blog/cloud-hosting/brief-history-aws/
|
||||
|
23
index.html
|
@ -10,11 +10,16 @@ layout: default
|
|||
<section id="who-am-i">
|
||||
<h2>Qui suis-je ?</h2>
|
||||
<p>
|
||||
Je suis ingénieur en informatique et libriste.
|
||||
J'ai travaillé sur <a href="https://www.torproject.org/">Tor</a> pendant <a href="https://theses.fr/s204646">ma thèse</a>.
|
||||
Je suis membre de <a href="https://deuxfleurs.fr">Deuxfleurs</a>, un hébergeur associatif expérimental.
|
||||
Nous développons des logiciels libres comme <a href="https://garagehq.deuxfleurs.fr/">Garage</a> pour un monde plus résilient et souverain.
|
||||
Le reste du temps, je suis disponible pour vous accompagner sur vos projet informatiques en <em>freelance</em>, écrivez-moi !
|
||||
Je suis <a href="https://www.astrolabe.coop/members/quentin-dufour/">dévelopeur logiciel indépendant</a>, j'ai une formation <a href="https://www.insa-rennes.fr/info.html">d'ingénieur en informatique généraliste</a> & une <a href="https://team.inria.fr/wide/team/quentin-dufour/">thèse en systèmes distribués</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
J'ai participé à la conception et développement d'une <a href="https://garagehq.deuxfleurs.fr/">alternative à Amazon S3</a>, <a href="https://aerogramme.deuxfleurs.fr/">un serveur email</a>, ou encore <a href="https://rapsodie.co/">d'un moteur de jeu de carte à la Magic</a>.
|
||||
</p>
|
||||
|
||||
</p>
|
||||
Je peux vous aider à concevoir ou améliorer vos services existants : contactez-moi
|
||||
pour me faire part de votre projet. Et dans tous les cas, bonne lecture !
|
||||
</p>
|
||||
|
||||
<nav class="list">
|
||||
|
@ -29,13 +34,19 @@ layout: default
|
|||
<a href="/cv.pdf">CV</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:quentin+web@dufour.io">Email</a> (<a href="/pgp.pem">PGP</a>)
|
||||
<a href="mailto:quentin@dufour.io">Email</a> (<a href="/pgp.pem">PGP</a>)
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://matrix.to/#/@quentin:deuxfleurs.fr">Matrix</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
<p style="font-family: monospace">
|
||||
#linux #raft #gossip #s3 #dynamo #email #cloud #ops #rust #scheme #fp #sre #availability #types #architecture #http #k8s #python #mypy #js #rescript #golang
|
||||
#c10k #async #hashistack #nixos #ci #otel
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="posts">
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
---
|
||||
title: Mic
|
||||
permalink: portfolio/mic/
|
||||
profile: false
|
||||
---
|
||||
|
||||
# Mic - import de fichiers audio
|
||||
|
||||
Mic est un petit logiciel **qui ne nécessite pas d'installation** pour faciliter l'import de fichiers
|
||||
depuis des supports USB comme les Micro-enregistreurs [Easi-Speak](https://www.amazon.fr/Micro-enregistreur-Jaune-Easi-Speak/dp/B002UP5QB6).
|
||||
|
||||
## Captures d'écrans
|
||||
|
||||
![Import des fichiers](/assets/images/pages/mic01.png)
|
||||
![Visualisation des fichiers importés](/assets/images/pages/mic02.png)
|
||||
![Fichiers dans l'explorateur Windows](/assets/images/pages/mic03.png)
|
||||
|
||||
## Fonctionnement
|
||||
|
||||
Lors de l'import, il est possible d'assigner le fichier à un élève et un exercice.
|
||||
Pour chaque exercice, un dossier est créé. Le fichier importé est renommé
|
||||
pour contenir dans son nom :
|
||||
|
||||
* le nom de l'élève
|
||||
* la date et l'heure de l'import
|
||||
* le nom du fichier original
|
||||
|
||||
<br/>
|
||||
Les fichiers sont importés dans un dossier nommé **import** à l'endroit où se trouve
|
||||
le programme.
|
||||
|
||||
## Pré-Requis
|
||||
|
||||
Vous pouvez directement utiliser Mic avec les versions de Windows suivantes :
|
||||
|
||||
* Windows 7 SP1 (si votre Windows 7 est à jour, vous avez la SP1)
|
||||
* Windows 8 et Windows 8.1
|
||||
* Windows 10
|
||||
|
||||
<br/>
|
||||
Vous devez préalablement télécharger & installer [Microsoft .NET Framework 4](https://www.microsoft.com/fr-fr/download/details.aspx?id=17851) si vous utilisez :
|
||||
|
||||
* Windows XP
|
||||
* Windows Vista
|
||||
* Windows 7 sans la SP1 (si vous avez désactivé les mises à jour sur Windows 7)
|
||||
|
||||
<br/>
|
||||
Ce logiciel ne fonctionne pas (pour le moment) sous Linux ou MacOS.
|
||||
|
||||
## Téléchargement et installation
|
||||
|
||||
Voici les étapes à suivre :
|
||||
|
||||
1. [Cliquez ici pour télécharger Mic](https://ci.deuxfleurs.fr/job/Mic/job/master/lastSuccessfulBuild/artifact/Mic/bin/Release/mic-windows-portable.zip)
|
||||
2. Décompressez le fichier .zip sur votre clé USB, dans votre espace réseau, dans "Documents" ou n'importe où ailleurs !
|
||||
3. Double-cliquez sur Mic.exe (ou juste Mic)
|
||||
4. C'est tout ! Il n'y a pas d'installation !
|
||||
|
||||
<br/>
|
||||
Une fenêtre d'import va s'ouvrir quand vous brancherez un microphone
|
||||
|
||||
## Fonctionnalités manquantes
|
||||
|
||||
Les actions suivantes ne peuvent pas être réalisées depuis le logiciel :
|
||||
|
||||
1. Il n'est pas possible de changer l'élève assigné à un fichier après son import
|
||||
2. Il n'est pas possible de changer l'exercice assigné à un fichier après son import
|
||||
3. Il n'est pas possible de supprimer un fichier importé
|
||||
4. Le bouton "Vider le micro" ne fonctionne pas
|
||||
|
||||
<br/>
|
||||
|
||||
Ces actions peuvent cependant être réalisées depuis l'explorateur de fichier Windows,
|
||||
après tout ce ne sont que des dossiers et des fichiers !
|
||||
|
||||
Si vous trouvez un bug ou que vous souhaitez que ces fonctionnalités ou que d'autres fonctionnalités soient ajoutées, merci de me contacter par email : [quentin@dufour.io](mailto:quentin@dufour.io).
|
133
votre-site-web.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
layout: post
|
||||
title: Votre site web autour d'un café
|
||||
date: 2023-10-28T00:00:00.000+02:00
|
||||
sitemap: false
|
||||
---
|
||||
|
||||
<!--*Cette page est à destination des personnes qui ont un projet de site web
|
||||
amateur que je prévois d'accompagner bénévolement en vue de les héberger chez [Deuxfleurs](https://deuxfleurs.fr). Bonne lecture.*-->
|
||||
|
||||
Vous voulez mettre un site web en ligne pour votre projet et vous cherchez *un geek* qui sache faire ? Halte là ! Je peux vous aider, mais ça va être un brin plus complexe.
|
||||
|
||||
Un site web n'est pas qu'un défi technique : c'est avant tout du contenu, souvent textuel, parfois multimédia. Il y a donc un travail intellectuel préliminaire : il faut réfléchir à sa cible, ce qu'on veut lui transmettre, et prendre sa plus belle plume...
|
||||
|
||||
Le document qui suit vise à vous accompagner dans cette tâche. Une fois votre contenu prêt, nous pouvons nous retrouver un après-midi autour d'un café pour la mise en ligne.
|
||||
|
||||
## Avant propos
|
||||
|
||||
![Ville de Vesoul, image d'illustration](/assets/images/pages/vesoul.jpg)
|
||||
|
||||
Avant de nous lancer, se pose la question de savoir si je suis le bon interlocuteur.
|
||||
|
||||
D'abord sur la finalité : aujourd'hui un site web peut être un ensemble de documents (comme ce site) ou une application (comme GMail, Zoom, Facebook, etc.). Si les deux sont souvent appelés "sites web" et sont utilisés au sein d'un même logiciel, le navigateur (Firefox, Chrome, Edge, etc.), je parle bien ici d'un ensemble de documents et non d'une application.
|
||||
|
||||
Ensuite, je suis très attaché à la dimension amateure et communautaire du web, qui n'est pas du tout synonyme d'amateurisme. D'abord parce que j'aime l'idée que tout le monde soit autorisé à s'exprimer sur Internet. Mais aussi parce que cela génère souvent des démarches plus altruistes, qui produisent du contenu plus pertinent et original. Il existe même des [moteurs de](https://wiby.me/) [recherche](https://search.marginalia.nu/) dédiés à cette niche.
|
||||
|
||||
Si je suis [ingénieur en informatique](/cv.pdf), concevoir des sites web n'est pas mon métier (c'est d'ailleurs plusieurs métiers très différents : [Designer Graphique](https://www.onisep.fr/ressources/Univers-Metier/Metiers/designer-graphique), [Intégrateur Web](https://www.onisep.fr/ressources/Univers-Metier/Metiers/integrateur-integratrice-web), [Rédacteur Web](https://www.onisep.fr/ressources/Univers-Metier/Metiers/redacteur-redactrice-on-line), etc.) - je prétends juste avoir une bonne compréhension des aspects techniques. Si vous cherchez une prestation professionnelle, il vaut mieux prévoir un budget et vous adresser à une agence web.
|
||||
|
||||
|
||||
Vous êtes encore là ? Bien ! En plus de la conception de votre site web, se pose la question de sa "mise en ligne". L'association [Deuxfleurs](https://deuxfleurs) dont je fais parti se chargera de cette partie. Son slogan "Fabriquer un Internet convivial" s'inscrit dans la continuité de notre démarche amateure. À travers [son blog](https://plume.deuxfleurs.fr/~/Deuxfleurs) et ses choix techniques, l'association interroge aussi la place qu'on veut accorder au numérique dans notre société.
|
||||
|
||||
|
||||
## L'apparence
|
||||
|
||||
![Image d'illustration. Les artistes du jardin des plantes en 1902](/assets/images/pages/peintres.jpg)
|
||||
|
||||
Après cette introduction sur *le web amateur*, vous ne savez peut-être pas à quoi vous attendre. Pour vous faire une idée, le site web de l'association [Envie Appart'Agée](https://www.envieappartagee.fr/) est un exemple de ce qu'on peut créer avec [un outil facile](https://getpublii.com/) à prendre en main.
|
||||
|
||||
|
||||
Si vous voulez pousser plus loin, certaines personnes ont aussi décidé d'apprendre [à coder des sites webs](https://openclassrooms.com/fr/courses/1603881-creez-votre-site-web-avec-html5-et-css3) (c'est beaucoup plus facile que ça en a l'air), ce qui permet de personnaliser le rendu comme on le souhaite. Ça peut être [très sobre](https://giraud.eu/), [très original](https://anneprudhomoz.fr/accueil.html), ou encore [très maitrisé](https://colineaubert.com/).
|
||||
|
||||
L'apparence de votre site web va influencer sa réception bien entendu, mais il n'a pas besoin d'être *beau*. En utilisant des [typographiques](https://fr.wikipedia.org/wiki/Typographie) avec [empatements](https://fr.wikipedia.org/wiki/Empattement_(typographie)), des [lettrines](https://fr.wikipedia.org/wiki/Lettrine), des [ornements](https://fr.wikipedia.org/wiki/Ornement_(typographie)), peu d'image, et une mise en page sobre, vous allez [signifier](https://visualdsgn.fr/semiologie-saussure-pierce-barthes/) que le contenu se rapporte à l'écriture, et par extension, à la sphère intellectuelle. Au contraire, si vous mettez des grandes images artificielles, des animations et autres procédés tape à l'œil, vous allez d'avantage signifier une vitrine de magasin.
|
||||
|
||||
Aujourd'hui, les sites commerciaux étant dominants, c'est souvent eux qui définissent la norme de "ce à quoi devrait ressembler votre site web". Mais en réalité si vous êtes une association, ou que vous souhaitez partager un loisir, ça envoie des signaux contradictoires de ressembler à une vitrine...
|
||||
|
||||
On ne va donc pas trop se concentrer sur les normes esthétiques, mais on va faire attention aux signes qu'on mobilise.
|
||||
|
||||
|
||||
## La motivation
|
||||
|
||||
![Image d'illustration. Gravure d'une femme assise à son bureau réfléchissant](/assets/images/pages/think.jpg)
|
||||
|
||||
Souvent la question du site web commence par une évidence : de nos jours, il faudrait être sur Internet ou risquer [une fracture numérique](https://books.openedition.org/pressesenssib/1940). C'est un mauvais point de départ selon moi car 1) tout n'a pas besoin d'être informatisé et 2) une [approche mimétique](https://fr.wikipedia.org/wiki/Culte_du_cargo) vise à reproduire quelque chose sans que ce soit nécessairement pertinent.
|
||||
|
||||
Un meilleur point de départ est de se demander ce que vous voulez dire, faire connaître et à qui. Et peut-être que vous n'aurez rien à dire (pour le moment), ou à des gens dont Internet n'est pas le média privilégié. Ce n'est pas grave, investissez votre énergie là où ça compte à la place. Autrement dit, votre site web doit faire partie d'une stratégie plus large de votre part : de communication, d'apprentissage, d'expression artistique, etc.
|
||||
|
||||
|
||||
<!--Il est fort probable qu'une part de votre public ait un smartphone ou un ordinateur. Et que vous ayez besoin de communiquer des informations basiques, comme une adresse, des évènements, des comptes sur les réseaux sociaux, etc. Et donc un site web pourrait faire partie de votre stratégie pour permettre aux gens de garder le contact avec vous et se tenir au courant de ce que vous faites. Vous pourriez aussi simplement vouloir partager vos recettes de cuisine avec votre entourage. Ou partager des connaissances sur la permaculture avec vos paires maraîchers. Tout est possible, mais votre objectif final n'est pas le site web.-->
|
||||
|
||||
C'est donc bien de préciser votre objectif, car ça guidera vos choix d'une part, et d'autre part comment je vous aiderai. Si c'est une fin en soi, comme pour apprendre ou une démarche artistique, alors mieux vaut que vous fassiez un maximum par vous même, et que vous exploriez. Si c'est un moyen pour communiquer, alors mieux vaut s'assurer que vous êtes sur les bons rails. La suite de cette page prend le parti que votre site web est un support pour communiquer.
|
||||
|
||||
## La cible
|
||||
|
||||
![Image d'illustration. Une gravure de foule](/assets/images/pages/foule.jpg)
|
||||
|
||||
Avant de réfléchir à ce qu'on va écrire, on va réfléchir à votre cible : qui est t'elle et que cherche t'elle ? L'enjeu pour vous c'est de comprendre *dans quels contextes* les gens vont intéragir avec vous et/ou votre projet. Il y a souvent plusieurs contextes, et donc plusieurs cibles.
|
||||
|
||||
Prenons l'exemple d'une autrice. Elle peut vouloir adresser son site web à son lectorat. Mais elle pourrait aussi vouloir l'adresser à des maisons d'édition, à la presse, etc. Et elle pourrait vouloir aussi partager des conseils d'écriture : dans ce cas, la cible sont d'autres auteurs. Il vous revient donc de 1) recenser les différentes cibles et 2) identifier les informations pertinentes.
|
||||
|
||||
Pour certaines cibles, Internet ne sera pas pertinent du tout : soit parce qu'elles ne sont pas à l'aise avec cet outil, soit parce que consulter le web pour obtenir cette information précise n'est pas une évidence. Il est tout à fait possible de vouloir adresser son site web à une seule de ses cibles : c'est plus simple. Quand on a plusieurs cibles, c'est une stratégie de faire une page par cible (comme certains sites webs découpés en "pour les particuliers", "pour les entreprises", etc.).
|
||||
|
||||
## Le format
|
||||
|
||||
![Image d'illustration. Gravure d'une imprimerie](/assets/images/pages/imprimerie.jpg)
|
||||
|
||||
Si un site web peut être une page blanche sur laquelle votre [créativité](https://anaissiere.club1.fr/nuit/) [peut](https://estherbouquet.com/) [s'exprimer](https://anaissiere.club1.fr/amour/) [sans fin](https://melvynpharaon.club1.fr/myel/), avoir des point de référence pour commencer facilite le processus. Je vous propose 4 métaphores par ordre de difficulté pour expliquer les différentes directions que peut prendre votre site web : la *carte de visite*, la *brochure*, le *catalogue* et le *livre*.
|
||||
|
||||
La *carte de visite* est l'incarnation la plus simple d'un site web : une seule page qui donne des informations de contact traditionnelles (numéro de téléphone, adresse physique, adresse email) et/ou redirige vers vos réseaux sociaux. Elle s'adresse à des gens qui *vous connaissent déjà* et qui *savent déjà ce que vous faites* mais qui veulent retrouver des informations sur vous. Pour faire une carte de visite, vous n'avez besoin que de rassembler les informations de contact & de comptes de réseaux sociaux que vous voulez partager. Facile !
|
||||
|
||||
La *brochure* (ou portfolio), en plus des informations de contact, va donner des informations sur ce que vous faites et ou proposez. Elle peut donc s'adresser à des gens qui ont *entendu parler de vous*, *qui connaissent votre domaine* mais qui ne *savent pas exactement ce que vous faites*. Il faut donc lister vos activités : prestations, évènements, livres, formations, ateliers, œuvres, etc.
|
||||
|
||||
<!--Si vous organisez des évènements, ou que vous avez une actualité, vous pouvez penser une place à cette dernière - tout en gardant en tête que c'est une charge de travail conséquente de garder un site web à jour. Là aussi une seule page web suffit, par contre, par rapport à la carte de visite, elle demande un travail de production de contenu (rédaction de textes, photos, etc.).-->
|
||||
|
||||
Le *catalogue*, par rappport à la brochure, vise un nouveau public : *des gens qui ne vous connaissent pas* mais qui *connaissent votre domaine*. Ces gens vont donc chercher par mots clés, via un moteur de recherche (Google, Qwant, Lilo, etc.), des personnes comme vous. Pour être indexé dans le moteur de recherche, vous ne pouvez plus vous contenter de lister vos activités, il va falloir les décrire. Par exemple, si vous faites des formations sur le tricot, à qui ça s'adresse (débutants ?), ce qu'on va y apprendre (le point mousse ? le point jersey ?), ce qu'on va faire (tricoter une écharpe ?), etc.
|
||||
|
||||
Le *livre* est la forme la plus aboutie : c'est un site web qui contient des ressources qui se suffisent à elles-mêmes : des tutoriels, des guides pratiques, des fiches références, de l'actualité de votre domaine, etc. Il peut toucher des *gens qui ne vous connaissent pas* et *ne connaissent pas (encore) votre domaine*. Ça peut permettre de créer une communauté de gens qui suivent votre travail, un très fort engagement vis-à-vis de ce que vous faites, et vous placez en tant que référence dans votre domaine. Attention, ça demande *vraiment* beaucoup de travail, et le résultat n'est pas garanti : si vous tentez, faites le pour vous (motivation intrinsèque) et non parce que vous attendez des retombées (motivation extrinsèque).
|
||||
|
||||
|
||||
Ces métaphores fonctionnent bien comme des poupées russes : la plupart des site webs ont une section contact, une large majorité parle de ce que la personne ou le collectif fait, un peu moins décrivent en détail ce qui est fait, et très peu fournissent des ressources clés en main en accès libre. Je vous recommande donc de commencer petit, et de jauger ce que vous étendez au fur et à mesure des retours, des envies et de votre temps. N'hésitez pas à réfléchir et à explorer d'autres métaphores physiques pour réfléchir au contenu que vous voulez partager.
|
||||
|
||||
Pour commencer, je vous recommande de partir des informations que l'on vous demande déjà souvent : vous savez qu'il y a déjà un intérêt pour ces dernières.
|
||||
|
||||
## Exister
|
||||
|
||||
![Image d'illustration. Une gravure de crieur](/assets/images/pages/crieur.jpg)
|
||||
|
||||
Contrairement aux réseaux sociaux qui ont des algorithmes de recommendation qui permettent de faire découvrir votre contenu sans action de votre part, ce n'est pas vraiment le cas des sites web. Une fois votre site web créé ce n'est donc pas terminé : il faut encore en assurer la promotion, du moins le faire connaître.
|
||||
|
||||
Pour faire connaître votre site web, il va falloir partager son adresse (ou URL).
|
||||
L'adresse de mon site est `https://quentin.dufour.io`. Sur les navigateurs modernes, on peut n'entrer qu'une partie de l'URL, le nom de domaine : `quentin.dufour.io` (notez la disparition de `https://`), ce qui est plus simple à écrire et retenir. Beaucoup de personnes ne maîtrisent pas ces concepts et ont tout le temps recourt à la recherche par mots-clés dans un moteur de recherche (Google, Lilo, etc.), par exemple : `Quentin Dufour Informatique`. Il va falloir composer avec ces contraintes.
|
||||
|
||||
*Vous vous demandez peut-être comment obtenir un nom de domaine à votre identité et combien ça coûte ? Il faut compter environ 15 euros/an pour un .fr et il faut s'adresser à [un bureau d'enregistrement](https://www.afnic.fr/lexique/#bureaux-enregistrement) comme [Gandi](https://www.gandi.net/fr/domain). Ça fait partie de la technique qu'on peut faire ensemble.*
|
||||
|
||||
Sur vos documents écrits ou à l'oral, je vous conseille donc de prévoir d'indiquer votre nom de domaine (comme `quentin.dufour.io`). Si vous avez des raisons de penser que la personne va avoir du mal à vous trouver comme ça, il est bon de connaître les quelques mots-clés qui vont vous permettre de vous trouver à coup sûr dans un moteur de recherche mais...
|
||||
|
||||
...ça n'est pas si simple d'être référencé (indexé) dans un moteur de recherche. Il y a 2 choses principales à savoir : 1) ça prend du temps et 2) c'est une boite noire. Si tout se passe bien, votre site commencera a être référencé 1 mois après sa publication. Mais parfois, pour des raisons obscures, il peut ne pas être référencé du tout : il faut alors tatonner pour comprendre pourquoi. La compétition au référencement a créé [des métiers du référencement](https://www.onisep.fr/ressources/Univers-Metier/Metiers/charge-chargee-de-referencement-web) qui prétendent connaître les arcanes des moteurs de recherche et vous promettent monts et merveilles. Selon moi, plutôt que d'essayer de tromper le système, il est préférable de suivre les recommendations : 1) avoir d'autres sites web qui font des liens vers le votre, 2) publier du contenu original et de qualité et 3) publier régulièrement du contenu.
|
||||
|
||||
Diffuser l'existence de votre site web c'est bien, faire revenir votre public quand vous avez publié quelque chose de nouveau (des nouveaux évènements, une nouvelle actualité, de nouvelles informations, etc.) c'est encore mieux ! Si vous êtes sur les réseaux sociaux (Instagram, Facebook, etc.), vous pouvez annoncer cette mise à jour par ce biais, en mettant un lien. Si les groupes What's App, Signal, Telegram, ou autres sont populaires parmi vos cercles, ça peut être un autre moyen d'atteindre votre public. Aujourd'hui [les newsletters](https://www.radiofrance.fr/franceinter/podcasts/net-plus-ultra/net-plus-ultra-du-vendredi-22-septembre-2023-3758867) font aussi leur grand retour : l'email est loin d'être mort. Si vous faites parti de réseaux, vous pouvez essayer de compter sur eux pour relayer vos informations (office de tourisme, fédérations en tout genre, etc.) à travers leurs propres canaux de communication.
|
||||
|
||||
Dans un premier temps, il n'est pas forcément nécessaire d'aller aussi loin : simplement mentionner à vos interlocuteurs que vous avez un site web est un bon début, et le reste sera du bonus !
|
||||
|
||||
|
||||
## On se lance ?
|
||||
|
||||
![Image d'illustration. Une gravure de Montgolfière](/assets/images/pages/aerostat.jpg)
|
||||
|
||||
|
||||
Vous avez maintenant toutes les cartes en main. Il ne vous reste plus qu'à prendre votre cahier ou [votre logiciel de traitement de texte](https://fr.wikipedia.org/wiki/Logiciel_de_traitement_de_texte) préféré,
|
||||
et coucher vos idées sur la papier (ou sur l'écran).
|
||||
Voici un récapitulatif des éléments auxquels vous devez réfléchir - quelques lignes peuvent suffire :
|
||||
- Dans quelle stratégie s'inscrit votre projet de site web ?
|
||||
- Quelle est votre cible (ou quelles sont vos cibles) et que cherchent t'elles selon vous ?
|
||||
- Quel format souhaitez-vous adopter pour votre contenu (carte de visite, brochure, catalogue, etc.) ?
|
||||
- En fonction du format que vous voulez, écrire les blocs de texte qui apparaîtront
|
||||
- Rassembler des images, si nécessaire, qui serviront à illustrer votre site
|
||||
- Si vous avez déjà des supports de communication, les rassembler aussi : on pourra les réutiliser pour créer du contenu.
|
||||
- Notez quelques idées sur comment vous allez faire connaître votre site web
|
||||
- Réfléchissez au nom de domaine que vous voudriez
|
||||
|
||||
Je suis conscient que ça fait beaucoup de choses, mais elles me semblent essentielles pour la réussite de votre projet. Rien ne presse : si vous n'avez pas le temps maintenant, ça peut être plus tard.
|
||||
|
||||
Une fois prêt·e, on peut planifier une demi-journée ensemble pour mettre en place la partie technique. Vous pouvez trouver mes informations de contact sur [ma page d'accueil](https://quentin.dufour.io).
|
||||
|