Conteneuriser CMatrix
Objectifs de cet atelier pratique
- 
Utiliser l'historique de la compilation de CMatrix pour rédiger un Dockerfile
- 
Conteneuriser CMatrix avec un multi-stage build optimisé 
Nettoyer le Dockerfile
Ouvrez le Dockerfile du précédent atelier pratique avec un éditeur de texte
et commencez par supprimer les numéros de ligne de l'historique. Ne vous en
faites pas si vous n'avez pas exactement le même résultat que moi :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
git clone https://github.com/abishekvashok/cmatrix
apk update
apk add git
git clone https://github.com/abishekvashok/cmatrix
cd cmatrix/
ls -l
autoreconf -i
apk add autoconf
autoreconf -i
apk add automake
autoreconf -i
echo $?
./configure LDFLAGS="-static"
apk add alpine-sdk
./configure LDFLAGS="-static"
mkdir -pv /usr/lib/kbd/consolefonts
mkdir -pv /usr/share/consolefonts
apk add ncurses-dev ncurses-static
./configure LDFLAGS="-static"
make
ls -lh ./cmatrix
./cmatrix
history
Identifiez toutes les étapes où nous avons avancé à tâtons pour expérimenter et/ou qui se sont soldées par un échec et commentez-les :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
# git clone https://github.com/abishekvashok/cmatrix
apk update
apk add git
git clone https://github.com/abishekvashok/cmatrix
cd cmatrix/
# ls -l
# autoreconf -i
apk add autoconf
# autoreconf -i
apk add automake
autoreconf -i
# echo $?
# ./configure LDFLAGS="-static"
apk add alpine-sdk
# ./configure LDFLAGS="-static"
mkdir -pv /usr/lib/kbd/consolefonts
mkdir -pv /usr/share/consolefonts
apk add ncurses-dev ncurses-static
./configure LDFLAGS="-static"
make
# ls -lh ./cmatrix
./cmatrix
# history
Les instructions RUN et CMD
Nous voilà avec un bon premier jet de la procédure nécessaire pour compiler un
binaire cmatrix depuis le code source.
- 
Nous allons ajouter l'instruction RUNà chacune de ces étapes, ce qui exécutera chaque commande en ajoutant à chaque fois un layer à notre image.
- 
L'instruction CMDservira à exécuter le binaire résultantcmatrix.
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
# git clone https://github.com/abishekvashok/cmatrix
RUN apk update
RUN apk add git
RUN git clone https://github.com/abishekvashok/cmatrix
RUN cd cmatrix/
# ls -l
# autoreconf -i
RUN apk add autoconf
# autoreconf -i
RUN apk add automake
RUN autoreconf -i
# echo $?
# ./configure LDFLAGS="-static"
RUN apk add alpine-sdk
# ./configure LDFLAGS="-static"
RUN mkdir -pv /usr/lib/kbd/consolefonts
RUN mkdir -pv /usr/share/consolefonts
RUN apk add ncurses-dev ncurses-static
RUN ./configure LDFLAGS="-static"
RUN make
# ls -lh ./cmatrix
CMD ["./cmatrix"]
# history
Une approche itérative
Dans un premier temps, notre approche sera inefficace, pour ne pas dire
pataude. Notre façon de procéder va même générer une série d'erreurs, mais
c'est tout à fait intentionnel. Petit à petit, nous allons corriger ces
erreurs et optimiser notre Dockerfile de façon itérative jusqu'à ce que
nous ayons un conteneur léger et rapide.
Faisons un premier test pour voir ce que ça donne :
La construction de l'image a échoué à la huitième étape. Jetons un œil sur le
Dockerfile :
Ici, nous avons utilisé la commande cd pour nous placer dans le répertoire
cmatrix. Or, ce changement de répertoire n'est pas persistant pour les
instructions RUN subséquentes.
L'instruction WORKDIR
Nous allons utiliser l'instruction WORKDIR qui définit le répertoire de
travail pour toutes les instructions RUN, COPY, ADD, CMD et
ENTRYPOINT subséquentes. Si le répertoire en question n'existe pas,
l'instruction WORKDIR se chargera de le créer :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
WORKDIR /cmatrix
# git clone https://github.com/abishekvashok/cmatrix
RUN apk update
RUN apk add git
RUN git clone https://github.com/abishekvashok/cmatrix .
# ls -l
# autoreconf -i
RUN apk add autoconf
# autoreconf -i
RUN apk add automake
RUN autoreconf -i
# echo $?
# ./configure LDFLAGS="-static"
RUN apk add alpine-sdk
# ./configure LDFLAGS="-static"
RUN mkdir -pv /usr/lib/kbd/consolefonts
RUN mkdir -pv /usr/share/consolefonts
RUN apk add ncurses-dev ncurses-static
RUN ./configure LDFLAGS="-static"
RUN make
# ls -lh ./cmatrix
CMD ["./cmatrix"]
# history
Récupérer le code source dans le répertoire courant
Notez que nous ajoutons un . en argument à notre git clone de manière à
ce que le code source de CMatrix soit récupéré dans le répertoire courant.
À partir de là, la construction de l'image devrait se terminer avec succès :
$ docker build -t kikinovak/cmatrix .
[+] Building 42.7s (18/18) FINISHED                                    docker
 => [internal] load build definition from Dockerfile                   0.0s
 => => transferring dockerfile: 805B                                   0.0s
 => [internal] load metadata for docker.io/library/alpine:latest       0.0s
 => [internal] load .dockerignore                                      0.0s
 => => transferring context: 2B                                        0.0s
 => CACHED [ 1/14] FROM docker.io/library/alpine:latest                0.0s
 => [ 2/14] WORKDIR /cmatrix                                           0.0s
 => [ 3/14] RUN apk update                                             1.9s
 => [ 4/14] RUN apk add git                                            4.0s
 => [ 5/14] RUN git clone https://github.com/abishekvashok/cmatrix .   1.6s
 => [ 6/14] RUN apk add autoconf                                       4.0s
 => [ 7/14] RUN apk add automake                                       1.1s
 => [ 8/14] RUN autoreconf -i                                          2.4s
 => [ 9/14] RUN apk add alpine-sdk                                    21.5s
 => [10/14] RUN mkdir -pv /usr/lib/kbd/consolefonts                    0.3s
 => [11/14] RUN mkdir -pv /usr/share/consolefonts                      0.2s
 => [12/14] RUN apk add ncurses-dev ncurses-static                     2.5s
 => [13/14] RUN ./configure LDFLAGS="-static"                          1.5s
 => [14/14] RUN make                                                   0.5s
 => exporting to image                                                 1.0s
 => => exporting layers                                                1.0s
 => => writing image sha256:518e202f0446ae04bb20a147eea94dc2d80f1...   0.0s
 => => naming to docker.io/kikinovak/cmatrix                           0.0s
Jetons un œil sur l'image résultante :
$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
kikinovak/cmatrix   latest    316c1d277496   34 seconds ago   327MB
alpine              latest    706db57fb206   7 days ago       8.32MB
Premier test
Qu'est-ce qui se passe si nous lançons un conteneur depuis l'image ? Essayons :
Parfait ! Apparemment tout se passe comme prévu. Mais pour l'instant c'est
loin d'être parfait. Notre conteneur a un embonpoint conséquent (plus de 300
Mo) pour un simple binaire cmatrix.
Jusqu'ici nous n'avons appliqué aucune des bonnes pratiques conseillées pour la
rédaction d'un Dockerfile, et nous disposons de bien trop de layers.
Élaguer le Dockerfile
Commençons déjà par nous débarrasser de toutes les sections commentées :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
WORKDIR /cmatrix
RUN apk update
RUN apk add git
RUN git clone https://github.com/abishekvashok/cmatrix .
RUN apk add autoconf
RUN apk add automake
RUN autoreconf -i
RUN apk add alpine-sdk
RUN mkdir -pv /usr/lib/kbd/consolefonts
RUN mkdir -pv /usr/share/consolefonts
RUN apk add ncurses-dev ncurses-static
RUN ./configure LDFLAGS="-static"
RUN make
CMD ["./cmatrix"]
Combiner les commandes d'installation
La commande apk add est invoquée plusieurs fois de suite à tire larigot. Nous
pouvons combiner toutes ces commandes en une seule :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs"
LABEL org.opencontainers.image.description="Container image for CMatrix"
WORKDIR /cmatrix
RUN apk update
RUN apk add git autoconf automake alpine-sdk ncurses-dev ncurses-static
RUN git clone https://github.com/abishekvashok/cmatrix .
RUN autoreconf -i
RUN mkdir -pv /usr/lib/kbd/consolefonts
RUN mkdir -pv /usr/share/consolefonts
RUN ./configure LDFLAGS="-static"
RUN make
CMD ["./cmatrix"]
Si nous relançons la construction de l'image, nous voyons que nous n'avons plus qu'une dizaine de layers :
Combiner les instructions
La prochaine étape dans notre recherche d'optimisation consiste à combiner les
instructions. La barre oblique inversée \ nous permet d'obtenir des
instructions multi-lignes :
LABEL org.opencontainers.image.authors="Nicolas Kovacs" \
      org.opencontainers.image.description="Container image for CMatrix"
Nous pouvons faire de même avec l'instruction RUN en combinant les commandes
à l'aide de l'opérateur && qui effectue une commande lorsque la commande
précédente s'est terminée avec succès :
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs" \
      org.opencontainers.image.description="Container image for CMatrix"
WORKDIR /cmatrix
RUN apk update && \
    apk add git autoconf automake alpine-sdk ncurses-dev ncurses-static && \
    git clone https://github.com/abishekvashok/cmatrix . && \
    autoreconf -i && \
    mkdir -pv /usr/lib/kbd/consolefonts && \
    mkdir -pv /usr/share/consolefonts && \
    ./configure LDFLAGS="-static" && \
    make
CMD ["./cmatrix"]
Si nous relançons la construction de l'image, nous voyons que nous en sommes à trois layers, avec un conteneur qui s'exécute toujours correctement.
Le souci, c'est que l'image est toujours énorme, étant donné qu'elle contient toutes les briques logicielles nécessaires à la compilation du code.
Comment est-ce qu'on pourrait réduire tout ça ?
Est-ce qu'il n'y aurait pas moyen de compiler le code source pour ensuite extraire juste le résultat de cette opération ? La réponse est oui, et nous allons nous servir d'un multi-stage build (construction en plusieurs étapes) pour ce faire.
Le multi-stage build
Notre Dockerfile sera organisé en deux sections distinctes :
# Build Container Image
FROM alpine AS cmatrixbuilder
WORKDIR /cmatrix
RUN apk update && \
    apk add git autoconf automake alpine-sdk ncurses-dev ncurses-static && \
    git clone https://github.com/abishekvashok/cmatrix . && \
    autoreconf -i && \
    mkdir -pv /usr/lib/kbd/consolefonts && \
    mkdir -pv /usr/share/consolefonts && \
    ./configure LDFLAGS="-static" && \
    make
# CMatrix Container Image
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs" \
      org.opencontainers.image.description="Container image for CMatrix"
COPY --from=cmatrixbuilder /cmatrix/cmatrix /cmatrix
CMD ["./cmatrix"]
Relançons la construction de l'image et voyons le résultat :
$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
kikinovak/cmatrix   latest    fe0f4c200429   12 seconds ago   9.13MB
alpine              latest    706db57fb206   7 days ago       8.32MB
La réduction en termes de taille est assez spectaculaire. Mais ne nous réjouissons pas trop tôt. Voici ce que nous obtenons lorsque nous lançons un conteneur :
$ docker run --rm -it kikinovak/cmatrix
ncurses: cannot initialize terminal type ($TERM="xterm"); exiting
Qu'est-ce qui manque ?
Nous en avons parlé dans le précédent atelier pratique : CMatrix a
besoin de NCurses, et plus précisément du paquet ncurses-terminfo-base comme
dépendance d'exécution.  Il va donc falloir ajouter ce paquet à notre
image :
# Build Container Image
FROM alpine AS cmatrixbuilder
WORKDIR /cmatrix
RUN apk update --no-cache && \
    apk add git autoconf automake alpine-sdk ncurses-dev ncurses-static && \
    git clone https://github.com/abishekvashok/cmatrix . && \
    autoreconf -i && \
    mkdir -pv /usr/lib/kbd/consolefonts && \
    mkdir -pv /usr/share/consolefonts && \
    ./configure LDFLAGS="-static" && \
    make
# CMatrix Container Image
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs" \
      org.opencontainers.image.description="Container image for CMatrix"
RUN apk update --no-cache && apk add ncurses-terminfo-base
COPY --from=cmatrixbuilder /cmatrix/cmatrix /cmatrix
CMD ["./cmatrix"]
Supprimer le cache des paquets
Notez en passant que j'ai ajouté l'option --no-cache à la commande apk
update. Comme son nom le suggère, cette option permet de ne pas garder les
méta-informations dans le cache et de réduire encore plus la taille de
l'image résultante.
On relance la construction. L'image a augmenté un tout petit peu en taille :
$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
kikinovak/cmatrix   latest    0c673c178130   23 seconds ago   11.8MB
alpine              latest    706db57fb206   7 days ago       8.32MB
En revanche, elle fonctionne parfaitement :
Définir un utilisateur non privilégié
La prochaine étape dans le perfectionnement de notre image concerne
l'utilisateur. Pour l'instant notre conteneur s'exécute en tant que
root :
Nous allons ajouter un utilisateur « commun mortel » pour exécuter notre application :
# Build Container Image
FROM alpine AS cmatrixbuilder
WORKDIR /cmatrix
RUN apk update --no-cache && \
    apk add git autoconf automake alpine-sdk ncurses-dev ncurses-static && \
    git clone https://github.com/abishekvashok/cmatrix . && \
    autoreconf -i && \
    mkdir -pv /usr/lib/kbd/consolefonts && \
    mkdir -pv /usr/share/consolefonts && \
    ./configure LDFLAGS="-static" && \
    make
# CMatrix Container Image
FROM alpine
LABEL org.opencontainers.image.authors="Nicolas Kovacs" \
      org.opencontainers.image.description="Container image for CMatrix"
RUN apk update --no-cache && \
    apk add ncurses-terminfo-base && \
    adduser -g "Thomas Anderson" -s /usr/sbin/nologin -D -H thomas
RUN apk update --no-cache && apk add ncurses-terminfo-base
COPY --from=cmatrixbuilder /cmatrix/cmatrix /cmatrix
USER thomas
CMD ["./cmatrix"]
- 
-gpour le nom complet de l'utilisateur
- 
-spour le shell de l'utilisateur
- 
-Dpour désactiver le mot de passe
- 
-Hpour éviter la création d'un répertoire utilisateur
Je reconstruis l'image et je vérifie :
Gérer les options de CMatrix
Il nous reste un dernier point de détail à régler. C'est que l'application
cmatrix est susceptible d'être lancée avec toute une série d'options. Voyez
par vous-même :
$ docker run --rm -it kikinovak/cmatrix sh
/ $ ./cmatrix --help
 Usage: cmatrix -[abBcfhlsmVxk] [-u delay] [-C color] [-t tty] [-M message]
 -a: Asynchronous scroll
 -b: Bold characters on
 -B: All bold characters (overrides -b)
 -c: Use Japanese characters as seen in the original matrix. 
     Requires appropriate fonts
 -f: Force the linux $TERM type to be on
 -l: Linux mode (uses matrix console font)
 -L: Lock mode (can be closed from another terminal)
 -o: Use old-style scrolling
 -h: Print usage and exit
 -n: No bold characters (overrides -b and -B, default)
 -s: "Screensaver" mode, exits on first keystroke
 -x: X window mode, use if your xterm is using mtx.pcf
 -V: Print version information and exit
 -M [message]: Prints your message in the center of the screen. 
               Overrides -L's default message.
 -u delay (0 - 10, default 4): Screen update delay
 -C [color]: Use this color for matrix (default green)
 -r: rainbow mode
 -m: lambda mode
 -k: Characters change while scrolling. (Works without -o opt.)
 -t [tty]: Set tty to use
Affichez des caractères gras :
Les caractères peuvent changer pendant le défilement :
Combinez ces options avec -r comme rainbow (arc-en-ciel) :
Pour l'instant notre image Docker permet la seule exécution de cmatrix sans
options. Nous allons donc modifier notre approche. CMD fournit un paramètre
par défaut pour exécuter un paramètre, mais nous pouvons très bien le remplacer
par ENTRYPOINT. Dans ce cas, CMD fournira la ou les options par défaut à
utiliser :
À partir de là, les arguments que nous fournirons à l'exécution du conteneur
constitueront autant de paramètres pour ENTRYPOINT avec le mode -b (bold
pour les caractères gras) activé par défaut :
Testez le défilement asynchrone :
Combinez le défilement asynchrone avec les caractères gras :
Accélérez le défilement :
Modifiez la couleur de l'affichage :
La suite au prochain atelier pratique, où nous allons voir en détail la construction de notre image dans le but de l'utiliser sur différentes architectures : processeurs Intel & AMD, processeurs Mac Silicon, Raspberry Pi, etc.
Remerciements
Cet atelier pratique s'inspire d'une série de cours de James Spurin, un formateur Linux & Open Source assez exceptionnel. Je tiens à le remercier ici.


