{{tag>sysadmin docker image}}
====== Docker: créer une image ======
Il y a plusieurs façons d'aboutir à une nouvelle image:
* A partir d'un conteneur existant et la commande docker container save, facile à mettre en place mais compliqué à maintenir.
* From scratch, plus complexe et difficile à maintenir.
* Via un Dockerfile, un fichier contenant les instructions de création en se basant sur une image existante.
Les instructions du Dockerfile sont listés ci-dessous:
FROM # Pour choisir l'image sur laquelle on se base, toujours en premier
RUN # Permet d'exécuter une commande
CMD # Commande exécutée au démarrage du conteneur par défaut
EXPOSE # Ouvre un port
ENV # Permet d'éditer des variables d'environnement
ARG # Variables utilisées seulement le temps de la construction de l'image
COPY # Permet de copier un fichier ou répertoire de l'hôte vers l'image
ADD # Permet de copier un fichier de l'hôte ou depuis une URL vers l'image, permet également de décompresser une archive tar
LABEL # Des métadonnées utiles pour certains logiciels de gestion de conteneurs, comme rancher ou swarm, ou tout simplement pour mettre des informations sur l'image.
ENTRYPOINT # Commande exécutée au démarrage du conteneur, non modifiable, utilisée pour package une commande
VOLUME # Crée une partition spécifique
WORKDIR # Permet de choisir le répertoire de travail
USER # Choisit l'utilisateur qui lance la commande du ENTRYPOINT ou du CMD
ONBUILD # Crée un step qui sera exécuté seulement si notre image est choisie comme base
HEALTHCHECK # Permet d'ajouter une commande pour vérifier le fonctionnement de votre conteneur
STOPSIGNAL # permet de choisir le [signal](http://man7.org/linux/man-pages/man7/signal.7.html) qui sera envoyé au conteneur lorsque vous ferez un docker container stop
===== Construire une image =====
Pour construire l'image on utilisera la commande **image build** ou **build**:
docker image build -t [imagename][:tag] aContextFolder
* La commande accepte un seul argument obligatoire (ici "aContextFolder"), c'est le chemin du ''build context'';
* L'option %%--tag%%, -t permet de définir à la fois le nom de l'image et la version.
==== A propos du contexte =====
L'image Docker est construite côté serveur. Le client Docker communique au serveur le ''Dockerfile et les fichiers présents dans le dossier en argument de la commande **docker build**'': c'est le contexte. Il est possible d'exclure des fichiers du contexte grâce au fichier **.dockerignore**. Le détail du fonctionnement du contexte est abordé dans le wiki [[sysadmin/docker/build_context|build context]].
==== Exemple ====
Dans l'exemple ci-dessous, on a écrit un fichier script.sh qui affiche régulièrement un message:
while true;
do
echo $(date +"%d-%m-%Y %H:%M:%S")": [APP version="$APP_VERSION"]"
sleep 5
done
exit 0
La variable APP_VERSION non définie dans le script sera présente dans l’environnement d'exécution. On souhaite partir d'une image de busybox existante pour y ajouter notre script
FROM busybox:1.24-glibc
RUN mkdir -p /usr/local/bin
COPY script.sh /usr/local/bin
CMD /usr/local/bin/script.sh
Pour construire la nouvelle image:
docker image build --tag test_script:v0.1.1 .
On peut ensuite créer le conteneur à partir de notre image:
docker container run --detach --env APP_VERSION="0.1" test_script:v0.1.1
===== Optimiser la taille des images =====
Chaque instruction modifiant la structure du système de fichier dans le Dockerfile produit un layer (calque). Un nombre important de layers conduit à une image de taille importante et peu nuire au temps d'accès de lecture/écriture. Pour éviter cela on groupe au maximum les commandes.
Ci dessous un exemple non optimisé:
# layer 1
RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git
# layer 2
RUN cpan install Carton
# layer 3
RUN cd / && git clone https://git.framasoft.org/luc/lutim.git
# layer 4
RUN cd /lutim && carton install
# layer 5
RUN apt-get purge -y make gcc ca-certificates libssl-dev git
# layer 6
RUN apt-get autoremove --purge -y && apt-get clean
#layer 7
RUN rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
Les mêmes commandes regroupées conduiront à la création d'un seul calque:
RUN apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git \
&& cpan install Carton \
&& cd / \
&& git clone https://git.framasoft.org/luc/lutim.git \
&& cd /lutim \
&& carton install \
&& apt-get purge -y make \
gcc \
ca-certificates \
libssl-dev \
git \
&& apt-get autoremove --purge -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
Depuis les versions 17.09 et supérieures de Docker les commandes COPY/ADD supportent le modificateur **%%--chown%%**. Cela permet d'éviter la création d'un calque supplémentaire lorsqu'une commande de changement de droits d'accès au fichier est enchaînée après la copie.
# Pour l'exemple on crée un fichier de 10 Mo que l'on va ajouter à une image
mkdir test
cd test
dd if=/dev/zero of=file.dat bs=1M count=10
Pour illustrer la problématique on va utiliser une image busybox d'une taille initiale approximative de 1 Mo
# Pour lister les images locales
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
phobos/buildenv-ffsync v1.0 5335d8ff969e 6 weeks ago 144MB
grafana/grafana 8.1.1 c6e5c4f48cf9 7 weeks ago 213MB
bitnami/dokuwiki latest 1f0bc8445e43 2 months ago 387MB
traefik v2.4.12 8b7f6bb63b8a 2 months ago 92MB
...
busybox latest 22667f53682a 8 months ago 1.23MB
On crée un **Dockerfile** pour notre nouveau conteneur basé sur une image busybox à laquelle on ajoute notre fichier de données de 10 Mo:
FROM busybox:latest
COPY file.dat /opt/file.dat
...
On crée l'image à partir de ce Dockerfile:
docker image build -t phobos/layer-test:v1.0 .
Si on affiche à présent les images disponibles:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
phobos/layer-test v1.0 53be48f115d7 17 seconds ago 11.7MB
...
busybox latest 22667f53682a 8 months ago 1.23MB
Comme on pouvait s'y attendre la taille de notre nouvelle image correspond à l'image initiale busybox à laquelle on ajoute le poids de notre fichier de données.
Le phénomène des calques apparaîtra avec la modification suivante dans le Dockerfile:
FROM busybox:latest
COPY file.dat /opt/file.dat
# Pour nos besoins le fichier doit appartenir à l'utilisateur 1003
RUN chown 1003:1003 /opt/file.dat
# création de l'image
docker image build -t phobos/layer-test:v1.1 .
# lister les images
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
phobos/layer-test v1.1 776d428be3b6 About a minute ago 22.2MB
phobos/layer-test v1.0 53be48f115d7 10 minutes ago 11.7MB
...
busybox latest 22667f53682a 8 months ago 1.23MB
Ici on voit bien qu'entre les versions v1.0 et v1.1, la taille de l'image a doublé alors qu'aucun nouveau fichier n'a été ajouté à l'image. L'opération de changement de droits a conduit à un nouveau calque contenant le même fichier de 10 Mo avec les droits adéquats.
Pour éviter de créer un nouveau calque et doubler l'occupation disque on peut modifier le Dockerfile comme ci-dessous:
FROM busybox:latest
COPY --chown=1003:1003 file.dat /opt/file.dat
On crée une nouvelle version avec le Dockerfile modifié
# création de l'image
docker image build -t phobos/layer-test:v1.2 .
# lister les images
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
phobos/layer-test v1.2 72ca2ea7cac0 5 seconds ago 11.7MB
phobos/layer-test v1.1 776d428be3b6 37 minutes ago 22.2MB
phobos/layer-test v1.0 53be48f115d7 47 minutes ago 11.7MB
...
busybox latest 22667f53682a 8 months ago 1.23MB
La version v1.2 obtenue avec la dernière version du Dockerfile n'a pas produit de nouveau calque, on a bien une taille de 11.7Mo pour notre image.
===== Sécurité =====
Limiter au maximum les processus s’exécutant en tant que root même dans le container. Utiliser exec, su-exec pour remplacer dès que possible les processus root.
===== Utilisation des labels =====
Docker permet d'associer à une image des métadonnées via le mécanisme des labels.
* Un label est une paire clé/valeur stockée dans une chaîne de caractères.
* Les noms de labels commencent et se terminent par des lettres minuscules. Ils ne peuvent contenir que des lettre et les caractères point '.' ou trait d'union '-'.
* La clé du label doit être unique, si elle est définie plusieurs fois, c'est la dernière déclaration qui est conservée. Les labels peuvent ainsi être hérités/redéfinis.
* Pour éviter les conflits ou redéfinitions involontaires des clés, il est recommandé d'utiliser un espace de nommage basé sur son DNS (org.example.my-key=value).
Confère la [[https://docs.docker.com/config/labels-custom-metadata/ | documentation officielle Docker à propos des labels]]
Il existe également un projet **OCI** ((**O**pen **C**ontainer **I**nitiative))
* https://opencontainers.org/
# metadata
LABEL "org.opencontainers.image.authors"="contact@example.org" \
"org.opencontainers.image.url"="https://example.org/docker-images/app" \
"org.opencontainers.image.source"="https://example.org/src/app/master" \
"org.opencontainers.image.vendor"="Phobos, Inc" \
"org.opencontainers.image.description"="My Lovely App 1.5"
===== Références =====
* https://adilsoncarvalho.com/use-labels-on-your-docker-images-3abe4477e9f5
* https://www.fosstechnix.com/dockerfile-instructions/
* https://vsupalov.com/docker-arg-env-variable-guide/