Aller au contenu principal

Modus peut il remplacer les Dockerfiles ?

· 4 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Découvert par hasard, Modus est un langage permettant de créer des images de conteneur Docker/OCI. Modus utilise la programmation logique pour exprimer les interactions entre les paramètres de build, spécifier des workflows de build complexes, paralléliser et mettre en cache automatiquement les builds et aussi permet d'optimiser la taille des images produites.

Attention : c'est un projet encore sujet à des modifications importantes pouvant amener à des dysfonctionnements !

Installation de modus

Il suffit d'utiliser le script du site pour installer modus. Je fais le choix de l'installer dans le dossier .local/bin.

curl -sfL 'https://github.com/modus-continens/modus/releases/download/0.1.15/modus-x86_64-linux-musl' -o ~/.local/bin/modus && \
chmod +x ~/.local/bin/modus && \

Ecrire un Modusfile

Un Modusfile, est comme un Dockerfile, un ensemble de directives qui spécifient comment créer des images sauf que la syntaxe est totalement différentes puisqu'elle s'appuie sur celle de Datalog un langage de programmation logique déclaratif.

Exemple de fichier Modusfile (repris du tutoriel de Modus) :

my_app(profile) :-
  (
    from("rust:alpine")::set_workdir("/usr/src/app"),   # FROM rust:alpine; WORKDIR /usr/src/app
    copy(".", "."),                                     # COPY . .
    cargo_build(profile)                                # calling into another predicate
  )::set_entrypoint(f"./target/${profile}/my_app").     # ENTRYPOINT ["./target/release/my_app"]

cargo_build("debug") :- run("cargo build").             # RUN cargo build
cargo_build("release") :- run("cargo build --release"). # RUN cargo build --release

Les commentaires débutent par le #. Dans l'exemple ci-dessus, les instructions équivalentes à un Dockerfile ont été écrites pour plus de clarté à l'aide de commentaires.

Pour le reste tout est expliqué dans la documentation de Modus (que je ne maitrise pas).

Build d'une image avec Modus

La CLI de modus permet de lancer le build des images, elle est de la forme suivante :

modus build context query

Le contexte est comme avec docker, il indique depuis quel dossier on veut construire l'image. La requête indique quelle image construite avec ses paramètres.

Je vous propose de construire l'image de modus à partir de son projet :

git clone https://github.com/modus-continens/modus.git
sudo apt install rust-all
cd modus
cargo update

Voici le contenu du builde :

build_env :-
  (from("rust:alpine")::set_workdir("/usr/src/app"),
   # musl-dev required for tokio (https://github.com/hound-search/hound/issues/238#issuecomment-336915272),
   # protobuf-dev required for buildkit-proto -> tonic-build
   # git required for our build.rs
   run("apk add --no-cache musl-dev protobuf-dev git"))
       ::set_env("PROTOC", "/usr/bin/protoc")
       ::set_env("PROTOC_INCLUDE", "/usr/include/google/protobuf").

run_env :- from("alpine")::set_workdir("/usr/src/app").

cargo_build("debug") :- run(f"cargo build").
cargo_build("release") :- run(f"cargo build --release").

build_deps(profile) :-
  copy("modus-lib/Cargo.toml", "modus-lib/"),
  copy("modus/Cargo.toml", "modus/"),
  copy("Cargo.toml", "."),
  copy("Cargo.lock", "."),
  copy("rust-toolchain.toml", "."),
  run("mkdir modus-lib/src modus/src &&\
       echo 'pub struct Nothing;' > modus-lib/src/lib.rs &&\
       echo 'fn main () {}' > modus/src/main.rs &&\
       echo 'fn main () {}' > modus/src/buildkit_frontend.rs"),
  cargo_build(profile).

build(profile) :-
  build_env,
  build_deps(profile),
  copy(".", "."),
  # Since cargo use the timestamp of build artifact to determine if it needs to
  # rebuild, and the source file copied in will be older than the previous build
  # done by build_deps, we touch all the source files to force a re-build.
  run("touch modus-lib/src/* modus/src/*"),
  cargo_build(profile).

frontend_img(profile) :-
  (run_env,
    profile_to_target_folder(profile, target_folder),
    # Work-around for https://github.com/modus-continens/modus/issues/98
    # We need to transpile this to Dockerfile to bootstrap the frontend.
    # Normally just saying f"${target_folder}/..." is good enough.
    build(profile)::copy(f"/usr/src/app/${target_folder}/buildkit-frontend", "./buildkit-frontend")
  )::set_entrypoint(["./buildkit-frontend"]).

modus(profile) :-
  (run_env,
    profile_to_target_folder(profile, target_folder),
    build(profile)::copy(f"/usr/src/app/${target_folder}/modus", "./modus")
  )::set_entrypoint(["./modus"]).

profile_to_target_folder("debug", "target/debug").
profile_to_target_folder("release", "target/release").

all("modus", profile) :- modus(profile).
all("frontend", profile) :- frontend_img(profile).

all(X) :- all(X, "debug").

On voit ici que ce Modusfile peut construire deux versions de l'image de conteneur : modus et fronted. On peut les construire toutes les deux avec la requete all.

On voit aussi que pour la version debug on peut ajouter des packages supplémentaires nécessaires à ce mode. Cela permet en effet de réduire la taille dees images de production, mais aussi de limiter la présence de package qui pourrait poser des problèmes de sécurité. Allez on lance le build des images :

modus build . 'all(X)'

Pour builder aussi les images de debug, ajoutons le predicat debug. Ajoutons l'option --json qui va nous permettre de tager les images :

modus build . 'all(X, debug)' --json=build.json
jq '.[] | [.digest, .predicate + ":" + (.args | join("-"))] | join(" ")' build.json | xargs -I % sh -c 'docker tag %'
docker images
REPOSITORY              TAG                  IMAGE ID       CREATED       SIZE
python                  3.10-slim-bullseye   af1108142cf5   12 days ago   126MB
debian                  11                   dd8bae8d259f   12 days ago   124MB
all                     frontend-release     dd18d71e850c   3 weeks ago   15.7MB
all                     modus-debug          93c7afa90409   3 weeks ago   62.7MB
all                     frontend-debug       6a1960fb25da   3 weeks ago   121MB
all                     modus-release        1cc74b931a7e   3 weeks ago   12.2MB

Conclusion

Je trouve que modus offre des possibilités qui permettent d'optimiser la production d'images OCI. C'est un outil en cours de développement et il manque de quelques options comme tager les images directement. Par contre, encore un nouveau langage à apprendre qui est bien différent de ceux que nous utilisons habituellement. Donc à suivre.

Lien vers le projet