[{"content":"Les slides de ma présentation sur les images Docker.\n","permalink":"https://cyrillesondag.github.io/blog/presentation-containers/","summary":"\u003cp\u003eLes slides de ma présentation sur les images Docker.\u003c/p\u003e\n\u003cdiv id=\"Container\"\n style=\"padding-bottom:56.25%; position:relative; display:block; width: 100%\"\u003e\n \u003ciframe id=\"googleSlideIframe\"\n  width=\"100%\" height=\"100%\"\n  src=\"https://slides.com/cyrillesondag/copy-of-copy-of-les-images-dockers/embed\"\n  frameborder=\"0\" allowfullscreen=\"\"\n  style=\"position:absolute; top:0; left: 0\"\u003e\u003c/iframe\u003e\n\u003c/div\u003e","title":"Les images Docker"},{"content":"Molecule est un framework de test Ansible. Il permet en isolation de tester les roles (mais aussi les playbooks) Ansible.\nC\u0026rsquo;est une réelle aide pour s\u0026rsquo;assurer de la non-régression mais également pour accélérer le processus de développement.\nInit Donc d\u0026rsquo;abord pour commencer, nous allons créer un role vide et explorer la structure proposée par Molecule.\nEn explorant la commande init role on obtient le résultat suivant :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 molecule init role --help Usage: molecule init role [OPTIONS] ROLE_NAME Initialize a new role for use with Molecule, namespace is required outside collections, like acme.myrole. Options: --dependency-name [galaxy] Name of dependency to initialize. (galaxy) -d, --driver-name [delegated|openstack|podman] Name of driver to initialize. (delegated) --lint-name [yamllint] Name of lint to initialize. (yamllint) --provisioner-name [ansible] Name of provisioner to initialize. (ansible) --verifier-name [ansible|testinfra] Name of verifier to initialize. (ansible) Ok donc créons notre nouveau rôle :\n1 bash# molecule init role my_company.my_new_role -d podman Cela aura pour effet de créer un nouveau role dans ./my_new_role\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cd my_new_role/ \u0026amp;\u0026amp; tree . ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── molecule │ └── default │ ├── converge.yml │ ├── molecule.yml │ └── verify.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml Au-delà de la structure classique d\u0026rsquo;un rôle, nous allons surtout nous intéresser au répertoire molecule qui contient tous les éléments nécessaires aux tests.\nAu premier niveau un répertoire default, il s\u0026rsquo;agit du scenario par défaut, celui qui sera exécuté si aucune option n\u0026rsquo;est spécifiée (-s \u0026lt;nom_du_scenario\u0026gt; ou le nom du scenario est celui du répertoire).\nEnsuite à l\u0026rsquo;intérieur le plus important molecule.yml.\nMolecule.yml C\u0026rsquo;est le fichier qui est requis dans chaque scenario. Il va servir à décrire et configurer : le driver, le(s) plateforme(s), le provisioner, le linter et le verifier\nDans notre exemple il ressemble à cela :\n1 2 3 4 5 6 7 8 9 10 11 12 13 --- dependency: name: galaxy driver: name: podman platforms: - name: instance image: quay.io/centos/centos:stream8 pre_build_image: true provisioner: name: ansible verifier: name: ansible dependency (doc) Molecule utilise Ansible galaxy comme gestionnaire de dépendances. Cet objet va permettre de configurer ansible-galaxy pour installer les rôles et collections requises.\ndriver (doc) Ici on spécifie le nom du pilote à utiliser pour accéder aux instances des plateformes sur lesquelles les tests seront exécutés.\nIl existe différents types de drivers : podman, openstack, vagrant, docker, azure\u0026hellip; qui correspondent chacun à une technologie pour créer des instances de tests (vm ou container).\nLe driver delegated est quant à lui un peu particulier, car à la différence des autres il n\u0026rsquo;est pas chargé de créer / détruire les instances de test.\nOn peut l\u0026rsquo;utiliser par exemple pour se connecter à une machine préexistante en renseignant seulement une clé ssh.\nLes drivers ne sont pas présents par défaut, ils doivent être installés dans les librairies Python de la façon suivante (ici Docker) :\n1 pip install \u0026#39;molecule[docker]\u0026#39; platforms (doc) C\u0026rsquo;est dans ce dictionnaire que nous allons retrouver la configuration de nos instances. Ces configurations sont bien évidemment dépendantes du driver utilisé (bien qu\u0026rsquo;il puisse y avoir des similitudes).\nLeurs noms doivent être uniques (ils serviront de hostname pour ansible), sans limitation de nombre. On peut définir pour chaque instance un nom de groupe (et des children) pour générer l´inventaire de notre test.\nCette partie de configuration n\u0026rsquo;est pas forcément toujours très très bien documentée. Pour voir l\u0026rsquo;ensemble des options de configuration, il est souvent plus simple de se référer directement au code du driver (ou dans la section \u0026lsquo;Common uses cases\u0026rsquo;). Par exemple pour Docker on peut trouver le détail ici\nprovisionner (doc) Cet objet est là où est défini la configuration d\u0026rsquo;Ansible. Il va être chargé à l\u0026rsquo;aide de playbook classique ansible de réaliser les actions suivantes :\ncreate : création de l\u0026rsquo;ensemble des plateformes à l\u0026rsquo;aide du driver. Ce playbook n\u0026rsquo;est appelé qu\u0026rsquo;une fois durant la durée de vie des instances de test prepare : utilisé par le tester pour appliquer les prérequis. Ce playbook n\u0026rsquo;est appelé qu\u0026rsquo;une fois durant la durée de vie des instances de test converge : applique le role en cours de test sur les instances. idempotence : re-applique le role et vérifie qu\u0026rsquo;aucun changed n\u0026rsquo;ait été déclenché side_effect : utilisé pour des tests de haute disponibilité (pas activé par défaut) verify : réalise les assertions sur les instances de tests (activé uniquement sur verifier = ansible) cleanup (*) : utilisé pour nettoyer les ressources avant le destroy destroy (*) : utilisé pour supprimer les plateformes de test. Les playbooks des actions sont définis par défaut à la racine du scenario sous la forme ./$action.yml.\nIl est également possible de définir des playbooks spécifiques par driver de la façon suivante ./$driver/$action.yml.\nEnfin certains playbooks sont automatiquement définis par le driver lui-même.\nPar exemple le driver docker va utiliser les arguments du dictionnaire des plateformes pour générer des Dockerfile via une template jinja2 et lancer la création des containers via un playbook Ansible.\nIl est bien évidemment possible de définir des playbooks comme bon nous semble pour réaliser les actions que l\u0026rsquo;on souhaite.\nEnfin, c\u0026rsquo;est aussi dans cet objet provisioner que l\u0026rsquo;on va pouvoir configurer ansible : niveau de log, variables, liens\u0026hellip; etc\nverifier (doc) Le verifier est chargé de valider le bon déroulement du rôle, en réalisant des assertions sur les plateformes de test.\nPar défaut Ansible est utilisé, les tests s\u0026rsquo;écrivent comme n\u0026rsquo;importe quel playbook (via par exemple le module assert)\nHistoriquement testinfra était privilégié pour cette tâche, dorénavant Ansible l\u0026rsquo;a remplacé. À titre personnel, je trouve que la syntaxe Ansible n\u0026rsquo;est pas vraiment efficace pour réaliser ce genre de tâche tant c\u0026rsquo;est verbeux à écrire. J\u0026rsquo;imagine que l\u0026rsquo;homogénéité a primé sur l\u0026rsquo;efficacité, car testinfra lui doit être écrit en python.\nLes scenarios (je me demande s\u0026rsquo;il n\u0026rsquo;y a pas une confusion entre les termes : scénario répertoire où se trouve le fichier molecule.yml et les scénarios la liste des séquences des actions)\nPour la bonne compréhension de l\u0026rsquo;outil je remets ici la liste par défaut des actions réalisées par molecule :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 scenario: create_sequence: - dependency - create - prepare check_sequence: - dependency - cleanup - destroy - create - prepare - converge - check - destroy converge_sequence: - dependency - create - prepare - converge destroy_sequence: - dependency - cleanup - destroy test_sequence: - dependency - lint - cleanup - destroy - syntax - create - prepare - converge - idempotence - side_effect - verify - cleanup - destroy Ces séquences peuvent être appelées par les commandes suivantes :\n1 molecule [create|check|converge|destroy|test] NB: il peut être utile en phase de développement de travailler uniquement avec le converge, ainsi les phases \u0026ldquo;couteuses\u0026rdquo; (create et prepare) ne sont appelées qu\u0026rsquo;une fois et on peut travailler plus rapidement sur notre propre environnement.\nConclusion Voilà qui conclut cet article, Molecule est un outil indispensable à Ansible. Il souffre néanmoins de quelques lacunes : documentation pas toujours très claire, pas de doc sur les drivers, la configuration du provisioner n\u0026rsquo;est pas simple à configurer dans des cas un peu complexes, et comme je l\u0026rsquo;ai dit plus le verifier ansible ne me semble pas idéal.\nJe ne suis pas rentré beaucoup dans les détails mais tout est évidemment hautement paramétrable.\nPour autant une fois le pas franchi et la configuration mise en place, c\u0026rsquo;est un réel plaisir à utiliser (merci le molecule login)\n","permalink":"https://cyrillesondag.github.io/blog/ansible-molecule/","summary":"\u003cp\u003eMolecule est un framework de test Ansible. Il permet en isolation de tester les roles (mais aussi les playbooks) Ansible.\u003c/p\u003e\n\u003cp\u003eC\u0026rsquo;est une réelle aide pour s\u0026rsquo;assurer de la non-régression mais également pour accélérer le processus de développement.\u003c/p\u003e\n\u003ch2 id=\"init\"\u003eInit\u003c/h2\u003e\n\u003cp\u003eDonc d\u0026rsquo;abord pour commencer, nous allons créer un role vide et explorer la structure proposée par Molecule.\u003c/p\u003e\n\u003cp\u003eEn explorant la commande \u003ccode\u003einit role\u003c/code\u003e on obtient le résultat suivant :\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e13\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e14\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emolecule init role --help\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUsage: molecule init role \u003cspan class=\"o\"\u003e[\u003c/span\u003eOPTIONS\u003cspan class=\"o\"\u003e]\u003c/span\u003e ROLE_NAME\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  Initialize a new role \u003cspan class=\"k\"\u003efor\u003c/span\u003e use with Molecule, namespace is required outside\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  collections, like acme.myrole.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eOptions:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --dependency-name \u003cspan class=\"o\"\u003e[\u003c/span\u003egalaxy\u003cspan class=\"o\"\u003e]\u003c/span\u003e      Name of dependency to initialize. \u003cspan class=\"o\"\u003e(\u003c/span\u003egalaxy\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  -d, --driver-name \u003cspan class=\"o\"\u003e[\u003c/span\u003edelegated\u003cspan class=\"p\"\u003e|\u003c/span\u003eopenstack\u003cspan class=\"p\"\u003e|\u003c/span\u003epodman\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  Name of driver to initialize. \u003cspan class=\"o\"\u003e(\u003c/span\u003edelegated\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --lint-name \u003cspan class=\"o\"\u003e[\u003c/span\u003eyamllint\u003cspan class=\"o\"\u003e]\u003c/span\u003e          Name of lint to initialize. \u003cspan class=\"o\"\u003e(\u003c/span\u003eyamllint\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --provisioner-name \u003cspan class=\"o\"\u003e[\u003c/span\u003eansible\u003cspan class=\"o\"\u003e]\u003c/span\u003e    Name of provisioner to initialize. \u003cspan class=\"o\"\u003e(\u003c/span\u003eansible\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --verifier-name \u003cspan class=\"o\"\u003e[\u003c/span\u003eansible\u003cspan class=\"p\"\u003e|\u003c/span\u003etestinfra\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  Name of verifier to initialize. \u003cspan class=\"o\"\u003e(\u003c/span\u003eansible\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eOk donc créons notre nouveau rôle :\u003c/p\u003e","title":"Ansible Molecule"},{"content":" Jean Auguste Dominique Ingres - Portrait d\u0026rsquo;une jeune femme (1766–1817), huile sur toile, 59.6 x 73.2 cm, Hull (UK), Ferens Art Gallery.\nCet article sera consacré à l\u0026rsquo;architecture de systemd.\nLe cœur de systemd est basé sur quelques piliers : UDev et DBus qui permettent de mettre en place une approche évènementielle, les CGroup pour l\u0026rsquo;encapsulation des processus et le contrôle des ressources et plus récemment EBPF pour les métriques.\nArchitecture de systemd - By Shmuel Csaba Otto Traian, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=28698339\nIl se base sur plusieurs principes forts que je vais tenter de détailler ci-dessous.\nRetarder l\u0026rsquo;initialisation L\u0026rsquo;un des objectifs initiaux du projet était d\u0026rsquo;améliorer le temps de démarrage des systèmes jugé à juste titre trop long.\nIl faut garder à l\u0026rsquo;esprit qu\u0026rsquo;il a été pensé pour répondre à ces différents cas d\u0026rsquo;utilisation (serveur, station de travail, embarqué\u0026hellip;). Si le temps de démarrage n\u0026rsquo;est (souvent) pas crucial pour un serveur, ce n\u0026rsquo;est absolument pas le cas pour une station de travail ou pour un mobile.\nPour arriver à cela plusieurs types d\u0026rsquo;unit sont chargés uniquement de l\u0026rsquo;activation des services lors de leur première utilisation. On peut citer les types : socket, path, automount \u0026hellip;\nCela permet d\u0026rsquo;éviter de lancer un service trop tôt (bien souvent au démarrage) et ainsi optimiser le temps de d\u0026rsquo;initialisation.\nPour réaliser cela systemd utilise plusieurs méthodes d\u0026rsquo;activation :\nLes Socket. Le principe est assez simple et est largement inspiré de ce qui se faisait déjà sur inetd. Mais contrairement à ce dernier, de nombreux types de socket sont maintenant supportés : UNIX, INET, named pipes, netlink\u0026hellip;\nOn peut résumer les étapes de la façon suivante :\nDes buffers sont alloués automatiquement au démarrage pour chaque service qui le demande. Lors d\u0026rsquo;un appel, ces buffers se remplissent jusqu\u0026rsquo;à une taille limite au-delà de laquelle l\u0026rsquo;écriture devient impossible. Dans ce cas le client se met alors en attente et l\u0026rsquo;appel devient bloquant (c\u0026rsquo;est ce qui se passe d\u0026rsquo;ailleurs lorsqu\u0026rsquo;il attend une réponse). Les descripteurs de fichiers sont scrutés et au premier message le service consommateur est démarré en parallèle. Enfin on lui transmet le(s) socket(s) en paramètres afin qu\u0026rsquo;il puisse les lire une fois initialisé. En cas d\u0026rsquo;échec de démarrage, rien n\u0026rsquo;est lu et le service peut être relancé sans pertes d\u0026rsquo;informations. Néanmoins, attention le passage de sockets n\u0026rsquo;est pas un comportement \u0026ldquo;natif\u0026rdquo; sous Linux. Il faut donc que le service soit compatible avec systemd (c\u0026rsquo;est heureusement le cas de nombreux serveurs web entre autres).\nGrâce à ce comportement, on peut non seulement activer un service au premier appel, mais également briser les chaines de dépendances entre services et paralléliser leur activation.\nPar exemple dans le cas d\u0026rsquo;un service qui nécessite syslog : Il est possible de lancer les deux en parallèle, les messages syslog seront envoyés mis en attente dans le buffer avant d\u0026rsquo;être dépilé à l\u0026rsquo;initialisation complète de syslog.\nUDev UDev est un daemon qui permet d\u0026rsquo;exposer dans l\u0026rsquo;espace utilisateur les périphériques de façon dynamique. Il prend en charge le hot-plug mais aussi pour les périphériques classiques.\nC\u0026rsquo;est un daemon qui écoute dans l\u0026rsquo;espace utilisateur les évènements publiés par le kernel via netlink (un socket entre le kernel et l\u0026rsquo;espace utilisateur).\nIl fait ensuite la liaison entres les informations du sysfs et déclenche des règles spécifiques écrites par un administrateur.\nCe projet a acquis une telle importance qu\u0026rsquo;il fait maintenant partie à part entière de systemd.\nPrenons un exemple concret de comment utiliser UDev avec systemd.\nTout d\u0026rsquo;abord nous pouvons écouter les évènements publiés par le kernel et UDev ainsi :\n1 udevadm monitor Ce qui donne ce résultat lors de l\u0026rsquo;activation de bluetooth sur mon portable\n1 2 3 4 5 6 7 8 9 10 11 12 monitor will print the received events for: UDEV - the event which udev sends out after rule processing KERNEL - the kernel uevent KERNEL[88503.920800] change /devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.0/bluetooth/hci0/rfkill0 (rfkill) UDEV [88503.926695] change /devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.0/bluetooth/hci0/rfkill0 (rfkill) KERNEL[88521.147695] add /devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.0/bluetooth/hci0/hci0:256 (bluetooth) UDEV [88521.151331] add /devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.0/bluetooth/hci0/hci0:256 (bluetooth) KERNEL[88522.280203] add /devices/virtual/input/input24 (input) KERNEL[88522.280341] add /devices/virtual/input/input24/event18 (input) UDEV [88522.282180] add /devices/virtual/input/input24 (input) UDEV [88522.319399] add /devices/virtual/input/input24/event18 (input) La première partie concerne la mise en route du récepteur bluetooth, la seconde la connexion du casque audio.\nOn voit apparaître un nouveau device sous l\u0026rsquo;arborescence /devices/virtual/input/input24.\nEnsuite on cherche à écrire une règle UDev qui va s\u0026rsquo;activer uniquement pour ce périphérique. Pour cela il nous faut donc chercher les attributs discriminants dans les événements UDev que l\u0026rsquo;on peut voir à l\u0026rsquo;aide de la commande (attention les chemins UDev sont toujours relatifs aux répertoires /sys ou /dev/):\n1 udevadm info -a /sys/devices/virtual/input/input24 Je serais bien incapable d\u0026rsquo;expliquer la signification de tous les attributs, par contre je peux en reconnaître quelque uns comme le nom et son adresse (qui me semble assez unique) :\n1 2 3 4 5 6 7 8 9 10 KERNEL==\u0026#34;input24\u0026#34; SUBSYSTEM==\u0026#34;input\u0026#34; DRIVER==\u0026#34;\u0026#34; .... ATTR{inhibited}==\u0026#34;0\u0026#34; ATTR{name}==\u0026#34;LG-TONE-FP9 (AVRCP)\u0026#34; ATTR{phys}==\u0026#34;0c:dd:24:30:06:00\u0026#34; ATTR{power/async}==\u0026#34;disabled\u0026#34; ATTR{power/control}==\u0026#34;auto\u0026#34; .... Ensuite avec ces informations on est en mesure d\u0026rsquo;écrire une règle appropriée pour sélectionner ce matériel de la façon suivante :\n1 SUBSYSTEM==\u0026#34;input\u0026#34;, ATTR{name}==\u0026#34;LG-TONE-FP9 (AVRCP)\u0026#34;, ATTR{phys}==\u0026#34;0c:dd:24:30:06:00\u0026#34;, TAG+=\u0026#34;systemd\u0026#34;, ENV{SYSTEMD_ALIAS}+=\u0026#34;/sys/devices/virtual/input/lg_tone_fp9\u0026#34; Les premiers éléments servent à sélectionner les attributs dans les événements UDev qui vont déclencher la règle : le nom du system, l\u0026rsquo;attribut nom et son adresse.\nPuis la partie importante pour l\u0026rsquo;intégration avec systemd est TAG+=\u0026quot;systemd. Ce tag permet de marquer le périphérique pour être interprété comme une unit \u0026ldquo;.device\u0026rdquo;.\nC\u0026rsquo;est d\u0026rsquo;ailleurs la seule chose à faire pour faire fonctionner cette règle avec systemd.\nJ\u0026rsquo;ai ajouté l\u0026rsquo;attribut SYSTEMD_ALIAS= car le chemin du périphérique n\u0026rsquo;est pas prévisible (/sys/devices/virtual/input/input\u0026lt;X\u0026gt;).\nComme le nom de la unit est déterminé par son chemin il est plus simple qu\u0026rsquo;il soit toujours le même. Maintenant ce device va apparaître systématiquement sous le nom sys-devices-virtual-input-lg_tone_fp9.device.\nOn est donc en mesure d\u0026rsquo;écrire un service qui va s\u0026rsquo;activer lors de la présence de ce device.\n1 2 3 4 5 6 [Unit] Wants=sys-devices-virtual-input-lg_tone_fp9.device [Service] ExecStart=/bin/echo headphone connected RemainAfterExit=true Il existe également la possibilité d\u0026rsquo;activer un service directement à partir d\u0026rsquo;une règle UDev.\nD-Bus D-Bus est un nouvel framework d\u0026rsquo;IPC (inter-process communication) qui permet de communiquer de façon standardisée entre différents services. Il permet - entre autre - de faire :\ndu RPC (Remote Procedure Call) en offrant une sérialisation standardisée. de sécuriser les communications via un mécanisme d\u0026rsquo;autorisation. de l\u0026rsquo;activation à la demande via un système de nommage. de la découverte de service \u0026hellip; Systemd est complètement intégré avec D-Bus (l\u0026rsquo;architecture des deux produits est par ailleurs assez similaire).\nIl est ainsi possible d\u0026rsquo;interagir avec systemd uniquement via les API D-BUS.\nPour voir les services exposés par DBus il suffit de taper la commande :\n1 2 3 4 ~ busctl list --acquired NAME PID PROCESS USER CONNECTION UNIT SESSION DESCRIPTION ... org.freedesktop.systemd1 1 systemd root :1.1 init.scope - - On peut également voir l\u0026rsquo;ensemble des propriétés d\u0026rsquo;un service\n1 ~ busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1 Puis pour lister les propriétés, méthodes d\u0026rsquo;un service (systemd ici) :\n1 ~ busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1 Cela donne accès à l\u0026rsquo;ensemble des objets accessibles via D-BUS (c\u0026rsquo;est aussi un bon moyen pour connaître les valeurs des propriétés par défaut).\nOn peut également écrire un service activable lors de la présence d\u0026rsquo;un bus de la façon suivante :\n1 2 3 4 5 6 7 Unit] Description=Simple DBus service [Service] Type=dbus BusName=org.example.simple-dbus-service ExecStart=/usr/sbin/simple-dbus-service Bref les possibilités sont impressionnantes, tellement l\u0026rsquo;intégration des deux systèmes est poussée.\nInotify Inotify est un mécanisme qui permet d\u0026rsquo;observer les actions sur un système de fichiers.\nIl permet de s\u0026rsquo;abonner à un répertoire ou fichier et de recevoir les types d\u0026rsquo;évènements suivants :\nIN_ACCESS : le fichier est accédé en lecture ; IN_MODIFY : le fichier est modifié ; IN_ATTRIB : les attributs du fichier sont modifiés ; IN_OPEN : le fichier est ouvert ; IN_DELETE : un fichier a été supprimé dans le répertoire surveillé ; IN_CREATE : un fichier a été créé dans le répertoire surveillé. \u0026hellip; En symétrie nous allons donc retrouver sous systemd les fonctions suivantes PathExists=, PathChanged=, PathModified=, DirectoryNotEmpty=\nAttention - comme spécifié dans la documentation - inotify souffre de certaines limitations, il n\u0026rsquo;est par exemple pas possible de scruter les évènements produits par une machine distante sur une filesystem en réseau (type NFS).\nAutofs Autofs est un module du kernel qui permet de retarder le montage d\u0026rsquo;un FS. Pour cela il crée des mount-trap (des dentry avec un attribut particulier) qui au premier accès (au-delà du stat) va faire appel à un daemon - dans notre cas systemd - qui va effectuer le montage. Par la suite le filesystem pourra être utilisé comme un FS habituel.\nCela offre deux avantages :\nalors que l\u0026rsquo;initialisation classique attend le montage de tous les FS (qui dans le cas de FS en réseaux peut être assez long), ici nous n\u0026rsquo;avons plus à attendre cette étape. cela permet également de paralléliser les montages. Paralléliser On l\u0026rsquo;a vu plus haut - en plus du chargement à la demande - beaucoup de composants facilitent la parallélisation des actions et font ainsi gagner en efficacité.\nDe son côté systemd se base sur son modèle de dépendances et cherche à minimiser les points de synchronisations qui ralentissent le démarrage.\nPour cela il va construire un arbre de dépendances pour pouvoir lancer un maximum de services en parallèle en ayant à attendre uniquement ceux qui lui sont nécessaires.\nPour arriver à cela des ordonnancements - souvent implicites - sont mis en place suivant différents critères afin de garantir la bonne cohérence des actions (c\u0026rsquo;est d\u0026rsquo;ailleurs souvent la partie la plus complexe à mettre en œuvre à mon avis).\nPour illustrer mes propos voici le graphique de démarrage, pour atteindre la default.target :\nBootup graph\nÀ noter quelques points qui me semblent importants :\nLorsque systemd active un nombre de unit - et si aucun ordre n\u0026rsquo;est spécifié - elles sont faites en parallèle. Par défaut les services s\u0026rsquo;exécutent après la sysinit.target (ou à la basic.target en mode user). Pour passer outre il faut ajouter la propriété DefaultDependencies=false. Comme souvent des outils sont mis à disposition pour nous aider : On peut citer systemd-analyse plot (en SVG) ou systemd-analyse dot (au format DOT) qui permettent de générer des représentations graphiques de ce que nous venons d\u0026rsquo;aborder.\nL\u0026rsquo;utilisation des cgroups Contrairement à ce que l\u0026rsquo;on pourrait croire le cgroups ne sont pas utilisés (du moins au départ) dans systemd pour le contrôle de resources, mais pour le contrôle des processus.\nIl s\u0026rsquo;avère en pratique compliqué de terminer correctement un service ayant un nombre de processus forkés important.\nPour tenter de comprendre le problème, revenons sur quelques notions UNIX :\nUn processus est rattaché à une session. Par convention le SID il est égal au PID du premier membre de la session, le \u0026ldquo;session leader\u0026rdquo;. La session peut être un terminal, une connexion ssh\u0026hellip; ou autre.\nDans une session on trouve un certain nombre de groupes de processus. Le PGID est égal au PID du premier membre du groupe le \u0026ldquo;process group leader\u0026rdquo; (vous voyez la logique). Au sein d\u0026rsquo;une session un seul groupe peut être au premier plan.\nUn processus PID a (presque) toujours un parent et hérite de certains de ses attributs (UID, GID, SID, PGID\u0026hellip;). Il est lui-même composé de plusieurs threads.\nSi en théorie c\u0026rsquo;est simple, en pratique, c\u0026rsquo;est assez compliqué : liberté d\u0026rsquo;implémentation, différences entre les systèmes UNIX, manque de syscall\u0026hellip; Tout cela a fait qu\u0026rsquo;il est difficile de suivre correctement l\u0026rsquo;ensemble des processus issus de différents forks.\nPar exemple l\u0026rsquo;une des techniques couramment utilisées pour lancer un processus en arrière-plan (daemon) est la technique dite du \u0026ldquo;double-fork\u0026rdquo;. Elle consiste à effectuer les opérations suivantes :\nUn premier fork() pour créer un processus enfant de façon classique. Qui va ensuite appeler setsid(0) pour se détacher de la session courante. Pour enfin effectuer un nouveau fork() pour que ce changement soit pris en compte. Le processus nouvellement créé est ainsi rendu orphelin et ne peut plus interagir avec la session d\u0026rsquo;origine (et vice versa). Du point de vue d\u0026rsquo;un init manager, le problème est qu\u0026rsquo;il n\u0026rsquo;a connaissance que du PID issu du retour du premier fork, et pas de celui issu du second et éventuellement des autres processus forkés.\nUne parade consistait à stocker le résultat du second fork dans un fichier PID file pour pouvoir le retrouver. Cette méthode est tout à fait valide, mais souffre de limites en cas de nouveau fork et est de plus très dépendante de l\u0026rsquo;implémentation qui en est faite.\nOn peut me faire remarquer que systemd utilise aussi des pid file, oui mais :\nc\u0026rsquo;est surtout pour assurer la rétrocompatibilité avec les anciens scripts. ce n\u0026rsquo;est pas exactement utilisé pour la même fonction (principalement par le watchdog). Pour pallier ce problème, systemd utilise les CGroup.\nPrésent depuis longtemps dans le kernel Linux les CGroups sont prévus pour régler ce type de problème. À la différence d\u0026rsquo;autres propriétés un processus ne peut pas s\u0026rsquo;échapper d\u0026rsquo;un CGroup (à moins de créer un nouveau sous CGroup qui reste néanmoins toujours le descendant du parent). Il est de plus assez aisé de recenser l\u0026rsquo;ensemble des processus d\u0026rsquo;un CGroup.\nSystemd a donc décidé de lancer tous les services dans leurs propres CGroup, cela permet de résoudre une bonne fois pour toutes les problèmes d\u0026rsquo;échappement des processus.\nEt encore une fois fournit un outil pour nous aider :\n1 systemd-cgls ","permalink":"https://cyrillesondag.github.io/blog/systemd-part-three/","summary":"\u003cfigure\u003e\u003ca href=\"Jean_Auguste_Dominique_Ingres_-_Portrait_of_a_Young_Woman_%28formerly_thought_to_be_Mme_de_Sta%c3%abl,_1766%e2%80%931817%29.jpg\"\u003e\n    \u003cimg\n        \n            sizes=\"(min-width: 35em) 1200px, 100vw\"\n            \n            srcset='\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-three/Jean_Auguste_Dominique_Ingres_-_Portrait_of_a_Young_Woman_%28formerly_thought_to_be_Mme_de_Sta%C3%ABl,_1766%E2%80%931817%29_hu_b6df43bd59b9fdae.jpg 480w,\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-three/Jean_Auguste_Dominique_Ingres_-_Portrait_of_a_Young_Woman_%28formerly_thought_to_be_Mme_de_Sta%C3%ABl,_1766%E2%80%931817%29_hu_16d971948921ad07.jpg 800w,\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-three/Jean_Auguste_Dominique_Ingres_-_Portrait_of_a_Young_Woman_%28formerly_thought_to_be_Mme_de_Sta%C3%ABl,_1766%E2%80%931817%29_hu_4e1ef40c3db9f7a0.jpg 1200w,\n            \n                   \n            '\n\n            \n            \n            src=\"https://cyrillesondag.github.io/blog/systemd-part-three/Jean_Auguste_Dominique_Ingres_-_Portrait_of_a_Young_Woman_%28formerly_thought_to_be_Mme_de_Sta%C3%ABl,_1766%E2%80%931817%29_hu_16d971948921ad07.jpg\"\n            \n\n        \n            alt=\"Jean Auguste Dominique Ingres - Portrait d\u0026rsquo;une jeune femme (1766–1817), huile sur toile, 59.6 x 73.2 cm, Hull (UK), Ferens Art Gallery.\"/\u003e \u003c/a\u003e\u003cfigcaption style=\"font-size: 80%; text-align: center;\"\u003e\n            \u003cp\u003eJean Auguste Dominique Ingres - Portrait d\u0026rsquo;une jeune femme (1766–1817), huile sur toile, 59.6 x 73.2 cm, Hull (UK), Ferens Art Gallery.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003cp\u003eCet article sera consacré à l\u0026rsquo;architecture de \u003cem\u003esystemd\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eLe cœur de systemd est basé sur quelques piliers : \u003cem\u003eUDev\u003c/em\u003e et \u003cem\u003eDBus\u003c/em\u003e qui permettent de mettre en place une approche évènementielle, les \u003cem\u003eCGroup\u003c/em\u003e pour l\u0026rsquo;encapsulation des processus et le contrôle des ressources et plus récemment \u003cem\u003eEBPF\u003c/em\u003e pour les métriques.\u003cbr\u003e\n\u003cbr/\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\u003ca href=\"Systemd_components.svg.png\"\u003e\n    \u003cimg\n        \n            sizes=\"(min-width: 35em) 1200px, 100vw\"\n            \n            srcset='\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-three/Systemd_components.svg_hu_bf7855a7b1ad0e55.png 480w,\n            \n                   \n            \n                   \n            \n                   \n            '\n\n            \n            \n            src=\"https://cyrillesondag.github.io/blog/systemd-part-three/Systemd_components.svg.png\"\n            \n\n        \n            alt=\"Architecture de systemd - By Shmuel Csaba Otto Traian, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=28698339\"/\u003e \u003c/a\u003e\u003cfigcaption style=\"font-size: 80%; text-align: center;\"\u003e\n            \u003cp\u003eArchitecture de systemd - By Shmuel Csaba Otto Traian, CC BY-SA 3.0, \u003ca href=\"https://commons.wikimedia.org/w/index.php?curid=28698339\"\u003ehttps://commons.wikimedia.org/w/index.php?curid=28698339\u003c/a\u003e\u003c/p\u003e","title":"Systemd - partie 3"},{"content":" Gustave Caillebotte - Périssoires sur l\u0026rsquo;Yerres (1877), huile sur toile, 103 × 155 cm, Milwaukee (USA), Milwaukee Art Museum.\nNous avons vu dans le précédent article quelques différences entre SysV init et systemd, ainsi que le mécanisme d\u0026rsquo;initialisation de l\u0026rsquo;espace utilisateur par le kernel. Dans cet article, je vais aborder plus en détail le fonctionnement de systemd.\nLes Managers L\u0026rsquo;objet manager est central, il contient les valeurs par défaut des différentes propriétés du système et est responsable de la gestion des units qui lui sont attachées. C\u0026rsquo;est avec lui que les \u0026ldquo;utilisateurs\u0026rdquo; vont communiquer.\nIl est chargé de propager les actions appelées job (par exemple : start, stop, reload\u0026hellip;) vers les units et d\u0026rsquo;interagir avec les éléments tiers du système (watchdog, inotify, D-bus, cgroups\u0026hellip;).\nIl en existe sous 2 formes ayant chacune des portées différentes :\nLa première (celle par défaut) appelée \u0026ldquo;system\u0026rdquo; est unique par machine et est normalement lancée par le processus d\u0026rsquo;init avec les droits root.\nOn peut vérifier que systemd est bien l\u0026rsquo;init manager du système de la façon suivante :\n1 2 # ls -ls /usr/sbin/init /usr/sbin/init -\u0026gt; /lib/systemd/systemd Ou plus simplement utiliser cette commande :\n1 systemctl La seconde est \u0026ldquo;user\u0026rdquo;, il est unique par utilisateur et hérite des mêmes droits que ce dernier. Ce type d\u0026rsquo;instance s\u0026rsquo;avère particulièrement utile pour limiter les droits des services qui lui sont associés (rootless).\nPour connaître l\u0026rsquo;état d\u0026rsquo;une instance utilisateur on peut utiliser la commande suivante :\n1 systemctl --user Bien spécifier l\u0026rsquo;argument --user devant toutes les commandes pour interagir avec l\u0026rsquo;instance de l\u0026rsquo;utilisateur.\nLa gestion des Units On l\u0026rsquo;a vu les managers sont responsables de la gestion des units.\nUne unit chargée par un manager elle est unique si son nom est unique (pour les templates, les alias, les fragments, le nom est résolu au runtime).\nLes units files sont lues au premier \u0026ldquo;référencement\u0026rdquo; (par exemple lors de leur activation, ou bien via de liens de dépendances\u0026hellip;), transformées en unit et mise en cache dans la mémoire du manager qui leur est associé.\nElles sont stockées à l\u0026rsquo;intérieur d\u0026rsquo;une HashMap sous la forme nom/valeur (d\u0026rsquo;où leur unicité).\nPour cette raison, il faut forcer le ramasse-miette (GC) ce qui a pour effet de sérialiser sur disque l\u0026rsquo;état des units actives, vider le cache du manager et recharger la configuration des units via leurs unit file (ce n\u0026rsquo;est pas néanmoins pas toujours nécessaire, mais ça évite des erreurs).\nPour forcer le ramasse miette on utilise la commande suivante :\n1 systemctl daemon-reload Arborescence et précédence La définition des unit files répondent à une précédence très précise utilisée afin de faciliter l\u0026rsquo;administration du système et sa mise à jour.\nPar ordre d\u0026rsquo;importance croissante (non exhaustif) en mode \u0026ldquo;\u0026ndash;system\u0026rdquo; :\n/lib/systemd/system/* : installés par le gestionnaire de paquets (il est recommandé d\u0026rsquo;éviter de les modifier sous peine de pertes lors des mises à jour du paquet) /usr/local/lib/systemd/system/* : gérés par l\u0026rsquo;administrateur /run/systemd/system/* : générées au runtime /etc/systemd/system/* : gérés par l\u0026rsquo;administrateur Ainsi une unit file définie dans le répertoire /etc/systemd/system/* sera prioritaire sur celle définie dans /lib/systemd/system/*\nEn mode \u0026ldquo;\u0026ndash;user\u0026rdquo; la hiérarchie est un peu différente :\n/lib/systemd/user/* : installés par le gestionnaire de paquets (ne doivent pas être modifiés sous peine de pertes lors des mises à jour) /usr/local/lib/systemd/user/* : gérés par l\u0026rsquo;administrateur /run/systemd/user/* : générées au runtime /etc/systemd/user/* : gérés par l\u0026rsquo;administrateur ~/.config/systemd/user/* : géré par l\u0026rsquo;utilisateur Ça peut paraître compliqué aux premiers abords, mais limite le périmètre des différents intervenants sur une machine (utilisateurs, administrateurs, packageurs\u0026hellip;) pour qu\u0026rsquo;ils ne se marchent sur les pieds.\nDe plus des outils spécifiques sont fournis (on le verra plus loin) pour aider à la compréhension de ce mécanisme.\nFragment Configuration Les fragments configuration sont des fichiers qui permettent de modifier les propriétés d\u0026rsquo;une ou d\u0026rsquo;un groupe de units sans avoir à la/les redéfinir entièrement. Ces fichiers répondent aux mêmes ordres de précédence que nous venons d\u0026rsquo;aborder plus haut.\nPour surcharger, il est possible de définir des fichiers .conf qui seront lus par ordre alphabétique dans les formats suivants :\nPar type de unit sous la forme \u0026lt;unit_type\u0026gt;.d/*.conf. Ainsi un fragment dans un répertoire service.d/*.conf s\u0026rsquo;appliquera à tous les *.service Une unit spécifique sous la forme \u0026lt;unit_name\u0026gt;.d/*.conf Ou grâce à une pseudo hiérarchie et l\u0026rsquo;utilisation du signe \u0026lsquo;-\u0026rsquo; dans le nom des units. Ainsi un fragment de configuration dans le répertoire foo-.d/*.conf va surcharger les configurations des units foo-bar.service, foo-foo.service et foo-bar-foo.service\u0026hellip; Voyons maintenant comment appliquer cela et les outils fournis par systemd pour nous assister dans la mise en place.\nSi l\u0026rsquo;on crée le fichier /etc/systemd/system/service1.service de la façon suivante :\n1 2 3 [Service] Type=oneshot ExecStart=/bin/echo hello world Puis le fragment de configuration dans le fichier suivant /etc/systemd/system/service1.service.d/00-override.conf :\n1 2 [Service] ExecStart=/bin/echo running this first Puis le fichier suivant /etc/systemd/system/service.d/00-override.conf :\n1 2 [Service] ExecStart=/bin/echo top level exec pre start On obtient en utilisant la commande systemctl cat le détail de la configuration de la unit ainsi que ses différents fragments :\n1 2 3 4 5 6 7 8 9 10 11 12 $ systemctl cat service1.service # /etc/systemd/system/service1.service [Service] ExecPreStart=/bin/echo hello world # /etc/systemd/system/service1.service.d/00-override.conf [Service] ExecPreStart=/bin/echo running this first # /etc/systemd/system/service.d/00-override.conf [Service] ExecPreStart=/bin/echo top level exec pre start Et produit le résultat suivant :\n1 2 3 top level exec pre start running this first hello world Il est intéressant de noter que les surcharges sont additives. Pour passer outre les précédentes définitions, il faut d\u0026rsquo;abord déclarer la propriété à vide avant de la surcharger avec la valeur attendue.\n1 2 3 [Service] ExecStart= ExecStart=/bin/echo top level exec pre start Les Units Les units sont les éléments de bases de systemd. Il existe 11 types différents de unit. Ils correspondent chacun à un type d\u0026rsquo;actions particulières.\nLe type d\u0026rsquo;une unit est déterminé par le suffix de son unit file :\n*.service : Qui permet de définir un groupe de processus. C\u0026rsquo;est l\u0026rsquo;objet qu\u0026rsquo;on est le plus souvent amené à utiliser. *.socket : Pour faire de l\u0026rsquo;activation de services par sockets (IPC, network, unix socket\u0026hellip;). *.device : Pour prendre en compte l\u0026rsquo;activation par périphériques via udev (dont le hot-plug). *.mount : Pour gérer le montage de partitions. *.automount : Qui permet l\u0026rsquo;activation des units .mount automatique lors du premier accès. *.swap : Configuration d\u0026rsquo;un point de montage swap *.target : Sont des point de synchronisation d\u0026rsquo;un ensemble de units (comme les run-levels de SysVinit), mais également des points de déclenchements. *.path : Activation par observation de chemins *.timer : Déclencheur périodique (à la manière de cron) *.scope : Configuration d\u0026rsquo;un ensemble de processus externes *.slice : Qui permet de regrouper un ensemble de processus au sein d\u0026rsquo;un cgroup commun. Une unit est décrite par une unit file qui est lue au premier référencement pour être transformée en unit.\nL\u0026rsquo;objet units implémente les opérations de bases génériques, une spécialisation est faite via la structure UnitVtable qui va référencer les méthodes spécifiques à chaque type.\nVoici un exemple non exhaustif des méthodes de UnitVtable :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 typedef struct UnitVTable { /* How much memory does an object of this unit type need */ size_t object_size; /* The name of the configuration file section with the private settings of this unit */ const char *private_section; /* Config file sections this unit type understands, separated * by NUL chars */ const char *sections; /* This should reset all type-specific variables. This should * not allocate memory, and is called with zero-initialized * data. It should hence only initialize variables that need * to be set != 0. */ void (*init)(Unit *u); /* This should free all type-specific variables. It should be * idempotent. */ void (*done)(Unit *u); /* Actually load data from disk. This may fail, and should set * load_state to UNIT_LOADED, UNIT_MERGED or leave it at * UNIT_STUB if no configuration could be found. */ int (*load)(Unit *u); void (*dump)(Unit *u, FILE *f, const char *prefix); int (*start)(Unit *u); int (*stop)(Unit *u); int (*reload)(Unit *u); int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); /* Boils down the more complex internal state of this unit to * a simpler one that the engine can understand */ UnitActiveState (*active_state)(Unit *u); /* Return false when there is a reason to prevent this unit from being gc\u0026#39;ed * even though nothing references it and it isn\u0026#39;t active in any way. */ bool (*may_gc)(Unit *u); /* Return true when the unit is not controlled by the manager (e.g. extrinsic mounts). */ bool (*is_extrinsic)(Unit *u); /* Returns the exit status to propagate in case of FailureAction=exit/SuccessAction=exit; usually returns the * exit code of the \u0026#34;main\u0026#34; process of the service or similar. */ int (*exit_status)(Unit *u); ..... } UnitVTable; On retrouve logiquement dans l\u0026rsquo;objet Unit l\u0026rsquo;ensemble des sous-types, qui permettra de propager les actions génériques vers leurs implementations.\n1 2 3 4 5 6 7 8 9 10 11 12 13 const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = \u0026amp;service_vtable, [UNIT_SOCKET] = \u0026amp;socket_vtable, [UNIT_TARGET] = \u0026amp;target_vtable, [UNIT_DEVICE] = \u0026amp;device_vtable, [UNIT_MOUNT] = \u0026amp;mount_vtable, [UNIT_AUTOMOUNT] = \u0026amp;automount_vtable, [UNIT_SWAP] = \u0026amp;swap_vtable, [UNIT_TIMER] = \u0026amp;timer_vtable, [UNIT_PATH] = \u0026amp;path_vtable, [UNIT_SLICE] = \u0026amp;slice_vtable, [UNIT_SCOPE] = \u0026amp;scope_vtable, }; Un unit a un cycle de vie, et donc un état. Les états de base sont les suivants :\nActive Reloading Inactive Failed Activating Deactivating Maintenance Cette liste d\u0026rsquo;état peut être, elle aussi, enrichie pour répondre au cycle de vie spécifique d\u0026rsquo;un type d\u0026rsquo;unit.\nPar exemple pour une unit de type service on trouve cette liste d\u0026rsquo;états qui font tous la correspondance avec les états de base.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_DEAD] = UNIT_INACTIVE, [SERVICE_CONDITION] = UNIT_ACTIVATING, [SERVICE_START_PRE] = UNIT_ACTIVATING, [SERVICE_START] = UNIT_ACTIVATING, [SERVICE_START_POST] = UNIT_ACTIVATING, [SERVICE_RUNNING] = UNIT_ACTIVE, [SERVICE_EXITED] = UNIT_ACTIVE, [SERVICE_RELOAD] = UNIT_RELOADING, [SERVICE_STOP] = UNIT_DEACTIVATING, [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, [SERVICE_STOP_POST] = UNIT_DEACTIVATING, [SERVICE_FINAL_WATCHDOG] = UNIT_DEACTIVATING, [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, [SERVICE_FAILED] = UNIT_FAILED, [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; Job et Transaction Tout changement d\u0026rsquo;état d\u0026rsquo;une unit se fait au travers d\u0026rsquo;un Job. Il existe plusieurs types de job :\nStart / Verify Stop Reload Restart Nop On trouve également d\u0026rsquo;autres types de Job qui sont la combinaison des types précédents : par exemple \u0026ldquo;try-restart\u0026rdquo; se transformera à l\u0026rsquo;exécution en \u0026ldquo;start\u0026rdquo; ou \u0026ldquo;nop\u0026rdquo; si la unit n\u0026rsquo;est pas activée ou en cours de rechargement.\nPrenons l\u0026rsquo;exemple de la unit file suivante :\n1 2 3 [Unit] Wants=multi-users.target ... La propriété Wants= a pour effet de créer une dépendance entre cette unit et la target unit \u0026ldquo;multi-user.target\u0026rdquo;.\nNB : La plupart des relations peuvent être notées dans un sens ou dans l\u0026rsquo;autre (Before=/ After=, Require=/RequiredBy=\u0026hellip;) On pourrait donc réaliser cette même dépendance à l\u0026rsquo;aide de la propriété WantedBy= dans la target et cela aurait le même effet.\nCette dépendance va avoir pour effet de propager l\u0026rsquo;activation de la target \u0026ldquo;multi-user.target\u0026rdquo; aux unit ayant une relation de type Wants= avec elle.\nLors de cette activation une transaction qui va contenir le job d\u0026rsquo;activation de la target en elle-même plus autant de job que de dépendances.\nAinsi dans le cas d\u0026rsquo;une relation avec une unit de type Wants= la transaction pourra être mise en succès même si ce job est en échec. Ce qui n\u0026rsquo;aurait pas été le cas avec une relation plus \u0026ldquo;forte\u0026rdquo; (par exemple Require=/RequiredBy=).\nEn pratique ces relations sont spécifiées par une énumération de propriétés beaucoup plus fines les UnitDependencyAtom ou \u0026ldquo;atoms\u0026rdquo; (qui ne sont rien d\u0026rsquo;autre qu\u0026rsquo;un bitmask).\nAinsi la propriété Wants= est composée de la sorte :\n1 2 3 4 [UNIT_WANTS] = UNIT_ATOM_PULL_IN_START_IGNORED | UNIT_ATOM_RETROACTIVE_START_FAIL | UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, Cela lui permet de réagir aux propriétés suivantes qui peuvent être utilisées par différents objets (transaction, units\u0026hellip;). Sans rentrer dans les détails on retrouve l\u0026rsquo;attribut \u0026ldquo;UNIT_ATOM_PULL_IN_START_IGNORED\u0026rdquo; le comportement décrit plus haut.\nIl implémente un mécanisme de transaction connu sous le nom de Job qui s\u0026rsquo;assure de la transition d\u0026rsquo;une unit d\u0026rsquo;un état A vers un état B (par exemple start / stop / restart\u0026hellip;).\nConclusion Vous l\u0026rsquo;avez peut-être remarqué, mais systemd est conçu comme un système orienté objet. On retrouve d\u0026rsquo;ailleurs beaucoup de principes de la POO (polymorphisme, sous-typage, redéfinition\u0026hellip;) dans les différents points que j\u0026rsquo;ai abordés\nVoilà ainsi va se conclure cet article. Dans le prochain, nous aborderons l\u0026rsquo;architecture de systemd.\n","permalink":"https://cyrillesondag.github.io/blog/systemd-part-two/","summary":"\u003cfigure\u003e\u003ca href=\"Gustave_Caillebotte_-_Perissoires_sur_yerres.jpg\"\u003e\n    \u003cimg\n        \n            sizes=\"(min-width: 35em) 1200px, 100vw\"\n            \n            srcset='\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-two/Gustave_Caillebotte_-_Perissoires_sur_yerres_hu_b3e53584bfcbaeaa.jpg 480w,\n            \n                   \n            \n                   \n            \n                   \n            '\n\n            \n            \n            src=\"https://cyrillesondag.github.io/blog/systemd-part-two/Gustave_Caillebotte_-_Perissoires_sur_yerres.jpg\"\n            \n\n        \n            alt=\"Gustave Caillebotte - Périssoires sur l\u0026rsquo;Yerres (1877), huile sur toile, 103 × 155 cm, Milwaukee (USA), Milwaukee Art Museum.\"/\u003e \u003c/a\u003e\u003cfigcaption style=\"font-size: 80%; text-align: center;\"\u003e\n            \u003cp\u003eGustave Caillebotte - Périssoires sur l\u0026rsquo;Yerres (1877), huile sur toile, 103 × 155 cm, Milwaukee (USA), Milwaukee Art Museum.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003cp\u003e\u003cbr\u003e\nNous avons vu dans le précédent article quelques différences entre SysV init et systemd, ainsi que le mécanisme d\u0026rsquo;initialisation de l\u0026rsquo;espace utilisateur par le kernel.\nDans cet article, je vais aborder plus en détail le fonctionnement de systemd.\u003c/p\u003e","title":"Systemd - partie 2"},{"content":" Jean-Francois Millet - Les Glaneuses (1857), huile sur toile, 83,5 × 110 cm, Paris, musée d\u0026rsquo;Orsay.\nJe vais m\u0026rsquo;attacher dans cette partie introductive à expliciter la séquence de boot sous Linux pour m\u0026rsquo;arrêter au lancement du processus d\u0026rsquo;init. Puis faire la distinction entre systemd et SysV init sans rentrer dans les détails d\u0026rsquo;implémentation, puis revenir rapidement sur le débat autour de l\u0026rsquo;adoption du systemd.\nLes détails techniques et l\u0026rsquo;utilisation de systemd en lui-même feront les sujets d\u0026rsquo;articles suivants.\nHistorique Systemd a été développé à l\u0026rsquo;initiative de Lennard Poettering (la première version date de 2010 selon Wikipedia) comme étant le remplaçant - entre autres - de l\u0026rsquo;historique SysV init.\nPromu par Red Hat, il a fini par s\u0026rsquo;imposer comme un standard de l\u0026rsquo;industrie dans les distributions majeures Linux, malgré les critiques encore nombreuses.\nSystemd est la contraction de system daemon.\nDu boot à l\u0026rsquo;init Avant de parler de l\u0026rsquo;init il me semble important d\u0026rsquo;expliquer les étapes précédentes pour bien comprendre le contexte et le rôle de ce processus.\nDonc lors du démarrage, la séquence est la suivante :\nLe bootloader va sélectionner et lancer l\u0026rsquo;image du kernel au format bzImage (pour boot executable image), elle est située sur la partition de boot généralement sous le nom vmlinux.**.\nCette image est compilée et généralement assez minimale, c\u0026rsquo;est-à-dire qu\u0026rsquo;elle contient peu d\u0026rsquo;éléments statiques dans le but d\u0026rsquo;occuper un minimum d\u0026rsquo;espace disque et de faciliter son chargement et sa distribution. Grâce à cela une même image peut être utilisée par exemple aussi bien sur un téléphone portable que sur un serveur dernière génération (pourvu qu\u0026rsquo;ils utilisent la même architecture).\nPour réaliser le démarrage, il faut pouvoir accéder aux fichiers de configuration qui peuvent se trouver sur différents types de support : disque, partition, réseau\u0026hellip; Pour y accéder, il est donc nécessaire de charger les différents modules/drivers correspondants.\nPour ce faire, il existe principalement deux méthodes :\nSoit pré-compiler dans l\u0026rsquo;image du kernel l\u0026rsquo;ensemble des drivers requis (mais sacrifier les avantages vus plus haut). Soit passer par une image temporaire contenant ce qui est nécessaire au démarrage du système. Parmi la multitude de tâches d\u0026rsquo;initialisations (vm, console, horloge\u0026hellip;) que le kernel va réaliser, on va s\u0026rsquo;intéresser à quelques-unes en particulier.\nInstanciation du rootfs. Le rootfs est un filesystem un peu spécial, c\u0026rsquo;est la base de tous les futurs filesystems, il est stocké en mémoire et est présent dès les premières étapes du démarrage du kernel, ne peut être démonté bien qu\u0026rsquo;il soit rarement utilisé après la phase d\u0026rsquo;init.\nIl existe sous deux types :\nRAMFS qui est un simple \u0026ldquo;page cache\u0026rdquo; en mémoire dont aucun élément ne peut être persisté. TMPFS qui à l\u0026rsquo;inverse de RAMFS permet de limiter l\u0026rsquo;espace utilisé en mémoire et l\u0026rsquo;écriture sur la SWAP. Le choix de l\u0026rsquo;un ou l\u0026rsquo;autre se fait à la compilation du kernel.\nLa décompression de l\u0026rsquo;initrd L\u0026rsquo;image temporaire du rootFS évoquée plus haut s\u0026rsquo;appelle l\u0026rsquo;initrd (initial ram disk) en hommage à l\u0026rsquo;ancien fonctionnement (\u0026lt; 2.6) qui émulait un périphérique disque. C\u0026rsquo;est une simple archive compressée d\u0026rsquo;un filesystem au format cpio (un gz \u0026ldquo;amélioré\u0026rdquo;). Elle a l\u0026rsquo;avantage d\u0026rsquo;être plus facilement manipulable que l\u0026rsquo;image kernel, car non compilée. Il n\u0026rsquo;est donc pas nécessaire d\u0026rsquo;avoir un gcc ou des headers installés pour la générer ou la modifier. Elle est d\u0026rsquo;ailleurs régulièrement mise à jour au cours de la vie du système (souvent par les package managers).\non peut facilement lister le contenu grâce à la commande lsinitramfs ou la mettre à jour via update-initramfs\nCette archive est aussi stockée dans la partition de boot et passée en paramètre au kernel via le paramètre initrd=, qui va l\u0026rsquo;extraire dans le rootFS.\nEnfin - et c\u0026rsquo;est là que le processus d\u0026rsquo;init à proprement parler commence - le kernel va appeler l\u0026rsquo;exécutable /init (ou l\u0026rsquo;exécutable spécifié via rdinit=) sur le rootFS.\nL\u0026rsquo;initrd a pour but d\u0026rsquo;inclure les différents drivers et de monter le root device spécifié par le paramètre root= dans le dossier /root. Ensuite, il va supprimer les autres fichiers du rootFS puis appeler la fonction pivot_root pour transformer le répertoire /root en / (à la manière d\u0026rsquo;un chroot). Bien souvent (c\u0026rsquo;est le cas de systemd) ce processus va ré-exécuter une autre phase d\u0026rsquo;init pour prendre en charge la suite de l\u0026rsquo;initialisation du système après le montage du root.\nLe montage \u0026ldquo;/root\u0026rdquo; En cas d\u0026rsquo;absence de l\u0026rsquo;initrd le kernel va appeler la méthode prepare_namespace (qui remplit le même contrat que l\u0026rsquo;exécutable /init) c\u0026rsquo;est-à-dire tenter de monter le périphérique spécifié par le paramètre root= dans le répertoire /root, puis faire basculer ce répertoire à la racine.\nEnfin il va appeler l\u0026rsquo;un des processus d\u0026rsquo;init suivants (/sbin/init, /etc/init, /bin/init, /bin/sh)\nmain.c \u0026lsquo;kernel_init()\u0026rsquo;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 if (ramdisk_execute_command) { ret = run_init_process(ramdisk_execute_command); if (!ret) return 0; pr_err(\u0026#34;Failed to execute %s (error %d)\\n\u0026#34;, ramdisk_execute_command, ret); } /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; panic(\u0026#34;Requested init %s failed (error %d).\u0026#34;, execute_command, ret); } if (!try_to_run_init_process(\u0026#34;/sbin/init\u0026#34;) || !try_to_run_init_process(\u0026#34;/etc/init\u0026#34;) || !try_to_run_init_process(\u0026#34;/bin/init\u0026#34;) || !try_to_run_init_process(\u0026#34;/bin/sh\u0026#34;)) return 0; panic(\u0026#34;No working init found. Try passing init= option to kernel. \u0026#34; \u0026#34;See Linux Documentation/admin-guide/init.rst for guidance.\u0026#34;); L\u0026rsquo;init Comme c\u0026rsquo;est le premier processus lancé par le kernel, il porte donc logiquement le numéro PID=1.\nCe processus est un peu différent des autres :\nNe peut être tué. N\u0026rsquo;a pas de parent PPID=. Est l\u0026rsquo;ancêtre de tous les autres processus. Est chargé d\u0026rsquo;initialiser tout un nombre de ressources (disque, terminaux, réseau, interface graphique…). Sous linux les processus sont tous issus de la methode fork() ou dérivés.\nTous les processus enfants héritent d\u0026rsquo;un certain nombre d\u0026rsquo;attributs de leur parent : user, session, cgroup, mémoire partagé\u0026hellip;\nL\u0026rsquo;init a donc un rôle primordial dans la vie du système.\nSysV init Pour bien comprendre ce que systemd apporte il faut le comparer à son prédécesseur SysV init (j\u0026rsquo;ai choisi initV uniquement parce que c\u0026rsquo;était le plus répandu et que je l\u0026rsquo;ai un peu utilisé).\nSysV init est basé sur les run-levels qui sont au nombre de 5 (presque) :\n0 - réservé - Arrêt (halt) 1 - Mode utilisateur unique avec les filesystems montés. 2..5 Mode multi-utilisateur, les significations varient suivant les distributions, mais correspondent à l\u0026rsquo;état opérationnel de la machine. 6 - réservé - Redémarrage Il faut d\u0026rsquo;ailleurs comprendre le nom SysV init comme étant \u0026ldquo;système 5 init\u0026rdquo; qui correspond aux 5 run-level de l\u0026rsquo;état d\u0026rsquo;un système.\nLors de l\u0026rsquo;init le système va passer d\u0026rsquo;un run-level à l\u0026rsquo;autre jusqu\u0026rsquo;à arriver au default level (qui peut varier suivant les distributions\u0026hellip;) qui correspond au mode nominal de fonctionnement (par exemple une interface graphique pour un desktop ou un terminal pour un serveur).\nEn cas d\u0026rsquo;erreur du level X le level X+1 n\u0026rsquo;est pas appelé et l\u0026rsquo;initialisation est marquée en échec.\nChacun des levels agit comme un point de synchronisation, c\u0026rsquo;est-à-dire qu\u0026rsquo;il va déclencher un nombre d\u0026rsquo;actions à une étape précise du démarrage du système.\nPar exemple si un daemon \u0026ldquo;A\u0026rdquo; nécessite une interface réseau ou des filesystems locaux, il a tout intérêt à se déclencher à un run-level supérieur à 1.\nScripts SysV init SysV init est basé sur un ensemble de scripts, qui doivent répondre à des conventions, certaines peuvent varier suivant les distributions Linux utilisées.\nPrenons l\u0026rsquo;exemple très simple d\u0026rsquo;un script de lancement d\u0026rsquo;un daemon :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #!/bin/sh #(1) # # Demonstrate creating your own init scripts # chkconfig: 2345 91 64 (2) ### BEGIN INIT INFO (3) # Provides: Welcome # Required-Start: $local_fs $all # Required-Stop: # Default-Start: 2345 # Default-Stop: # Short-Description: Display a welcome message # Description: Just display a message. Not much else. ###END INIT INFO # Source function library. . /etc/rc.d/init.d/functions #(4) lock_file=/var/lock/subsys/myservice start() { touch \u0026#34;$lock_file\u0026#34; echo \u0026#34;Starting service\u0026#34; sleep 2 } case \u0026#34;$1\u0026#34; in #(5) start) start ;; stop) rm -f \u0026#34;$lock_file\u0026#34; echo \u0026#34;Service is shutting down\u0026#34; sleep 2 ;; status) if [[ -f \u0026#34;$lock_file\u0026#34; ]]; then echo \u0026#34;Service looks good\u0026#34; else echo \u0026#34;Service not started !!\u0026#34; fi ;; *) echo $\u0026#34;Usage: $0 {start|stop|status}\u0026#34; exit 5 esac exit $? #(6) Il est composé :\n(1) D\u0026rsquo;une déclaration de l\u0026rsquo;interpréteur (ici shell) Ensuite une série de commentaires qui n\u0026rsquo;en sont pas : (2) chkconfig qui permet de définir les levels d\u0026rsquo;exécution et le niveau de priorité (3) LSB Headers qui contient des informations sur ordonnancement du service qui peut être éventuellement utilisé (4) L\u0026rsquo;import des fonctions communes /etc/rc.d/init.d/functions (5) Un switch case en fonction des arguments \u0026lsquo;start\u0026rsquo; / \u0026lsquo;stop\u0026rsquo; / \u0026lsquo;status\u0026rsquo; (6) Un code de retour PROS :\nOffre un nombre de fonctionnalités assez complètes : ckconfig, lsb-headers. Une grande flexibilité : c\u0026rsquo;est un script shell on fait \u0026lsquo;ce qu\u0026rsquo;on veut\u0026rsquo;. Un début de standardisation (avec les fonctions, lsb headers, checkconfig). CONS :\nTrès verbeux, répétitif et difficilement extensible. Repose uniquement sur des conventions (start, stop, status\u0026hellip;). Compliqué à réaliser. Peut avoir des comportements différents selon le contexte d\u0026rsquo;appel du script. D\u0026rsquo;un point de vue utilisateur, on remarque bien que ce n\u0026rsquo;est pas standardisé (avec ses avantages et ses inconvénients) et en pratique demande pas mal d\u0026rsquo;expertise en script pour réaliser des choses assez semblables (gérer les sorties standards, changer d\u0026rsquo;utilisateur, lancer un daemon, exécution unique\u0026hellip;)\nLà où SysV init se résume à séquencer l\u0026rsquo;exécution de scripts (en schématisant), systemd se base sur de la description de configuration.\nService unit systemd Ces configurations sont écrites au format ini et sont appelées \u0026ldquo;unit file\u0026rdquo; ou \u0026ldquo;unit\u0026rdquo; (mais ce n\u0026rsquo;est pas exact, car ça désigne les objets manipulés par systemd et non les fichiers).\nIl existe plusieurs types d\u0026rsquo;unit files qui répondent à différents cas d\u0026rsquo;utilisations (service, montage\u0026hellip;), chacune est suffixée par son type.\nL\u0026rsquo;équivalent systemd du script ci-dessus est une unit file de type \u0026ldquo;service\u0026rdquo; que l\u0026rsquo;on pourrait adapter de la sorte :\n1 2 3 4 5 6 7 8 9 10 [Unit] Description=Service [Service] Type=simple ExecStart=/usr/bin/sleep 2 ExecPreStop=/usr/bin/sleep 2 [Install] Wants=multi-users.target La première chose que l\u0026rsquo;on remarque est que le fichier systemd n\u0026rsquo;est pas un script, il n\u0026rsquo;a donc pas d\u0026rsquo;utilité en dehors du contexte de systemd.\nDeuxième chose, les attributs de configuration sont normés, ils correspondent à une série de propriétés interprétées par le programme. Ce n\u0026rsquo;est plus nous qui réalisons les actions, c\u0026rsquo;est systemd qui le fait à notre place.\nLe script de base est extrêmement simple, et n\u0026rsquo;a pas vraiment de sens (encore moins avec systemd) mais met bien en évidence toutes les choses qui sont incluses de base dans systemd, et la différence entre les deux approches.\nL\u0026rsquo;avantage ici d\u0026rsquo;être dans un \u0026ldquo;framework\u0026rdquo; est qu\u0026rsquo;il prend nativement en charge toutes les tâches usuelles sans avoir à les redéfinir nous-mêmes.\nUn service est unique, pour un même nom (au sein d\u0026rsquo;une instance systemd). Nous n\u0026rsquo;avons donc pas besoin de lock de synchronisation (il est possible de le variabiliser via le templating). Les états start / status / stop / restart sont implicites et communs à tous les services. Il faut noter que le status a souvent une signification un peu différente des scripts initV qui réalisent souvent des tâches de vérification assez complexes. Sous systemd le status ne reporte généralement que l\u0026rsquo;état du PID déterminé comme principal. Là où l\u0026rsquo;on devait spécifier via les lsb-headers l\u0026rsquo;ordonnancement (after $local_fs) fait ici partie de la valeur par défaut (cf defaultdependencies) qui doit répondre à la majorité des cas d\u0026rsquo;utilisations des daemons de haut niveau. Enfin on retrouve un équivalent des run-level avec l\u0026rsquo;attribut Target=multi-users.target qui sert ici également de point de synchronisation lors du démarrage. Le mal aimé On a beaucoup reproché à systemd d\u0026rsquo;être envahissant, et de briser le principe \u0026ldquo;un programme doit faire UNE chose et la faire bien\u0026rdquo;.\nOui systemd est envahissant, mais principalement parce que c\u0026rsquo;est un init system, une des pièces principales des systèmes UNIX. Il est de ce fait chargé de réaliser une multitude d\u0026rsquo;actions très différentes (monter les fs, mettre en place les interfaces réseau\u0026hellip;). Ce qui était \u0026ldquo;caché\u0026rdquo; sous des scripts est ici rendu apparent.\nLes défenseurs rétorquent souvent que systemd est modulaire - ce qui est vrai - mais cela ne me semble pas être un argument valable, les composants peuvent faire partie de différents projets/modules (journald, networkd, nspawn\u0026hellip;) ils font tous partie d\u0026rsquo;un ensemble cohérent et ont tous vocation à fonctionner ensemble.\nÀ mon avis la principale résistance est que systemd a imposé ses standards (en termes de nomenclature, arborescence, packaging et autres) au sein des distributions.\nJ\u0026rsquo;y vois également la réaction à la professionnalisation inévitable de l\u0026rsquo;écosystème Linux, avec la disparition progressive de l\u0026rsquo;esprit pionnier qui perdure dans les communautés de passionnés organisées autour des distributions / projets OSS. (Il faut aussi bien avouer que la communauté Linux ADORE les dramas et trouver des occasions de s\u0026rsquo;écharper).\nSystemd n\u0026rsquo;est certainement pas idéal, mais il est \u0026ldquo;constant\u0026rdquo;. Il a indéniablement apporté beaucoup de bénéfices : facilitation du packaging, une meilleure portabilité, une qualité accrue et plus homogène, une facilité d\u0026rsquo;accès aux fonctions avancées du kernel\u0026hellip;\nBref, il ne s\u0026rsquo;agit pas de défendre systemd mais bien de mettre en avant les bénéfices qu\u0026rsquo;il apporte.\n","permalink":"https://cyrillesondag.github.io/blog/systemd-part-one/","summary":"\u003cfigure\u003e\u003ca href=\"Jean-Fran%c3%a7ois_Millet_-_Gleaners_-_Google_Art_Project_2.jpg\"\u003e\n    \u003cimg\n        \n            sizes=\"(min-width: 35em) 1200px, 100vw\"\n            \n            srcset='\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-one/Jean-Fran%C3%A7ois_Millet_-_Gleaners_-_Google_Art_Project_2_hu_7a9ffdd23859508d.jpg 480w,\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-one/Jean-Fran%C3%A7ois_Millet_-_Gleaners_-_Google_Art_Project_2_hu_e3eb97c187c6fdeb.jpg 800w,\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-one/Jean-Fran%C3%A7ois_Millet_-_Gleaners_-_Google_Art_Project_2_hu_877d01c955c0e768.jpg 1200w,\n            \n                   https://cyrillesondag.github.io/blog/systemd-part-one/Jean-Fran%C3%A7ois_Millet_-_Gleaners_-_Google_Art_Project_2_hu_c67f72020effd2b9.jpg 1500w,\n            '\n\n            \n            \n            src=\"https://cyrillesondag.github.io/blog/systemd-part-one/Jean-Fran%C3%A7ois_Millet_-_Gleaners_-_Google_Art_Project_2_hu_e3eb97c187c6fdeb.jpg\"\n            \n\n        \n            alt=\"Jean-Francois Millet - Les Glaneuses (1857), huile sur toile, 83,5 × 110 cm, Paris, musée d\u0026rsquo;Orsay.\"/\u003e \u003c/a\u003e\u003cfigcaption style=\"font-size: 80%; text-align: center;\"\u003e\n            \u003cp\u003eJean-Francois Millet - Les Glaneuses (1857), huile sur toile, 83,5 × 110 cm, Paris, musée d\u0026rsquo;Orsay.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003cp\u003eJe vais m\u0026rsquo;attacher dans cette partie introductive à expliciter la séquence de boot sous Linux pour m\u0026rsquo;arrêter au lancement du processus d\u0026rsquo;init.\nPuis faire la distinction entre systemd et SysV init sans rentrer dans les détails d\u0026rsquo;implémentation, puis revenir rapidement sur le débat autour de l\u0026rsquo;adoption du systemd.\u003c/p\u003e","title":"Systemd - partie 1"},{"content":"Les slides de ma présentation sur la gestion de la mémoire sous Linux.\nvirtual memory memory management TLB \u0026hellip; ","permalink":"https://cyrillesondag.github.io/blog/presentation-linux-menory/","summary":"\u003cp\u003eLes slides de ma présentation sur la gestion de la mémoire sous Linux.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003evirtual memory\u003c/li\u003e\n\u003cli\u003ememory management\u003c/li\u003e\n\u003cli\u003eTLB\u003c/li\u003e\n\u003cli\u003e\u0026hellip;\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv id=\"Container\"\n style=\"padding-bottom:56.25%; position:relative; display:block; width: 100%\"\u003e\n \u003ciframe id=\"googleSlideIframe\"\n  width=\"100%\" height=\"100%\"\n  src=\"https://docs.google.com/presentation/d/1bdqIdZOZtda5FBrnxnOq1AWdBcDdxcwotYih_6B3AYk/embed?start=false\u0026amp;loop=false\u0026amp;delayms=3000\"\n  frameborder=\"0\" allowfullscreen=\"\"\n  style=\"position:absolute; top:0; left: 0\"\u003e\u003c/iframe\u003e\n\u003c/div\u003e","title":"Présentation Linux Memory"},{"content":"Pourquoi utiliser Rx ? Le Framework Rx prend une importance croissante dans le développement d\u0026rsquo;applications mobiles. Il apporte une très grande flexibilité dans la gestion des appels asynchrones et permet de répondre facilement aux problèmes de synchronisation des événements (le fameux Callback Hell).\nNéanmoins l\u0026rsquo;apprentissage peut être assez déroutant au départ si l\u0026rsquo;on ne comprend pas la philosophie sur laquelle est basée ce Framework. Une fois cette étape achevée, il devient très simple de répondre à des problématiques d’enchaînement d’événements complexes.\nPour démarrer d\u0026rsquo;un bon pied nous allons d\u0026rsquo;abord définir les principes fondamentaux.\nLes concepts fondamentaux. Rx est basé sur le pattern Observer en utilisant la terminologie Subscriber / Observer et Observable (un Subject en Rx recouvre une notion bien particulière).\nLes Observables Le reactive programming voit les événements comme des flux qui se propagent dans l\u0026rsquo;application.\nCes flux peuvent avoir des sources multiples : une interaction avec un utilisateur (focus, sélection \u0026hellip;), un élément extérieur (ex: la réponse d\u0026rsquo;un serveur distant) ou interne (la fin d\u0026rsquo;un traitement) etc.\nIls peuvent avoir une durée de vie déterminée (ex : une requête Http) ou non (ex le signal de perte de réseau). De la même manière ces flux peuvent ainsi émettre un ou plusieurs événements à des intervalles différents.\nEn Rx un Observable sert à matérialiser ces flux, ils peuvent être écoutés, avoir leurs propres cycles de vie, être composés de différents flux…\nLes Subcribers Est la notion la plus simple à appréhender. Il s\u0026rsquo;abonne à un Observable et il reçoit ses émissions, ses signaux d\u0026rsquo;erreurs ou fin de traitement.\nLes Operators Les Operators servent à traiter et organiser les flux. Ils permettent de filtrer, combiner, transformer, parcourir, composer des flux…\nDe base une foultitude d\u0026rsquo;opérateurs sont fournis. Bien qu\u0026rsquo;on puisse en créer de nouveaux, en pratique les opérateurs par défaut sont généralement largement suffisants pour répondre à la majorité des situations.\nLes Schedulers Par défaut Rx n\u0026rsquo;est pas asynchrone, pour la simple et bonne raison que le framework réserve cette décision au développeur de l\u0026rsquo;application.\nLes Schedulers servent à contrôler l’exécutions des traitements. Il est alors très simplement possible de décider sur quel Thread va s’exécuter un Observable et sur lequel le Subscriber va recevoir les réponses.\nRxJava en pratique. Commençons d\u0026rsquo;abord par un exemple très simple pour illustrer les concepts vus précédemment.\nHello World ! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Subscription subscription = Observable.just(example.dummyValue()) .observeOn(Schedulers.newThread()) .subscribe(new Observer\u0026lt;Boolean\u0026gt;() { @Override public void onCompleted() { System.out.println(\u0026#34;dummyValue has Completed\u0026#34;); } @Override public void onError(Throwable e) { System.out.println(\u0026#34;Error occured : \u0026#34; + getMessage()); } @Override public void onNext(Boolean aBoolean) { System.out.println(\u0026#34;dummyValue result : \u0026#34; + aBoolean); } }); } public boolean dummyValue() { try { Thread.sleep(600); return true; } catch (InterruptedException e) { throw new IllegalStateException(e); } } Observable.just crée un nouvel Observable avec une seule valeur de retour. .observeOn définit que l\u0026rsquo;Observable s’exécute sur un nouveau Thread. .subscribe s\u0026rsquo;abonne à l\u0026rsquo;observable. Retourne un objet Subscription. Il existe plusieurs méthodes génériques pour créer un Observable soit à partir de valeurs fixes, d\u0026rsquo;une collection, d\u0026rsquo;un Future\u0026lt;?\u0026gt; ou Callback\u0026lt;?\u0026gt; ou opérateurs. Il est aussi relativement facile de créer des Observable particuliers si l\u0026rsquo;on respecte son cycle de vie.\nComme vu précédemment par défaut un Observable s\u0026rsquo;exécute sur le même Thread que celui qui l\u0026rsquo;a instancié. En spécifiant Schedulers.newThread() on force l’exécution du traitement sur un nouveau Thread. Rx est fourni avec plusieurs schedulers prédéfinis tel que io, computation, … On a aussi la possibilité d\u0026rsquo;en créer afin d\u0026rsquo;optimiser la gestion des instances de Thread.\nL\u0026rsquo;interface Observer est, elle aussi, relativement simple et lisible. Elle est composée de trois méthodes :\nonNext: appelé à chaque objet émis par l\u0026rsquo;observable. onError: lorsqu\u0026rsquo;une erreur s\u0026rsquo;est produite lors du traitement. onComplete: lorsque que l\u0026rsquo;observable a terminé d’émettre des éléments (Par contrat onNext ou onError ne seront plus jamais appelés). L’objet Subscription - comme son nom l\u0026rsquo;indique - est la marque de la souscription d\u0026rsquo;un Observer vers un Observable.\nDans le cas présent, l\u0026rsquo;Observable est exécuté immédiatement avec une durée de vie fixée à un élément.\nOn peut schématiser son cycle comme ceci :\no \u0026mdash;\u0026gt; onNext(true) \u0026ndash;\u0026gt; onComplete()\nAu travers de cet exemple basique, il faut noter la gestion ultra simple des threads et des erreurs, qui est à mon avis le gros avantage de ce Framework.\nDébuter avec les Operators Prenons pour exemple un Observable assez simple pour comprendre leur fonctionnement.\n1 Observable\u0026lt;String\u0026gt; obs = Observable.just(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;); On aura donc 5 appels à onNext(String s) avant la méthode onComplete()\nLe Subscriber affichera le résultat de onNext dans la console.\n1 2 3 4 @Override public void onNext(String result) { System.out.println(\u0026#34;Result : \u0026#34; + result); } Par défaut les résultats seront donc :\nResult : a Result : b Result : c Result : d Result : e\nFilter: 1 2 3 4 5 6 .filter(new Func1\u0026lt;String, Boolean\u0026gt;() { @Override public Boolean call(String s) { return \u0026#34;a\u0026#34;.equals(s) || \u0026#34;d\u0026#34;.equals(s); } }) Retourne :\nResult : a Result : d\nTake: .take(2) ou .limit(2)\nRetourne les x premiers éléments :\nResult : a Result : b\nTakeLast: .takeLast(2)\nRetourne les x derniers éléments :\nResult : d Result : e\nMap: 1 2 3 4 5 6 .map(new Func1\u0026lt;String, String\u0026gt;() { @Override public String call(String s) { return \u0026#34;Hello \u0026#34; + s + \u0026#34; !!\u0026#34;; } }) Applique pour chaque élément une transformation (il est également possible de modifier le type de retour) :\nResult : Hello a !! Result : Hello b !! Result : Hello c !! Result : Hello d !! Result : Hello e !!\nCes opérateurs sont bien évidemment combinables entre eux.\nEn conclusion Voici une première approche des basiques du Framework. Nous verrons par la suite les avantages de son utilisation, mais aussi les problèmes spécifiques liés à son utilisation dans de prochains articles.\n","permalink":"https://cyrillesondag.github.io/blog/starting-rxjava/","summary":"\u003ch1 id=\"pourquoi-utiliser-rx-\"\u003ePourquoi utiliser Rx ?\u003c/h1\u003e\n\u003cp\u003eLe Framework Rx prend une importance croissante dans le développement d\u0026rsquo;applications mobiles. Il apporte une très grande flexibilité dans la gestion des appels asynchrones et permet de répondre facilement aux problèmes de synchronisation des événements (le fameux \u003ca href=\"https://www.quora.com/What-is-callback-hell\"\u003eCallback Hell\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003eNéanmoins l\u0026rsquo;apprentissage peut être assez déroutant au départ si l\u0026rsquo;on ne comprend pas la philosophie sur laquelle est basée ce Framework. Une fois cette étape achevée, il devient très simple de répondre à des problématiques d’enchaînement d’événements complexes.\u003c/p\u003e","title":"Débuter avec RxJava"}]