Aller au contenu

Modus peut il remplacer les Dockerfiles ?

logo docker

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.

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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