URL: https://linuxfr.org/news/mirageos-un-micro-os-unikernel-en-ocaml Title: MirageOS - un micro OS (unikernel) en OCaml Authors: Dinosaure Snark, Ysabeau đ§¶, palainp, theojouedubanjo, bobble bubble, BAud, Pierre Jarillon, palm123, claudex, nokomprendo, dourouc05, tisaac et Nicolas Casanova Date: 2020ćčŽ08æ25æ„T13:41:21+02:00 License: CC By-SA Tags: mirageos et unikernel Score: 74 [MirageOS](http://mirage.io/) est un outil permettant de crĂ©er un [unikernel](https://fr.m.wikipedia.org/wiki/Unikernel#:~:text=Les%20unikernels%20sont%20des%20images,des%20syst%C3%A8mes%20d'exploitation%20biblioth%C3%A8ques.) (un systĂšme dâexploitation) pouvant faire office de micro-service comme un site Internet, un service SMTP ou encore un service DNS. Lâobjectif de MirageOS est de proposer une solution modulaire afin que lâutilisateur puisse crĂ©er son propre systĂšme selon ce quâil souhaite **vraiment**. La modularitĂ© et lâapproche _clean-state_ de MirageOS permet de dĂ©lester le systĂšme final dâĂ©lĂ©ments superficiels _Ă priori_. De ce fait, MirageOS est capable de produire un systĂšme dâexploitation complet comme un simple site internet ne pesant au final que ~16 Mo. Lâapproche de MirageOS est de reconstruire tous les Ă©lĂ©ments de votre application finale en [OCaml](https://ocaml.org/) (en partant de votre API REST Ă la pile TCP/IP). Les logiques dâabstraction et la modularitĂ© dâOCaml sont les bases de MirageOS afin de sâabstraire de tout ce qui est Ă proprement parler liĂ© au systĂšme (les _syscalls_) et de pouvoir interchanger une implĂ©mentation avec une autre sans changer le reste de lâapplication. Par ces mĂ©canismes-lĂ , MirageOS a la possibilitĂ© de produire un simple exĂ©cutable ou de produire un systĂšme complet capable dâĂȘtre virtualisĂ© avec [Xen](https://xenproject.org) ou [KVM](https://www.linux-kvm.org). Dans cette dĂ©pĂȘche, nous allons voir ce quâest concrĂštement MirageOS et expliquer comment lâutiliser. ---- [MirageOS](https://mirage.io/) [OCaml](https://ocaml.org/) ---- # Un peu dâOCaml Avant de parler de MirageOS, nous avons besoin dâune petite introduction Ă OCaml. OCaml est un langage nĂ© en 1996 et maintenu par lâInria venant de la famille des langages [ML](https://fr.wikipedia.org/wiki/ML_(langage)) (contraction de Meta Language : un langage de programmation gĂ©nĂ©raliste fonctionnel). Il propose un systĂšme de types ainsi quâune gestion automatique de la mĂ©moire. Nous allons ici nous intĂ©resser aux points qui ont dirigĂ© MirageOS vers ce langage. ## Un systĂšme de modules OCaml a un systĂšme de modules, qui est rĂ©gi par une rĂšgle : un fichier OCaml, un module ou unitĂ© de compilation. Un module est une implĂ©mentation dâun certain type de donnĂ©es ainsi que des fonctions permettant de traiter/manipuler ce type de donnĂ©es. On peut par exemple parler du module `String` qui implĂ©mente les chaĂźnes de caractĂšres ainsi que les fonctions comme `find_character`/`trim`/ etc. La particularitĂ© dâOCaml rĂ©side dans lâidĂ©e quâune implĂ©mentation peut ĂȘtre dĂ©crite par une signature/interface, le fichier `*.mli`. Ce dernier dĂ©crit ce qui doit ĂȘtre montrĂ© aux autres modules de son implĂ©mentation associĂ©e. Il est courant en OCaml dâabstraire/cacher le type de donnĂ©es et de dĂ©crire uniquement les fonctions associĂ©es au type. Il est mĂȘme commun dâutiliser une signature/interface Ă plusieurs implĂ©mentations tel que : ```ocaml module type ORDERED = sig type t val compare : t -> t -> int end ``` Ici, on dĂ©crit une interface qui expose `compare`. Cette derniĂšre peut ĂȘtre utilisĂ©e avec `type t = string` (et utiliser `memcmp`) ou un entier `type t = int` (et utiliser la soustraction). ## Foncteur GrĂące Ă ce mĂ©canisme de modules, on a la possibilitĂ© de _produire_ un module/une implĂ©mentation Ă partir dâun autre module dĂ©crit par une interface tel que `Ordered`. Ainsi, un simple dictionnaire associant une clĂ© avec une valeur peut se spĂ©cialiser selon la clĂ© tel que : ```ocaml module Make (S : ORDERED) : sig type 'a t val empty : 'a t val add : S.t -> 'a -> 'a t -> 'a t end ``` Ce principe dâabstraction en OCaml est largement utilisĂ© par la communautĂ©, mais on le retrouve Ă une autre Ă©chelle en ce qui concerne MirageOS. ## _Fonctoriser_ les _syscalls_ Le principe de MirageOS et de son Ă©cosystĂšme est de ne pas dĂ©pendre dâun appel systĂšme tel que ceux offerts par le noyau Linux. Lâobjectif est de sâabstraire des _syscalls_ et dâ_injecter_ leurs implĂ©mentations Ă la production du systĂšme dâexploitation. De ce fait, on est en capacitĂ© autant dâintroduire les _syscalls_ usuels proposĂ©s par le noyau Linux comme ceux dâun _micro-noyau_ pouvant ĂȘtre virtualisĂ© avec Xen ou KVM. LâidĂ©e est dâĂȘtre en capacitĂ© de produire la logique applicative (votre service comme un site-web) qui est complĂ©tement abstrait de la logique du systĂšme - la _stack_ TCP/IP, la couche de cryptographie, le systĂšme de fichiers. De cette maniĂšre, il devient possible de _compiler_ votre application comme un exĂ©cutable ou comme un systĂšme entier qui peut ĂȘtre virtualisĂ© via Xen ou KVM ou encore un systĂšme pouvant fonctionner sur [des puces ESP32](https://mirage.io/blog/2018-esp32-booting). Lâobjectif est que le cĂŽtĂ© applicatif ne devrait pas changer. ## Gestion de la mĂ©moire Depuis la version [3.9](https://github.com/ocaml/opam-repository/pull/17485) (octobre 2020), seul le mode [PVH(VM)](https://wiki.xenproject.org/wiki/PV_on_HVM) est pris en charge, la partie bas niveau permettant le boot est assurĂ©e par [Solo5](https://github.com/solo5/solo5) et la gestion de la mĂ©moire est passĂ©e Ă la version de [Doug Lea pour malloc](https://github.com/mirage/ocaml-freestanding/blob/master/nolibc/dlmalloc.i). # LâĂ©co-systĂšme de MirageOS Ainsi, tout lâĂ©co-systĂšme de MirageOS se fonde sur un principe dâabstraction de ce qui peut ĂȘtre considĂ©rĂ© comme le systĂšme dâexploitation. Au-delĂ de ça, les composants du systĂšme sont eux-mĂȘmes abstraits Ă lâaide dâinterfaces comme `ORDERED`. Ainsi, MirageOS se dĂ©finit plus comme un outil permettant de faire la _glue_ entre plusieurs composants Ă lâaide dâinterfaces. Par ce biais, il devient simple par exemple dâinterchanger lâimplĂ©mentation dâun type de donnĂ©es de lâun Ă lâautre sans changer le reste de la logique du systĂšme. Câest, concrĂštement, ce qui se passe lorsquâon _injecte_ la _stack_ TCP/IP du systĂšme hĂŽte lorsquâon veut produire un simple exĂ©cutable avec MirageOS ou quâon utilise une implĂ©mentation de la _stack_ TCP/IP en OCaml: `mirage-tcpip`. Bien entendu, cette approche requiert que ces implĂ©mentations existent ! Et câest le principal travail de lâĂ©quipe de MirageOS : implĂ©menter des formats/protocoles/logiques qui peuvent ĂȘtre utilisĂ©s avec MirageOS. Au travers de ce travail dâabstraction, ces diffĂ©rentes implĂ©mentations peuvent ĂȘtre utilisĂ©es en dehors de MirageOS. Ainsi, la plupart des projets de MirageOS sont utilisĂ©s par la communautĂ© dans dâautres contextes que celui de MirageOS comme : - Windows - Mac OSX - votre navigateur web grĂące Ă `js_of_ocaml` ## Quelques super-stars Dans ces projets qui sont utilisĂ©s par dâautres personnes en dehors de MirageOS, nous avons : - `irmin` - `mirage-tcpip` - `cohttp` - `ocaml-tls` - `ocaml-dns` ### Irmin LâidĂ©e dâun systĂšme de fichiers nâest pas garantie par MirageOS et, mĂȘme si nous avons essayĂ© dâimplĂ©menter certains formats, lâĂ©quipe MirageOS a dĂ©cidĂ© de concentrer ses efforts dans lâimplĂ©mentation dâun _Key-Value store_. Irmin est une abstraction de ce que devrait ĂȘtre une telle base de donnĂ©es. Mais, Ă la diffĂ©rence des systĂšmes tels que LMDB ou encore Git, Irmin nâoffre quâune abstraction commune. Ensuite, câest Ă lâutilisateur de choisir son implĂ©mentation. MirageOS utilise aujourdâhui Irmin avec une implĂ©mentation de Git en OCaml. Par ce dernier, un _unikernel_ peut obtenir une base de donnĂ©es clĂ©-valeur interne qui peut se synchroniser au _boot_ avec un dĂ©pĂŽt Git. Sur ce dernier, il peut se synchroniser ou il peut le mettre Ă jour. On parle alors de systĂšme de base de donnĂ©es persistant â mĂȘme si le systĂšme sâĂ©teint, il peut reprendre lâĂ©tat dans lequel la base de donnĂ©es Ă©tait juste avant de sâĂ©teindre. Irmin est actuellement utilisĂ© par la crypto-monnaie [Tezos](https://tezos.com/) afin de manipuler la _block-chain_. Une autre utilisation concrĂšte dâIrmin avec MirageOS est un systĂšme dâexploitation qui fait office de serveur DNS primaire dont le fichier [`zone`](https://en.wikipedia.org/wiki/Zone_file) est stockĂ© dans un dĂ©pĂŽt Git. Ainsi, lâ_unikernel_ se synchronise avec ce dĂ©pĂŽt (avec le protocol Git - comme un `git clone`), il peut le modifier (comme `git push`) et lâutilisateur peut tout autant modifier aussi et demander ensuite Ă lâ_unikernel_ de se resynchroniser (comme `git pull`). Dans le dernier cas, lâutilisateur peut dĂ©crire sa politique de _merge_ (comment rĂ©soudre un conflit sâil y en a un) avec Irmin. Cela permet dâassurer la persistance des donnĂ©es en dehors de lâ_unikernel_ lui-mĂȘme. ### mirage-tcpip Du fait que MirageOS est un systĂšme dâexploitation complet, lâĂ©quipe de MirageOS a finalement implĂ©mentĂ© la _stack_ TCP/IP au travers du projet [mirage-tcpip](https://github.com/mirage/mirage-tcpip). Au-delĂ de lâintĂ©rĂȘt technique de rĂ©-implementer une _stack_ TCP/IP, cette derniĂšre est industriellement utilisĂ©e par [Docker](https://www.docker.com/blog/docker-unikernels-open-source/). Ce projet permet dâintroduire un concept fondateur de MirageOS. Lâobjectif de lâoutil `mirage` est de produire, au mieux, un systĂšme complet capable dâĂȘtre virtualisĂ© sur KVM ou Xen, mais il permet aussi de produire un simple exĂ©cutable UNIX comme nous aurions lâhabitude dâavoir. Le point crucial ici est la capacitĂ© de `mirage` Ă orchestrer (indĂ©pendamment de lâapplication) les _stacks_ selon la cible. Pour ce qui est de la production dâun simple exĂ©cutable, `mirage` va injecter la _stack_ TCP/IP du systĂšme hĂŽte. Pour ce qui est de KVM ou Xen, il va tout simplement injecter `mirage-tcpip` (qui nâest fait quâen OCaml). LâidĂ©e est de nâutiliser, du point de vue de lâapplication, quâune interface (en lâoccurrence [mirage-stack](https://github.com/mirage/mirage-stack/blob/master/src/mirage_stack.ml)) nous permettant de sĂ©parer la logique de lâapplication des autres fondements de notre systĂšme. ### cohttp Bien entendu, en tant que premier exemple rĂ©el dâun _unikernel_, il nous faut aussi une implĂ©mentation du protocole HTTP 1.1 : [cohttp](https://github.com/mirage/ocaml-cohttp). Pour lâexemple, le site officiel de [MirageOS](https://mirage.io/) est un _unikernel_ utilisant `cohttp`. Mais ce projet, comme la plupart des projets MirageOS, dĂ©passe lâĂ©cosystĂšme et fait partie intĂ©grante du plus large Ă©cosystĂšme dâOCaml. Puisque le dĂ©veloppement dâune bibliothĂšque OCaml pour MirageOS se fait toujours en abstraction du systĂšme, spĂ©cialiser le cĆur pour un systĂšme comme Linux ou Windows devient plus facile. La qualitĂ© la plus reconnue des projets Mirage est leur capacitĂ© Ă pouvoir ĂȘtre utilisĂ©s sur pratiquement tous les systĂšmes. Bien entendu, cette qualitĂ© est fortement liĂ©e Ă OCaml aussi qui propose un _runtime_ sâexĂ©cutant nativement sur une multitude de plateformes. ### ocaml-tls La conception dâun systĂšme entier nĂ©cessite aussi de disposer de primitives de cryptographie, qui sont usuellement disponibles avec OpenSSL (ou lâun de ses forks). Il nâest, pour autant, pas aussi _simple_ de rĂ©-intĂ©grer un code C existant (dĂ©pendant gĂ©nĂ©ralement des _syscalls_ POSIX) dans MirageOS qui nâa, pour le coup, rien de toutes ces primitives. Un effort colossal a donc Ă©tĂ© fait pour rĂ©-implĂ©menter les primitives de cryptographie essentielles afin de pouvoir rĂ©-implĂ©menter le protocole TLS, nĂ©cessaire pour servir un site internet accessible en HTTPS. LĂ aussi, `ocaml-tls` est un projet phare limitant le prĂ©requis dâinstructions assembleur tout en proposant une bibliothĂšque avec des performances raisonnables. Encore une fois, il nâa absolument aucune notion de ce que peut ĂȘtre un _socket_ ou de tout ce qui peut ĂȘtre _POSIX-compliant_. ### ocaml-dns Enfin, le domaine mirage.io est gĂ©rĂ© par un serveur primaire DNS qui est aussi un _unikernel_. Plusieurs services DNS tel quâun rĂ©solveur et un service sâoccupant du challenge DNS de let's encrypt sont disponibles grĂące Ă `ocaml-dns`. Ce projet sâinscrit dans lâambition de proposer des micro-services : un systĂšme dâexploitation pour un service spĂ©cifique. # Vous lancer dans lâĂ©criture dâun unikernel Des exemples prĂȘts Ă compiler sont disponibles sur le [github](https://github.com/mirage/mirage-skeleton) dont le classique _hello world_ entiĂšrement reproduit ici : open Lwt.Infix module Hello (Time : Mirage_time.S) = struct let start _time = let rec loop = function | 0 -> Lwt.return_unit | n -> Logs.info (fun f -> f "hello"); Time.sleep_ns (Duration.of_sec 1)>>= fun () -> loop (n-1) in loop 4 end Ce « noyau » se contente dâĂ©crire quatre fois hello en 4 secondes et se termine. Si vous voulez expĂ©rimenter chez vous, vous devez mettre en place un environnement propice Ă la compilation et Ă lâexĂ©cution, par exemple avec les commandes suivantes (Ă adapter en fonction de votre distribution) : sudo dnf install opam && \ opam init && opam update -yu && \ opam install mirage && eval $(opam env) && \ git clone https://github.com/mirage/mirage-skeleton && \ cd mirage-skeleton/tutorial/hello && \ mirage configure -t unix && make depend && make && \ ./hello (Dans cet exemple on produit un binaire exĂ©cutable mais en utilisant lâoption de configuration `mirage configure -t xen` on peut par exemple produire un noyau utilisable avec lâhyperviseur Xen.) ## Abstraction avec les _functors_ Comme vous pouvez le constater, un _functor_ `Time` est utilisĂ© pour _gĂ©nĂ©rer_ la fonction _start_ (qui est comme le `main` en C). Câest un module qui respect la signature [`Mirage_time.S`](https://mirage.github.io/mirage-time/mirage-time/Mirage_time/module-type-S/index.html). GrĂące Ă ce module, nous pouvons utiliser la fonction `sleep_ns`. Son implĂ©mentation dĂ©pend bien entendu de la cible de votre MirageOS: - pour UNIX, comme dans cette exemple, nous allons utiliser `Unix.sleep` - pour Solo5 (KVM ou Xen), nous allons utiliser une fonction spĂ©cifique disponible dans `mirage-solo5` Ce choix dâimplĂ©mentation est fait par lâoutil `mirage`, lorsque vous lancez: `mirage configure`. Dans ce cas, on prend lâimplĂ©mentation par dĂ©faut proposĂ©e par `mirage` mais lâutilisateur peut trĂšs bien choisir **son** implĂ©mentation (tant qu'elle respecte la signature `Mirage_time.S`). ## Autres aspects Il y aurait encore beaucoup Ă dire, car cet article ne peut ĂȘtre exhaustif ! Vous ĂȘtes invitĂ©s Ă le complĂ©ter dans vos commentaires.