25 KiB
Docker
Images de conteneurs
Maxime Poullain • Christian Tritten
Qu'est-ce qu'une image de conteneur ?
Une image :
-
Est un template de conteneur en lecture seule.
-
Est composée de fichiers et de métadonnées.
-
Peut par exemple contenir un système d'exploitation Debian avec Apache et une application web pré-installés.
-
Est "instanciée" pour créer un ou plusieurs conteneurs.
Images incrémentales et réutilisables
Remarque importante
Si l'image de base est modifiée (par exemple dans le cadre de l'application d'une mise à jour de sécurité), et que l'on souhaite propager cette mise à jour sur les images dérivées, il faudra reconstruire ces dernières.
Système de fichiers multi-couches
-
Les images sont composées d'une ou plusieurs couches superposées.
-
Chaque couche représente un différentiel des changements apportés par rapport à la couche inférieure.
-
Lorsque plusieurs images partagent des couches, cela permet d'optimiser l'espace disque, et les temps de transfert.
-
La principale différence entre un conteneur et une image réside dans la couche en r/w du sommet.
-
Toutes les écritures qui ajoutent ou modifient des données dans un conteneur sont stockées sur cette couche.
-
Quand le conteneur est supprimé la couche en r/w est aussi supprimée, les couches inférieures de l'image de base restent inchangées.
-
On peut sauvegarder les modifications effectuée au sein d'un conteneur en utilisant la commande :
docker commit
-
Ceci va ajouter une nouvelle couche en lecture seule au dessus de la pile composant l'image.
Lorsqu'une image est modifiée, une nouvelle couche est ajoutée au sommet de la pile :
Partage d'image entre plusieurs conteneurs
## Gestion des images
Outils et commandes pour la gestion des images
Images locales
docker image ls
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
composeformation_web latest 0d2d42971538 20 hours ago 237MB
debian-formation latest 257c415ffcc7 20 hours ago 237MB
mariadb 10.1.24 98f78d96be9c 2 weeks ago 395MB
debian jessie 3e83c23dba6a 3 weeks ago 124MB
hello-world latest 48b5124b2768 4 months ago 1.84kB
Le Docker Hub
Le Docker Hub est un entrepôt d'images de conteneurs sur lequel on peut télécharger :
- des images officielles certifiées par Docker
- des images publiques maintenues par la communauté
Attention !
Docker ne garantie pas le bon fonctionnement
ni même l'absence de faille de sécurité
sur les images non officielles.
Chercher une image sur le Docker Hub
docker search IMAGE
$ docker search mariadb
NAME DESCRIPTION STARS OFFICIAL
mariadb MariaDB is a community-developed for... 1354 [OK]
bitnami/mariadb Bitnami MariaDB Docker Image 37
paintedfox/mariadb A docker image for running MariaDB 5... 29
million12/mariadb MariaDB 10 on CentOS-7 with UTF8 default 14
toughiq/mariadb-cluster Dockerized Automated MariaDB Galera ... 11
webhippie/mariadb Docker images for mariadb 9
gists/mariadb MariaDB on Alpine 7
panubo/mariadb-galera MariaDB Galera Cluster 7
kakilangit/mariadb Docker for MariaDB with OQGraph & To... 6
maxexcloo/mariadb Service container with MariaDB insta... 4
tianon/mariadb DEPRECATED; use mariadb:* -- ♪ "I ju... 4
takaomag/mariadb docker image of archlinux (mariadb) 2
drupaldocker/mariadb MariaDB for Drupal 1
...
Récupérer une image sur le Docker Hub
docker pull IMAGE[:TAG]
$ docker pull mariadb:10.7.1
Using default tag: latest
latest: Pulling from library/mariadb
10a267c67f42: Pull complete
c2dcc7bb2a88: Pull complete
17e7a0445698: Pull complete
9a61839a176f: Pull complete
64675690edb1: Pull complete
3de17e251488: Pull complete
f814b22b783e: Pull complete
733ce1f03439: Pull complete
fb7b719835fd: Pull complete
8d3f82357729: Pull complete
a4f4cbdfcf7c: Pull complete
Digest: sha256:4b54358541679032f6c3a9d9fc944ad96d77ae72fecd6cb44bf18cf97743da24
Status: Downloaded newer image for mariadb:10.7.1
Visualiser l'historique d'une image
docker history IMAGE
$ docker history mariadb:10.7.1
IMAGE CREATED CREATED BY SIZE
98f78d96be9c 2 weeks ago /bin/sh -c #(nop) CMD ["mysqld"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) EXPOSE 3306/tcp 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-ent... 0B
<missing> 2 weeks ago /bin/sh -c ln -s usr/local/bin/docker-entr... 34B
<missing> 2 weeks ago /bin/sh -c #(nop) COPY file:d559178e6a2929... 5.6kB
<missing> 2 weeks ago /bin/sh -c #(nop) VOLUME [/var/lib/mysql] 0B
<missing> 2 weeks ago /bin/sh -c sed -Ei 's/^(bind-address|log)/... 5.27kB
<missing> 2 weeks ago /bin/sh -c { echo mariadb-server-$MARIAD... 252MB
<missing> 2 weeks ago /bin/sh -c echo "deb https://repo.percona.... 114B
<missing> 2 weeks ago /bin/sh -c set -ex; export GNUPGHOME="$(m... 21.1kB
<missing> 2 weeks ago /bin/sh -c apt-get update && apt-get insta... 14.3MB
<missing> 2 weeks ago /bin/sh -c set -x && apt-get update && ap... 4.58MB
<missing> 2 weeks ago /bin/sh -c groupadd -r mysql && useradd -r... 330kB
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:f4e6551ac34ab44... 124MB
"Inspecter" une image
docker inspect IMAGE
$ docker inspect mariadb
[
{
"Id": "sha256:98f78d96be9c7f513f21de040d083ee7ba23d74c8f3bc499373e56e93c...",
"RepoTags": [
"mariadb:10.7.1"
],
"RepoDigests": [
"mariadb@sha256:4b54358541679032f6c3a9d9fc944ad96d77ae72fecd6cb44bf1..."
],
"Parent": "",
"Comment": "",
"Created": "2017-05-09T17:28:06.071608373Z",
"Container": "83ce76bba170200d3783bde70b7c1d06a61ed2b91bec7351a5c5a664f5...",
"ContainerConfig": {
"Hostname": "200591939db7",
"Domainname": "",
"User": "",
"ExposedPorts": {
"3306/tcp": {}
Supprimer une image
docker image rm IMAGE [IMAGE...]
$ docker image rm mariadb:10.7.1
Untagged: mariadb:10.7.1
Untagged: mariadb@sha256:4b54358541679032f6c3a9d9fc944ad96d77ae72fecd6cb44bf18cf...
Deleted: sha256:98f78d96be9c7f513f21de040d083ee7ba23d74c8f3bc499373e56e93c8e9ec9
Deleted: sha256:bea03b338eb87d64861847305aa63f6104212c60719168f25b54ca713db4b870
Deleted: sha256:519db73d66bef13a78573160ddf2059f9dc382e03fd2e85f354c3172ded67b90
Deleted: sha256:7f728a3fd818a51a5425306ef40f398c7698f4252ade70cb83b3d26b825bb613
Deleted: sha256:48159803f1446a31e60af329025fc5c3ae8ef07f950d8750a7e14d46d1d1191c
Deleted: sha256:ff5b1cc6d50c6f7ab9e6ee77ff89b0b037a6840f7b1f44cbe234499362221c15
Deleted: sha256:d147674f5cce42f85942e815f154c6a7ecb86359689d823a9840b28126a12f4e
Deleted: sha256:571be45150dde0fb8f6c3862abbcfa06fbad0e6a128d459a6b8ad0660f9f0660
Deleted: sha256:458271f19a2c854fc6fd3338f9151662173b96a14cfe1a2e46eca95d27e4102c
Deleted: sha256:ba779192baede4aadd009c269406b5e8fd885c653ce19719316bf40cc66a6cf3
Deleted: sha256:2302bd8bbdd530199aa432c357a4da9eab2621c3ba4c4dacb4ea0f4afecbcae7
Deleted: sha256:00771f8e1e12bdfc9d47bc52a78e3f5ce5306a1caa5dd6237731cff9ca106040
Deleted: sha256:8d4d1ab5ff74fc361fb74212fff3b6dc1e6c16d1e1f0e8b44f9a9112b00b564f
Importer / Exporter une image
-
Export
$ docker save -o mon-image.tar IMAGE
-
Import
$ docker load -i mon-image.tar
Travaux pratiques
Dockerfile
Automatiser la construction d'une image
-
Il est possible de construire une image à la main puis de la sauvegarder avec un
docker commit
. -
Toutefois ceci est fastidieux, non parfaitement reproductible et donc potentiellement source d'erreur.
-
D'autre part, comment gérer les mises à jours d'une telle image ?
-
Docker est capable de construire des images automatiquement à partir des instructions d'un Dockerfile.
-
Le Dockerfile est un fichier texte qui contient toutes les instructions permettant de construire une image Docker pour une application donnée.
https://docs.docker.com/engine/reference/builder/
-
Le Dockerfile est versionnable et permet de produire une image à tout moment.
-
Ceci s'inscrit dans la philosophie Infrastructure as Code qui prône la définition d'une architecture dans des fichiers textes déclaratifs.
-
Grâce au Dockerfile on peut reconstruire périodiquement une image afin quelle intègre les dernières mises à jour applicatives et les derniers patches de sécurité.
Exemple de Dockerfile
FROM debian:bullseye
LABEL maintainer "robert@produpot.com"
# On installe Apache httpd
ENV DEBIAN_FRONTEND noninteractive
RUN apt update \
&& apt install -y apache2 \
&& rm -rf /var/lib/apt/lists/*
# Ajout d'un script d'init
ADD run.sh /run.sh
RUN chmod 755 /run.sh
# Importe l'application
RUN mkdir -p /app && rm -fr /var/www/html && ln -s /app /var/www/html
ADD homepage/ /app
# On expose le port 80
EXPOSE 80
# On indique le script qui doit être lancé au démarrage du conteneur
ENTRYPOINT ["/run.sh"]
Principales directives
Directive | Description
-
| -
FROM
| Spécifie l'image de départ pour la construction de la nouvelle image.
LABEL
| Ajoute des métadonnées à la nouvelle image.
ENV
/ ARG
| Ajoute des variables d'environnement.
ADD
/ COPY
| Copie des fichiers ou des dossiers sur le système de fichiers de l'image.
VOLUME
| Créé un point de montage à l'instanciation du conteneur.
Principales directives (suite)
Directive | Description
-
| -
RUN
| Exécute une commande et "commite" le résultat dans une nouvelle couche de l'image.
USER
| Spécifie l'utilisateur à utiliser pour jouer les instructions RUN
et ENTRYPOINT
.
HEALTHCHECK
| Indique une commande qui sera lancée à l'intérieur du conteneur pour vérifier que celui-ci tourne correctement.
ENTRYPOINT
| Définie une commande de base à exécuter dans le conteneur.
CMD
| Définie les paramètres par défaut de la commande de base.
LABEL
LABEL maintainer="user@example.com" \
vendor=ACME\ Incorporated \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"
Choisir entre ARG
et ENV
-
ARG
etENV
permettent de déclarer des variables qui sont utilisables à partir du moment où elles sont déclarées dans le Dockerfile. -
Les
ARG
peuvent être surchargées au moment du build via l'option--build-arg
. -
Les
ENV
peuvent être surchargées à l'exécution via l'option-e
. -
Si une variable
ARG
est utilisée sans valeur par défaut et qu'aucune valeur n'est fournie via--build-arg
, cela déclenche une erreur lors du build.
https://vsupalov.com/docker-arg-env-variable-guide/
Dockerfile
ARG MY_VAR_1 <---- expect a build-time variable
ARG MY_VAR_2=pouet <---- set a build-time variable
RUN touch "$MY_VAR_2"
ARG A_VARIABLE <---- expect a build-time variable
ENV another_var=$A_VARIABLE <---- use the value to set the ENV var default
if not overridden, that value of another_var
will be available to your containers!
Choisir entre ADD
et COPY
Les deux directives ont la même syntaxe :
COPY <src>... <dest>
ADD <src>... <dest>
Selon le guide des bonnes pratiques Docker :
-
Utilisez
COPY
dans tous les cas, sauf si vous avez besoin d'extraire automatiquement le contenu d'une archive, dans ce cas précis utilisezADD
. -
Pour récupérer des fichiers distants, préférez plutôt l'instruction
RUN wget ...
.
CMD et ENTRYPOINT
Les commandes CMD
et ENTRYPOINT
permettent
de définir la commande par défaut à exécuter
à l'intérieur du conteneur.
-
ENTRYPOINT
définie la commande de base pour le conteneur, -
CMD
définie les paramètres par défaut pour cette commande.
FROM debian:bullseye
RUN apt update && apt install -y cowsay
ENTRYPOINT ["/usr/games/cowsay"]
CMD ["hello"]
$ docker build -t cowsay .
...
Successfully built a27691083512
Successfully tagged cowsay:latest
$ docker run cowsay
-------
< hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ docker run cowsay 2,21 Gigowatts ?!
-------------------
< 2,21 Gigowatts ?! >
-------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
HEALTHCHECK
L'exemple suivant teste toutes les 5 minutes que le conteneur est capable de servir une ressource HTTP en moins de 3 secondes :
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
Le test de santé étant lancé depuis l'intérieur du conteneur, la commande curl
utilisée dans l'exemple ci-dessus doit être présente dans le conteneur.
Le status healthy / unhealthy est consultable
via docker ps
ou docker inspect
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
9e2ea5f59f8b xian/web "/start.sh" 28 minutes ago Up 28 minutes (healthy)
20851619e1af xian/web "/start.sh" 23 minutes ago Up 23 minutes (unhealthy)
$ docker inspect -f '{{json .State.Health.Status}}' conteneur
"healthy"
Il s'agit d'un simple message informatif, en effet Docker ne relance pas de lui-même un conteneur détecté comme unhealthy.
-
Toutefois, Le changement de santé d’un conteneur génère un évenement Docker que les outils de monitoring et d’orchestration peuvent intercepter.
-
Par exemple l'orchestrateur Swarm utilise cette information pour remplacer automatiquement le conteneur défectueux par une nouvelle instance.
Construire l'image à partir du Dockerfile
docker build -t IMAGE[:TAG] .
Les tags permettent de proposer
plusieurs versions d'une image.
docker build -t mon-image:v1 .
Une même image peut
être tagguée plusieurs fois :
ex : debian:10
et debian:buster
Derrière un proxy
--build-arg http_proxy=PROXY
$ docker build --build-arg http_proxy=http://my.proxy.url:3128 \
--tag debian-formation .
Travaux pratiques
Rendre une image publique
-
On peut rendre une image publique en la poussant sur le Docker Hub.
-
La création d'un Docker ID est nécessaire.
-
Le Docker ID sera le nom d'utilisateur pour le Docker Hub.
-
La commande
docker login
permet de se connecter au Hub.
Pour pouvoir pousser une image sur le Hub, il faut la nommer avec un nom de la forme docker-id/image:tag
Ceci se fait avec la commande docker tag
:
$ docker tag mon-image mon-docker-id/mon-image:1.0.0
Il est ensuite possible de pousser l'image avec la commande docker push
:
$ docker push mon-docker-id/mon-image:1.0.0
A partir de là, l'image devient accessible publiquement par n'importe qui.
Dockerfile
les bonnes pratiques
Réutiliser au maximum la même image de base afin de mutualiser les couches entre vos différentes images applicatives.
Eviter d'installer tout ce qui n'est pas strictement nécessaire.
Paquets de la distribution, paquets applicatifs, ...
Alléger les images en supprimant les données inutiles, mais pas n'importe comment !
RUN apt-get update && apt-get install -y package
RUN rm -rf /var/lib/apt/lists/*
Chaque couche est commitée en readonly avant de passer à l'instruction suivante !
Il faut nettoyer dans la même couche !
RUN apt update \
&& apt install -y package \
&& rm -rf /var/lib/apt/lists/*
RUN wget archive.tar.gz \
&& tar xzvf archive.tar.gz \
&& rm archive.tar.gz
(Combiner plusieurs instructions RUN
permet également de diminuer le nombre de couches constituant l'image.)
Ne pas utiliser apt-get upgrade
ou apt-get dist-upgrade
dans vos images spécialisées.
Ceci doit être fait dans l'image OS de base.
Chaque conteneur devrait se focaliser sur une seule tâche.
Utiliser une image de base légère.
Par exemple, debian:bullseye-slim pèse ~30MB tout en restant une distribution complète.
Proscrire l'utilisation du tag latest
en production.
Attention !
docker pull myimage
== docker pull myimage:latest
Le tag latest
est utilisé par défaut si vous n'en fournissez pas un dans vos commandes Docker !
3 bonnes raisons de ne pas utiliser le tag latest
-
Votre outil de déploiement ne déploiera pas une nouvelle version de votre application taggué latest, tout simplement parce qu'il ne détectera pas la différence avec l'ancienne version elle aussi tagguée avec latest.
-
Tagguer une image avec un numéro de version unique permet d'effectuer de la tracabilité. On sait exactement quelle version est déployée.
-
En réutilisant systématiquement le tag latest, tout retour arrière à une ancienne version de l'image est impossible, car vous écrasez systématiquement la précédente version de l'image docker.
Déterminer une stratégie pour les tags
-
Lorsque on crée une image, il nous appartient de lui ajouter des tags appropriés.
-
Utiliser une stratégie pour les tags qui soit cohérente et consistante sur toutes les images produites.
-
La stratégie doit être facilement compréhensible par les utilisateurs de ces images.
L'exécution de processus avec l'utilisateur root
à l'intérieur d'un conteneur est un préliminaire à de nombreux types d'attaques.
Déclarer un utilisateur dédié permet d'éviter un grand nombre d'attaques :
RUN useradd -d /home/my-app-user -m -s /bin/bash my-app-user
USER my-app-user
On peut forcer l'utilisateur à l'exécution :
$ docker run --user my-app-user -d -t my-application
L'utilisateur doit exister dans /etc/passwd
à l'intérieur du conteneur.
Ne pas écrire de secrets dans le Dockerfile.
(secret == mot de passe, clé de chiffrement, clé d'API)
Injecter la configuration à l'exécution du conteneur.
Utiliser la fonctionnalité de multi-stage build (Docker > v17.05) pour produire des images plus petites.
Un cas d'usage est celui où l'on doit compiler une binaire afin de produire l'image applicative finale.
En découpant le processus de build, on va tout d'abord construire une image dédié à la compilation du binaire et injecter le résultat de cette compilation dans l'image finale qui sera ainsi d'une beaucoup plus réduite.
Dockerfile
FROM golang:1.10 as builder
WORKDIR /tmp/go
COPY hello.go ./
RUN CGO_ENABLED=0 go build -a -ldflags '-s' -o hello
FROM scratch
CMD [ "/hello" ]
COPY --from=builder /tmp/go/hello /hello
$ docker build -t hello:1 .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 212f44bc4048 4 seconds ago 3.2MB
<none> <none> 08370cf772b1 5 seconds ago 693MB
Le résultat final est une image d'environ 3Mb au lieu de 700Mb sans le multi-stage build.
Pour le reste des bonnes pratiques :
- https://sysdig.com/blog/dockerfile-best-practices/
- https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
- https://cloud.google.com/solutions/best-practices-for-building-containers
Haskell Dockerfile Linter
Un outil pour vérifier les bonnes pratiques
sur les fichiers Dockerfile
$ docker run --rm -i hadolint/hadolint < Dockerfile
/dev/stdin:2 DL3020 Use COPY instead of ADD for files and folders
/dev/stdin:4 DL3025 Use arguments JSON notation for CMD and ENTRYPOINT arguments
https://github.com/hadolint/hadolint
Nettoyer les images
Les images ont tendance à s'accumuler et à remplir progressivement l'espace de stockage sur les hôtes.
-
$ docker image prune
Supprime toutes les images qui ne sont ni taguées ni référencées par au moins un conteneur. -
$ docker image prune -a
Supprime toutes les images qui ne sont pas référencées par au moins un conteneur.
Registry
-
Le composant Docker Registry permet le stockage et la distribution d'images Docker.
-
Docker Inc. fourni une image officielle prête à l'emploi pour déployer facilement un Registry.
Fonctionnalités
-
Authentification des utilisateurs
-
Push/Pull d'images
-
Stockage des images sur une grande variété de backends (S3, Posix Filesystems, Ceph, Swift...)
### Utilisation du Docker Registry
$ docker pull hello-world
$ docker tag hello-world mon_nom_de_domaine:5000/hello-world
$ docker push mon_nom_de_domaine:5000/hello-world
...
$ docker pull mon_nom_de_domaine:5000/hello-world
Sécurité - rapports
-
NCC Group
Understanding and Hardening Linux Containers
Juin 2016
www.nccgroup.trust/ -
NIST
Application Container Security Guide
Sept. 2017
http://nvlpubs.nist.gov/
Sécurité - outils
-
Docker Bench for Security
github.com/docker/docker-bench-security -
Trivy - Analyse de vulnérabilités sur les images https://aquasecurity.github.io/trivy/
Outils alternatifs pour construire des Images compatibles OCI
-
Pas besoin d'élévation de privilèges (Docker build nécessite d'être root).
-
Utilisation d'un cache distribué pour optimiser les performances sur un cluster de build.
-
Permet de contrôler les couches générées dans l'image.