CI (continous integration)
Depuis des années nous utilisons MkDOcs pour générer notre documentation utilisateur.
Deuis des années nous utilisions le fichier .gitlab-ci.yml suivant
# A partir de l'image ubuntu bionic https://hub.docker.com/_/ubuntu
image: ubuntu:bionic
# Avant toute chpse
before_script:
# mettre à jour les repo
- apt-get update
# installer ssh-agent si cela est nécessaire https://linux.die.net/man/1/ssh-agent
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
# ouvrir un shell de commandes sur la sortie standard
- eval $(ssh-agent -s)
# ajouter la clé privée stockée dans les secrets de la ci sur gitlab via https://gitlab.isima.fr/cri/doc-user/-/settings/ci_cd > expand > collapse
- ssh-add <(echo "$SSH_PRIVATE_KEY")
# créer un dossier .ssh dans le home directory de l'utilisateur courant
- mkdir -p ~/.ssh
# forcer l'acceptation des clés de l'ihôte distant
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
# déclarer 2 étapes
stages:
# une étape de construction de l'html
- build
# une étape de déploiement de l'html généré
- deploy
# nommer l'étape de déploiement build_job
build_job:
# associer build_job à l'étape de build décllarée plus haut
stage: build
script:
# exporter dses variables d'environnement pour obtenir la bonne langue lors de la génération du html
- export LC_ALL=C.UTF-8
- export LANG=C.UTF-8
# installer tout le nécessaire pour python et pip
- apt-get install -y --force-yes build-essential python3-dev python3-pip
# installer tous les paquets pip mentionnées dans le fichier requirements.txt de notre repo (notez que tout les fichier de notre repo sont accessibles depuis notre conteneur)
- pip3 install -r requirements.txt
# hum je crois que cette ligne ne servait à rien dans la mesure ou nous n'utilisaon pas d'envirionnement virtuel python ...
- source bin/activate
# avec mkdocs généré l'html statiques à partir des fichier markdown du répertoire docs de notre repo
- mkdocs build
# l'html est généré dans le répertoire ./site
artifacts:
# cette directive permet de conserver le répertoire généré pour l'étape suivante
untracked: true
# nommer l'étape de déploiement deploy_job
deploy_job:
# associer deploy_job à l'étape de build décllarée plus haut
stage: deploy
# rendre deploy_job dépendant de l'étape build_job i.e. deploy_job ne s'exécute que si build_job c'est exécuté sans erreur
dependencies:
- build_job
script:
# installer rsync
- apt-get install -y --force-yes rsync
# synchroniser les fichiers générés dans site/ avec les fichiers du serveur distant (username est également un secret de CI)
- rsync -az -e ssh site/ $SSH_USERNAME@doc.isima.fr:/var/www/doc.isima.fr/web
Sans être très complexe, cette approche est coûteuse à différents points de vue:
- télécharger une image système entier pour utiliser une commande est probablement surdimensionné
- la mise à jour des repos est longue, et couteuse en requếtes réseaux
- l'installation des différents paquets est couteuse en temps
- la politique des gitlab runners étaient jusqu'à il y a peu de toujours télécharger l'image afin d'avoir la denière version. La politique de docker hub est de limité le nombre de téléchargement d'image (
docker pull) à 200 par jour pour un utilisateur non authentifié ... or tous les giltab-runners sortent avec la même IP, donc toutes les CI git gitlab isima et gitlab limos sont décomptés pour un utilisateur
Tous ces coûts se répétent à chaque exécution de la CI, donc à chaque commit sur notre doc ... que nous mettons souvent à jour.
De plus l'utilisation du conteneur est circonscrite au seul contexte de la CI. Si nous pouvions utiliser docker également en local nous pourrions bénéficier d'une meilleure reproducitbilité, en garantissant les mêmes versions de toutes les dépendances. En effet s'il est possible de spécifier les versions des paquets pip dans requirements.txt rien ne nous permet, en l'état, de spécifier la version de python qui va exécuter mkdocs, ni même la version de pip qui va installer les paquets.
Vous pouvez parcourir la documentation officielle pour en savoir plus sur la CI Gitlab
Choisir son image
Si partir d'un système que l'on connaît n'est pas une mauvaise approche, c'est souvent une bonne idée de regarder ce qui existe déjà dans le monde docker sur https://hub.docker.com/.
Chaque image possède un namespace sou la forme <registry>/<image>/<name>.
Si <registry> n'est pas spécifié il s'agit implicitement du registry dockerhub.
Dans les candidats:
-
https://hub.docker.com/_/ubuntu
-
git pull ubuntu/xenial -
https://hub.docker.com/_/python
-
git pull python/3.7-slim -
si vous voulez savoir pourquoi plutôt
-slimqu'un autre type d'image -
https://hub.docker.com/r/squidfunk/mkdocs-material
-
git pull squidfunk/mkdocs-material
Lors de la sélection des images il est souvent instructif de chercher le repo github associé afin de voir ce que fait le Dockerfile utilisé pour builder l'image.
Il est également intéressant de regarder la pulse du projet: date de dernière mise à jour, combien de contributeurs sur le projet, fréquence des mises à jours, nombre d'issues ouvertes et fermées ...
Après avoir tirer toutes ces images on peut se faire une idée de leur taille respective avec un docker image ls:
REPOSITORY TAG IMAGE ID CREATED SIZE
squidfunk/mkdocs-material latest 05fb07a7cc1a 38 hours ago 99.6MB
python 3.7-slim 3e12d0db6381 3 weeks ago 120MB
ubuntu focal ba6acccedd29 5 weeks ago 72.8MB
ubuntu bionic 5a214d77f5d7 7 weeks ago 63.1MB
ubuntu xenial b6f507652425 2 months ago 135MB
Construire un image
Pour construire une image il nous faut un fichier Dockerfile.
Docker met à disposition un ensemble de commandes utilisables dans les Dockerfile
Nous allons commencer par tester un build minimaliste avec ubuntu/focal, en utilisant la commande FROM qui permet de spécifier une image existante sur laquelle nous allons travailler
Contenu du Dockerfile
FROM ubuntu:focal
Construire l'image mkdocs à partir de Dockerfile
$ docker build -t mkdocs .
Sending build context to Docker daemon 1.45GB
Step 1/1 : FROM ubuntu:focal
---> ba6acccedd29
Successfully built ba6acccedd29
Successfully tagged mkdocs:latest
un docker image ls donne:
mkdocs latest ba6acccedd29 5 weeks ago 72.8MB
ubuntu focal ba6acccedd29 5 weeks ago 72.8MB
En vérité nous avons juste copier l'image ubuntu en une image nommée mkdocs. Notez qu'elle a automatiquement été taggé latest par docker
Exécuter des commandes à la construction d'une image
La commande RUN permet d'exécuter des commandes systèmes pendant la construction d'une image, c'est le bon endroit pour installer nos dépendances. Commençons par les dépendances python.
Contenu du Dockerfile
FROM ubuntu:focal
RUN apt-get update
RUN apt-get install -y --allow build-essential python3-dev python3-pip
$ docker build -t mkdocs .
Sending build context to Docker daemon 1.45GB
Step 1/3 : FROM ubuntu:focal
---> ba6acccedd29
Step 2/3 : RUN apt-get update
---> Running in 2ef776c9893a
Get:1 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
..
Fetched 19.8 MB in 1s (16.2 MB/s)
Reading package lists...
Removing intermediate container 2ef776c9893a
---> 4f947ee6c4fd
Step 3/3 : RUN apt-get install -y --force-yes build-essential python3-dev python3-pip
---> Running in 2227f0637319
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
binutils binutils-common binutils-x86-64-linux-gnu ca-certificates cpp cpp-9
...
Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
W: --force-yes is deprecated, use one of the options starting with --allow instead.
Removing intermediate container 2227f0637319
---> 6e3956e6d628
Successfully built 6e3956e6d628
Successfully tagged mkdocs:latest
Notez les différentes étapes (Step). Docker créée un "calque" (layer) à chaque commande du Dockerfile. Un calque peut être vu comme un snapshot du système associé à la commande exécutrée (ici RUN). Cette gestion a beaucoup d'avantages:
- lors de la construction d'une image, seuls les calques impactés par la modification du Dockerfile seront mis à jour
- lors du téléchargement d'une image (docker pull) seuls les calques à jour seront téléchargés
- il est aussi possible d'écrire d'un calque dans un autre avec la commande COPY
un docker image ls donne:
mkdocs latest 6e3956e6d628 12 seconds ago 412MB
ubuntu focal ba6acccedd29 5 weeks ago 72.8MB
Notre image à pris du poids! Nous verrons comment l'alléger un peu plus tard en jouant avec ces fameux calques
Exécuter un conteneur
Que peut on attendre de notre image mkdocs en l'état ... probablement pas grand chose.
Testons la commande docker run
$ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
docker run mkdocs ne produit pas grand chose. En effet une image docker si elle n'exécute pas un service en avant plan, rend la main ...
C'est pourquoi un docker ps n'affichera rien.
Alors que peut on faire avec cette image?
Observoncs le Dockerfile qui a servi à construire l'image de laquelle nous sommes partis
FROM scratch
ADD ubuntu-focal-oci-amd64-root.tar.gz /
CMD ["bash"]
1 - on apprend qu'il est possible de partir d'une image vierge avec FROM scratch. Rien de surprenant à retrouver cette commande pour une image officelle d'OS
2 - la commande CMD est la commande par défaut exécuter par notre container. il est donc possible de passer des paramètres à cette commande (ici bash) à la fin de la commande docker run.
Essayons
$ docker run mkdocs pip
Usage:
pip <command> [options]
Commands:
install Install packages.
download Download packages.
uninstall Uninstall packages.
freeze Output installed packages in requirements format.
list List installed packages.
show Show information about installed packages.
check Verify installed packages have compatible dependencies.
config Manage local and global configuration.
search Search PyPI for packages.
wheel Build wheels from your requirements.
hash Compute hashes of package archives.
completion A helper command used for command completion.
debug Show information useful for debugging.
help Show help for commands.
General Options:
...
Il semblerait que notre image a bien pip d'installer \o/
Reste à installer toutes nos dépendances, et à regrouper le tout afin de ne pas créer de calques inutiles.
FROM ubuntu:focal
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
openssh-client \
python3-dev \
python3-pip \
rsync
COPY requirements.txt .
RUN pip install --user -r requirements.txt
notez la commande COPY qui permet de copier le fichier requirements.txt sur notre système, vers . du container.
Docker dispose également d'une commande ADD légèrement différente de COPY.
voir aussi: Docker Tip #2: The Difference between COPY and ADD in a Dockerfile
$ docker build -t mkdocs .
un docker image ls donne:
mkdocs latest 6e3956e6d628 12 seconds ago 482MB
ubuntu focal ba6acccedd29 5 weeks ago 72.8MB
Sans surprise notre image a encore enflée.
Parcontre elle ne fait toujours pas grand chose, et se termine toujours aussitôt lancée
Lançons notre conteneur et passons lui un sleep pour le maintenir articiellement up pour 5 minutes
docker run mkdocs sleep 300
voyons voir si notre conteneur tourne
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5254e7534930 mkdocs "sleep 300" 36 seconds ago Up 34 seconds practical_cori
Notez que docker a nommé aléatoirement ce conteneur en exécution.
Nous pouvons donner un nom avec la commande docker run --name mkdocs_container mkdocs sleep 300
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7f6126c4b13 mkdocs "sleep 300" 9 seconds ago Up 8 seconds mkdocs_container
Si vous relancez ce conteneur avec ce nom il vous faudra alors le supprimer et pour cela l'arrêter
$ docker stop mkdocs_container
$ docker rm mkdocs_container
Explorer un container en cours d'exécution
La commande docker exec permet d'exécuter des commandes sur un conteneur en cours d'exécution.
Lançons maintenant un shell interactif sur notre conteneur
$ docker exec -it mkdocs_container /bin/bash
root@781021be46e5:/# /root/.local/bin/mkdocs
Usage: mkdocs [OPTIONS] COMMAND [ARGS]...
MkDocs - Project documentation with Markdown.
Options:
-V, --version Show the version and exit.
-q, --quiet Silence warnings
-v, --verbose Enable verbose output
-h, --help Show this message and exit.
Commands:
build Build the MkDocs documentation
gh-deploy Deploy your documentation to GitHub Pages
new Create a new MkDocs project
serve Run the builtin development server
root@781021be46e5:/# /root/.local/bin/mkdocs build
Config file '/mkdocs.yml' does not exist.
la commande mkdocs est disponible dans notre conteneur \o/
En revanche notre code source n'est pas disponible à l'intérieur du conteneur.
On pourrait le copier au moment du build avec la commade COPY mais cela risque d'allourdir notre image et surtout il faudrait reconstruire l'image à chaque modification de notre source.
Exposer un volume
Il est possible d'accéder à notre code source via un volume.
C'est la commande VOLUME qui va nous permettre d'exposer les fichiers présents sur notre système dans ..
Dans notre Dockerfile
FROM ubuntu:focal
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
openssh-client \
python3-dev \
python3-pip \
rsync
COPY requirements.txt .
RUN pip install --user -r requirements.txt
WORKDIR /srv/mkdocs
RUN mkdir site
VOLUME ["/srv/mkdocs/"]
Ces 3 dernières lignes vont nous permettre de retrouver les fichiers présents sur le système dans . dans le répertoire /srv/mkdocs du conteneur.
Elles sont interprétées à l'exécution, elles ne créent pas de calques supplémentaires, donc il n'y a pas besoin de reconstruire l'image.
$ docker stop mkdocs_container
$ docker rm mkdocs_container
$ docker run --volume $(pwd):/srv/mkdocs --name mkdocs_container mkdocs sleep 300
$ docker exec -it mkdocs_container /bin/bash
root@cc24c9e0c0bf:/# cd /srv/
root@cc24c9e0c0bf:/srv# /root/.local/bin/mkdocs build
INFO - Cleaning site directory
INFO - Building documentation to directory: /srv/site
INFO - The following pages exist in the docs directory, but are not included in the "nav" configuration:
- clouds/onedrive/presentation.md
- lecteurs_reseaux/glouglou.md
- mail/isima.md
- mail/webmail.md
- mail/sogo/99-add-mail-account.md
- pedagogie/remote_access/linux.md
- pedagogie/remote_access/windows.md
- sauvegardes/windows.md
- temp_doc/change_domain.md
- visio/te50.md
- vpn/connection.md
- vpn/faq.md
- vpn/installation.md
INFO - Documentation built in 6.94 seconds
On dirait bien qu'on a construit notre documentation \o/.
en listant notre répertoire courant
ls -al
total 76
drwxrwxr-x 8 mazenovi mazenovi 4096 nov. 10 11:46 .
drwxrwxr-x 5 mazenovi mazenovi 4096 sept. 2 17:16 ..
drwxrwxr-x 2 mazenovi mazenovi 4096 nov. 9 09:36 bin
drwxrwxr-x 6 mazenovi mazenovi 4096 oct. 6 10:40 custom_theme
-rw-rw-r-- 1 mazenovi mazenovi 268 nov. 25 10:07 Dockerfile
-rw-rw-r-- 1 mazenovi mazenovi 793 nov. 23 10:39 Dockerfile.prod
drwxrwxr-x 35 mazenovi mazenovi 4096 sept. 7 14:14 docs
drwxrwxr-x 9 mazenovi mazenovi 4096 nov. 10 11:46 .git
-rw-rw-r-- 1 mazenovi mazenovi 50 août 30 13:13 .gitignore
-rw-rw-r-- 1 mazenovi mazenovi 510 nov. 10 11:46 .gitlab-ci.yml
-rw-rw-r-- 1 mazenovi mazenovi 1622 août 30 13:13 LICENCE.md
-rw-rw-r-- 1 mazenovi mazenovi 747 nov. 10 10:12 Makefile
-rw-rw-r-- 1 mazenovi mazenovi 5959 nov. 9 14:17 mkdocs.yml
-rw-rw-r-- 1 mazenovi mazenovi 202 août 30 13:13 pyvenv.cfg
-rw-rw-r-- 1 mazenovi mazenovi 766 nov. 10 10:13 README.md
-rw-rw-r-- 1 mazenovi mazenovi 121 nov. 9 14:17 requirements.txt
drwxr-xr-x 41 root root 4096 nov. 25 11:39 site
drwxrwxr-x 4 mazenovi mazenovi 4096 août 30 14:32 .venv
On s'aperçoit qu'un répertoire site contenant tout le html généré a été créé.
En fait nous avons créé ce répertoire vide à partir du docker file et la commande mkdocs build a écrit les fihgiers html générés ensuite.
Notez que le propriétaire du répertoire est root. Nous y reviendrons plus tard.
Exposer une commande
Docker dispose d'une command CMD qui permet au conteneur d'exposer directement une commande.
FROM ubuntu:focal
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
openssh-client \
python3-dev \
python3-pip \
rsync
COPY requirements.txt .
RUN pip install --user -r requirements.txt
WORKDIR /srv/mkdocs
RUN mkdir site
VOLUME ["/srv/mkdocs/"]
CMD ["/root/.local/bin/mkdocs","build"]
on reconstruit l'image
$ docker build -t mkdocs .
$ docker stop mkdocs_container && docker rm mkdocs_container
mkdocs_container
mkdocs_container
$ docker run --volume $(pwd):/srv/mkdocs/ --name mkdocs_container mkdocs
INFO - Cleaning site directory
INFO - Building documentation to directory: /srv/mkdocs/site
INFO - The following pages exist in the docs directory, but are not included in the "nav" configuration:
- clouds/onedrive/presentation.md
- lecteurs_reseaux/glouglou.md
- mail/isima.md
- mail/webmail.md
- mail/sogo/99-add-mail-account.md
- pedagogie/remote_access/linux.md
- pedagogie/remote_access/windows.md
- sauvegardes/windows.md
- temp_doc/change_domain.md
- visio/te50.md
- vpn/connection.md
- vpn/faq.md
- vpn/installation.md
INFO - Documentation built in 7.21 seconds
Nous avons construit une image docker capable de générés les pages statiques avec mkdocs à partir de nos source sans avoir mkdocs installé sur notre machine ;)
