Update tuto2

This commit is contained in:
nemunaire 2021-09-23 02:55:18 +02:00
parent 5f097b4221
commit 2c5317f4f9
35 changed files with 3588 additions and 472 deletions

View File

@ -12,7 +12,7 @@ abstract: |
Le TP se termine par un petit projet à rendre à <virli@nemunai.re> au
plus tard le **mercredi 22 septembre 2021 à 23 h 42**. Consultez la
dernière section de chaque partie pour plus d'information sur les
dernière section de chaque partie pour plus d'informations sur les
éléments à rendre. Et n'oubliez pas de répondre aux [questions de
cours](https://virli.nemunai.re/quiz/11).

View File

@ -1,15 +1,21 @@
include ../pandoc-opts.mk
SOURCES = tutorial.md \
../dockerfiles/interactive.md ../dockerfiles/dockerfile.md ../dockerfiles/goodpractices.md ../dockerfiles/entrypoint.md \
../docker-internals/clair.md \
../dockerfiles/what.md ../dockerfiles/interactive.md ../dockerfiles/dockerfile.md ../dockerfiles/goodpractices.md ../dockerfiles/others.md ../dockerfiles/entrypoint.md \
../docker-internals/vulnerability-scan.md ../docker-internals/clair-tiny.md ../docker-internals/vulnerability-scan-ex.md \
rendu.md
SOURCES_CLAIR = tutorial-clair.md \
../docker-internals/clair.md
all: tutorial.pdf
tutorial.pdf: ${SOURCES}
all: tutorial-2.pdf tutorial-2-clair.pdf
tutorial-2.pdf: ${SOURCES}
pandoc ${PANDOCOPTS} -o $@ $+
tutorial-2-clair.pdf: ${SOURCES_CLAIR}
pandoc ${PANDOCOPTS} -o $@ $+
clean::
rm tutorial.pdf
rm tutorial-2.pdf

2621
tutorial/2/mysql-vulns.html Normal file

File diff suppressed because it is too large Load Diff

1
tutorial/2/quay-vulns.png Symbolic link
View File

@ -0,0 +1 @@
../docker-internals/quay-vulns.png

View File

@ -39,8 +39,10 @@ login_x-TP2/youp0m/Dockerfile
login_x-TP2/youp0m/entrypoint.sh
login_x-TP2/youp0m/.dockerignore
login_x-TP2/youp0m/...
login_x-TP2/clair/docker-compose.yml
login_x-TP2/clair/clair_config/config.yaml
login_x-TP2/nginx:mainline.html # rapport d'analyse PAclair
login_x-TP2/mysql:latest.html # rapport d'analyse PAclair, Trivy, ...
login_x-TP2/....html
```
</div>
Inutile d'inclure le contenu du dépôt `youp0m` dans votre tarball. Placez-y
uniquement les fichiers que vous avez ajouté.

View File

@ -0,0 +1,17 @@
---
title: Virtualisation légère -- TP n^o^ 2
subtitle: Construire des images Docker et leur sécurité -- Annexe
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
institute: EPITA
date: Jeudi 23 septembre 2021
abstract: |
Clair est un outil très intéressant à déployer dans un contexte d'analyse
continue, au sein d'une plateforme de déploiement continue par exemple. Vous
retrouverez dans cette annexe un guide pour le mettre en place et faire vos
premiers pas avec.
...
![Rapport d'analyse statique des vulnérabilités d'un conteneur](paclair.png)
Une vision plus Clair de la sécurité des images
-----------------------------------------------

View File

@ -6,16 +6,16 @@ institute: EPITA
date: Jeudi 23 septembre 2021
abstract: |
Durant ce deuxième TP, nous allons voir comment créer nos propres
images, comment s'assurer qu'elles n'ont pas de vulnérabilités
images, et comment s'assurer qu'elles n'ont pas de vulnérabilités
connues !
\vspace{1em}
Tous les éléments de ce TP (exercices et projet) sont à rendre à
<virli@nemunai.re> au plus tard le **jeudi 22 octobre 2020 à 12 h
42**. Consultez la dernière section de chaque partie pour plus d'information
<virli@nemunai.re> au plus tard le **mercredi 29 septembre 2021 à 23 h
42**. Consultez la dernière section de chaque partie pour plus d'informations
sur les éléments à rendre. Et n'oubliez pas de répondre aux [questions de
cours](https://virli.nemunai.re/quiz/4).
cours](https://virli.nemunai.re/quiz/12).
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à

View File

@ -13,7 +13,7 @@ abstract: |
Certains éléments de ce TP sont à rendre à <virli@nemunai.re> au
plus tard le jeudi 12 novembre 2020 à 12 h 42. Consultez la
dernière section de chaque partie pour plus d'information sur les
dernière section de chaque partie pour plus d'informations sur les
éléments à rendre.
En tant que personnes sensibilisées à la sécurité des échanges électroniques,

View File

@ -12,7 +12,7 @@ abstract: |
Tous les éléments de ce TP (exercices et projet) sont à rendre à
<virli@nemunai.re> au plus tard le **mercredi 4 novembre 2020 à 12 h
42**. Consultez la dernière section de chaque partie pour plus d'information
42**. Consultez la dernière section de chaque partie pour plus d'informations
sur les éléments à rendre. Et n'oubliez pas de répondre aux [questions de
cours](https://virli.nemunai.re/quiz/5).

View File

@ -0,0 +1 @@
\newpage

View File

@ -0,0 +1,4 @@
\newpage
Découvrons Docker
=================

View File

@ -50,10 +50,10 @@ proposait pas d'image et que quelqu'un s'est dévoué pour la faire, soit parce
qu'il avait des besoins plus spécifiques qui n'étaient pas couvert par l'image
originale. Il est important de garder en tête que vous téléchargez des
exécutables, et bien qu'ils s'exécutent dans un environnement isolé, ils
peuvent contenir du code malveillant. De la même manière que vous devez être
attentif aux binaires que vous exécutez sur votre machine et au contexte de son
téléchargement, ici assurez-vous d'avoir confiance dans la personne affiliée à
l'image.
peuvent contenir du code malveillant. **De la même manière que vous devez être
attentif aux binaires que vous exécutez sur votre machine et au contexte de
leurs téléchargements, ici assurez-vous d'avoir confiance dans la personne
affiliée à l'image.**
### Arguments de la ligne de commande
@ -167,8 +167,6 @@ que lorsque nous exécutons une commande modifiant les fichiers d'un conteneur,
cela ne modifie pas l'image de base. La modification reste contenue dans la
couche propre au conteneur dans l'UnionFS.
![Images vs. conteneurs](img-vs-cntr.png "Images vs. conteneurs"){ width=85% }
Dans le schéma ci-après, on considère les images comme étant la partie figée de
Docker à partir desquelles on peut créer des conteneurs.
@ -176,6 +174,8 @@ Si l'on souhaite qu'une modification faite dans un conteneur (par exemple
l'installation d'un paquet) s'applique à d'autres conteneurs, il va falloir
créer une nouvelle image à partir de ce conteneur.
![Images vs. conteneurs](img-vs-cntr.png "Images vs. conteneurs"){ width=85% }
### Programme par défaut

View File

@ -1,6 +1,6 @@
include ../pandoc-opts.mk
SOURCES = tutorial.md oci.md runc.md linuxkit.md rendu.md
SOURCES = tutorial.md oci.md runc.md linuxkit.md linuxkit-admin.md linuxkit-content.md rendu.md
all: tutorial.pdf

View File

@ -0,0 +1,20 @@
[`Clair`](https://github.com/coreos/clair/) est un projet de CoreOS, similaire
à Trivy : il va rechercher parmi sa base de vulnérabilités lesquels concernent
les images qu'on lui donne.
Contrairement à Trivy, il ne va pas tenter d'analyser les dépendances
supplémentaires des applications métiers (Trivy est capable d'analyser les
dépendances PHP, Python, Ruby, Go, ...).
La mise en œuvre de Clair est un peu plus complexe car il s'agit d'un *daemon*
qui s'exécute en permanence, et auquel on peut transmettre via une API, nos
images. Dans un contexte d'intégration continue, ou d'un registre d'images qui
teste régulièrement ses images hébergées, c'est un outil idéal. Néanmoins il
faut le configurer un minimum pour qu'il soit opérationnel. Consultez [l'annexe
dédiée](https://virli.nemunai.re/tutorial-2-clair.pdf) si vous souhaitez opter
pour cette solution.
On notera tout de même que les outils sont donnés pour générer des rapports
HTML avec des graphiques explicites :
![Rapport d'analyse statique des vulnérabilités par Clair](paclair.png)

View File

@ -1,53 +1,20 @@
\newpage
### Clair
Une vision plus Clair de la sécurité
====================================
Nous avons vu, au travers de nos TPs jusqu'à présent, que Docker nous apportait
un certain degré de sécurité d'emblée au lancement du conteneur. Cela peut sans
doute paraître quelque peu rassurant pour la personne chargée d'administrer la
machine hébergeant les conteneurs, car cela lui apporte des garanties quant à
l'effort de cloisonnement mis en place.
Mais doit-on pour autant s'arrêter là et considérer que nous avons réglé
l'ensemble des problématiques de sécurité liées aux conteneurs ?
Évidemment, non : une fois nos services lancés dans des conteneurs, il ne sont
pas moins exposés aux bugs et autres failles applicatives ; qu'elles soient
dans notre code ou celui d'une bibliothèque, accessible par rebond, ...
Il est donc primordial de ne pas laisser ses conteneurs à l'abandon une fois
leur image créée et envoyée en production. Nos conteneurs doivent être
regénérés sitôt que leur image de base est mise à jour (une mise à jour d'une
image telle que Debian, Ubuntu ou Redhat n'apparaît que pour cela) ou bien
lorsqu'un des programmes ou bibliothèques que l'on a installé ensuite.
Convaincu ? Cela sonne encore comme des bonnes pratiques difficile à mettre en
œuvre, pouvant mettre en péril tout un système d'information. Pour s'en
protéger, nous allons avoir besoin de réaliser à intervalles réguliers une
analyse statique de nos conteneurs.
![Rapport d'analyse statique des vulnérabilités d'un conteneur](paclair.png)
## Clair
Le principal outil pour indexer et chercher des vulnérabilités est
Un outil complet indexer et chercher des vulnérabilités est
[`Clair`](https://github.com/coreos/clair/), du projet CoreOS. À partir des
informations mises à disposition par les équipes de sécurités des principales
distributions, cela alimente en continu une base de données qui sera accéder au
moment de l'analyse.
L'outil se présente sous la forme d'un serveur autonome dans la récupération
de ses données sources, auquel nous pourrons interagir au moyen d'une API :
de ses données sources, auquel nous pourrons interagir au moyen d'une API :
pour lui envoyer des images et lui demander une analyse. Les clients de cette
API seront soit les registres directement, soit un programme dédié.
![Scan de vulnérabilités sur le Docker Hub](hubvuln.png)
---
Commençons par lancer notre propre instance de `Clair`, à l'aide d'un
`docker-compose.yml` :
`docker-compose.yml` :
<div lang="en-US">
```yml
@ -77,10 +44,10 @@ services:
```
</div>
Prenez quelques minutes pour comprendre ce `docker-compose.yml` : notez la
présence de la variable d'environnement `POSTGRES_PASSWORD`, non définie : ce
Prenez quelques minutes pour comprendre ce `docker-compose.yml` : notez la
présence de la variable d'environnement `POSTGRES_PASSWORD`, non définie : ce
sera la variable présente dans votre environnement, au moment du
`docker-compose up` qui sera utilisée. N'oubliez pas de la définir :
`docker-compose up` qui sera utilisée. N'oubliez pas de la définir :
<div lang="en-US">
```bash
@ -99,7 +66,7 @@ pas de changer le nom d'hôte et le mot de passe pour se connecter au conteneur
de base de données.**
Une fois lancé, la base nécessite d'être initialisée. L'opération peut prendre
plusieurs minutes. Vous pouvez suivre l'avancement de l'ajout via :
plusieurs minutes. Vous pouvez suivre l'avancement de l'ajout via :
<div lang="en-US">
```bash
@ -109,11 +76,11 @@ curl http://localhost:6060/v1/namespaces/debian:9/vulnerabilities?limit=10
</div>
## PAClair
### PAClair
Afin de pouvoir réaliser à la demande et sans registre privé, l'analyse de
conteneur, nous allons utiliser le programme
[`paclair`](https://github.com/yebinama/paclair) :
[`paclair`](https://github.com/yebinama/paclair) :
<div lang="en-US">
```bash
@ -121,7 +88,7 @@ pip3 install paclair
```
</div>
Il nécessite un fichier de configuration pour être utilisé, essayez :
Il nécessite un fichier de configuration pour être utilisé, essayez :
<div lang="en-US">
```yml
@ -134,7 +101,7 @@ Plugins:
</div>
Pour obtenir un rapport d'analyse, on commence par envoyer les couches de
l'image à `Clair` :
l'image à `Clair` :
<div lang="en-US">
```bash
@ -142,7 +109,7 @@ paclair --conf conf.yml Docker nemunaire/fic-admin push
```
</div>
Puis on lui demande la génération d'un rapport `html` :
Puis on lui demande la génération d'un rapport `html` :
<div lang="en-US">
```bash
@ -150,7 +117,7 @@ paclair --conf conf.yml Docker nemunaire/fic-admin analyse --output-format html
```
</div>
Si l'on souhaite uniquement avoir des statistiques dans la console :
Si l'on souhaite uniquement avoir des statistiques dans la console :
<div lang="en-US">
```bash
@ -161,10 +128,3 @@ Medium: 5
High: 4
```
</div>
## Exercice {-}
Déterminez le nombre de vulnérabilités dans les principales images officielles
du [Docker Hub](https://hub.docker.com/explore), notamment `nginx`, `golang`,
`redis`, ...

View File

@ -0,0 +1,11 @@
Si vous vous rappelez du cours d'AdLin[^adlin] en début d'années, toutes les
VMs que vous avez utilisées reposaient entièrement sur `linuxkit`. En fait,
chaque conteneur représentait alors une machine différente : un conteneur
postgres d'un côté, un conteneur `miniflux` directement issu du DockerHub, un
conteneur `unbound` pour faire office de serveur DNS, et les machines clientes
étaient de simples conteneurs exécutant un client DHCP.
[^adlin]: toutes les sources des machines sont dans ce dépôt :
<https://git.nemunai.re/srs/adlin>. Les fichiers de construction
LinuxKit sont `challenge.yml`, `server.yml`, `tuto2.yml`,
`tuto3.yml`

View File

@ -0,0 +1,243 @@
## Prérequis
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :\
<https://github.com/linuxkit/linuxkit/releases/latest>.
Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire
en plus du binaire[^lollibc] ;-)
[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.
Vous aurez également besoin de QEMU pour tester vos créations.
## Structure d'un fichier `linuxkit.yml`
Le fichier utilisé pour construire notre image se décompose en plusieurs
parties :
- `kernel` : il est attendu ici une image OCI contenant le nécessaire pour
pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
- `init` : l'ensemble des images OCI de cette liste seront fusionnés pour
donner naissance au *rootfs* de la machine. On n'y place normalement qu'un
gestionnaire de conteneur, qui sera chargé de lancer chaque conteneur au bon
moment ;
- `onboot`, `onshutdown` et `services` : il s'agit de conteneurs qui seront
lancés par le système disponible dans l'`init`, au bon moment. Les conteneurs
indiqués dans `onboot` seront lancés **séquentiellement** au démarrage de la
machine, ceux dans `onshutdown` seront lancés lors de l'arrêt de la
machine. Les conteneurs dans `services` seront lancés simultanément une fois
que le dernier conteneur de `onboot` aura rendu la main ;
- `files` : des fichiers supplémentaires à placer dans le rootfs.
Le format est documenté
[ici](https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md).
## Hello?
L'image la plus simple que l'on puisse réaliser pourrait être :
<div lang="en-US">
```yaml
kernel:
image: linuxkit/kernel:4.14.80
cmdline: "console=tty0 console=ttyS0"
init:
- linuxkit/init:c563953a2277eb73a89d89f70e4b6dcdcfebc2d1
- linuxkit/runc:83d0edb4552b1a5df1f0976f05f442829eac38fe
- linuxkit/containerd:326b096cd5fbab0f864e52721d036cade67599d6
onboot:
- name: dhcpcd
image: linuxkit/dhcpcd:v0.6
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
services:
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
env:
- INSECURE=true
trust:
org:
- linuxkit
```
</div>
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un
shell sur la machine !
On notera cependant que, positionné dans `services`, le shell que nous
obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un
accès entièrement privilégier. Pour déboguer, il faut placer cette image dans
la partie `init`, elle sera alors lancé comme un équivalent de
`init=/bin/sh`.[^infogetty]
[^infogetty]: Plus d'infos à
<https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>
## `namespaces`
Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourra
pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une
faille exploitable.
Néanmoins, contrairement à Docker qui va de base nous dissocier du maximum de
*namespaces*, `linuxkit` ne le fait pas pour les *namespaces* `net`, `ipc` et
`uts`. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les
conteneurs, tout comme les IPC et le nom de la machine.
Il reste possible de se dissocier également de ces namespaces, en précisant :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
net: new
```
</div>
Ou inversement, pour persister dans le namespace initial :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
pid: host
```
</div>
### Partage de `namespace`
Dans le cas où l'on souhaite que deux conteneurs partagent le même *namespace*,
il faut passer le chemin vers la structure du noyau correspondante.
On commence donc d'abord par créer le nouveau *namespace*, en prenant soin de
*bind mount* la structure du noyau à un emplacement connu :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
net: new
runtime:
bindNS: /run/netns/mynewnetns
```
</div>
À la création du *namespace* `net`, le lien vers la structure du noyau
correspondante sera *bind mount* sur `/run/netns/synchro`. On pourra alors
réutiliser plus tard ce chemin, en remplacement du mot clef `new` :
<div lang="en-US">
```yaml
- name: xxxx
image: linuxkit/xxxx:v0.6
net: /run/netns/mynewnetns
```
</div>
## Construction et démarrage
Toute la puissance de `linuxkit` repose dans son système de construction et
surtout de lancement. En effet, il peut construire des images pour un grand
nombre de plate-forme, mais il est également possible d'utiliser les API de ces
plates-formes pour aller y lancer des instances de cette image !
Pour construire l'image faite précédemment :
<div lang="en-US">
```bash
linuxkit build hello.yml
```
</div>
Cela va générer plusieurs fichiers dont un noyau (extrait de l'image de la
partie `kernel`) ainsi qu'une image. Exactement ce qu'attend QEMU ! Pour
tester, n'attendons pas davantage pour lancer :
<div lang="en-US">
```bash
linuxkit run qemu -gui hello
```
</div>
## Ajouter un service
Maintenant que notre machine fonctionne, que nous pouvons interagir avec elle,
tentons de se passer de l'interface de QEMU (option `-gui`) en ajoutant un
serveur SSH aux `services` :
<div lang="en-US">
```yaml
- name: sshd
image: linuxkit/sshd:c4bc89cf0d66733c923ab9cb46198b599eb99320
```
</div>
Comme nous n'avons définis aucun mot de passe, il va falloir utiliser une clef
SSH pour se connecter. Voilà un bon début d'utilisation de la section `files` :
<div lang="en-US">
```yaml
- path: root/.ssh/authorized_keys
source: ~/.ssh/id_rsa.pub
mode: "0600"
```
</div>
Ceci va aller chercher votre clef RSA sur votre machine, pour la placer dans
Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi
recopié, car le *package* `linuxkit/sshd` défini déjà cela :
[pkg/sshd/build.yml#L5](https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5).
## Interface réseau virtuelle
Lorsque l'on souhaite se dissocier d'un *namespace* `net` afin de s'isoler,
mais que l'on veut tout de même pouvoir communiquer, il est nécessaire de créer
une interface `virtual ethernet` :
<div lang="en-US">
```yaml
- name: db
image: mariadb:latest
net: new
runtime:
bindNS:
net: /run/netns/db
interfaces:
- name: vethin-db
add: veth
peer: veth-db
```
</div>
## Exercice {-}
Réalisez une recette `vault.yml` démarrant une instance du gestionnaire de
secrets [Hashicorp Vault](https://www.vaultproject.io/), utilisant une [base de
données au
choix](https://www.vaultproject.io/docs/configuration/storage/index.html)
(Consul, Etcd, MySQL, Cassandra, ...).
Au démarrage, Vault devra déjà être configuré pour parler à sa base de données,
qui devra se trouver dans un conteneur isolé et non accessible d'internet. Il
faudra donc établir un lien `virtual ethernet` entre les deux conteneurs ; et
ne pas oublier de le configurer (automatiquement au *runtime*, grâce à un
[`poststart`
*hook*](https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks)
ou bien à un conteneur issu du *package*
[`ip`](https://github.com/linuxkit/linuxkit/tree/master/pkg/ip)).
Les permissions étant généralement très strictes, vous aurez sans doute besoin
de les assouplir un peu en ajoutant des *capabilities* autorisées à vos
conteneurs, sans quoi vos conteneurs risquent d'être tués prématurément.
En bonus, vous pouvez gérer la [persistance des
données](https://github.com/linuxkit/linuxkit/blob/master/examples/swap.yml)
stockées dans Vault.

View File

@ -13,258 +13,3 @@ des images pour les différents fournisseurs de cloud !
Bien entendu, au sein de ce système, tout est fait de conteneur ! Alors quand
il s'agit de donner une IP publique utilisable par l'ensemble des conteneurs,
il faut savoir jouer avec les *namespaces* pour arriver à ses fins !
Si vous vous rappelez du cours d'AdLin[^adlin] en début d'années, toutes les
VMs que vous avez utilisées reposaient entièrement sur `linuxkit`. En fait,
chaque conteneur représentait alors une machine différente : un conteneur
mattermost d'un côté, un conteneur `unbound` pour faire office de serveur DNS,
et les machines clientes étaient de simples conteneurs exécutant un client
dhcp.
[^adlin]: toutes les sources des machines sont dans ce dépôt :
<https://git.nemunai.re/?p=lectures/adlin.git;a=tree>
## Prérequis
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :
<https://github.com/linuxkit/linuxkit/releases/latest>.
Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire
en plus du binaire[^lollibc] ;-)
[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.
Vous aurez également besoin de QEMU pour tester vos créations.
## Structure d'un fichier `linuxkit.yml`
Le fichier utilisé pour construire notre image se décompose en plusieurs
parties :
- `kernel` : il est attendu ici une image OCI contenant le nécessaire pour
pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
- `init` : l'ensemble des images OCI de cette liste seront fusionnés pour
donner naissance au *rootfs* de la machine. On n'y place normalement qu'un
gestionnaire de conteneur, qui sera chargé de lancer chaque conteneur au bon
moment ;
- `onboot`, `onshutdown` et `services` : il s'agit de conteneurs qui seront
lancés par le système disponible dans l'`init`, au bon moment. Les conteneurs
indiqués dans `onboot` seront lancés **séquentiellement** au démarrage de la
machine, ceux dans `onshutdown` seront lancés lors de l'arrêt de la
machine. Les conteneurs dans `services` seront lancés simultanément une fois
que le dernier conteneur de `onboot` aura rendu la main ;
- `files` : des fichiers supplémentaires à placer dans le rootfs.
Le format est documenté
[ici](https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md).
## Hello?
L'image la plus simple que l'on puisse réaliser pourrait être :
<div lang="en-US">
```yaml
kernel:
image: linuxkit/kernel:4.14.80
cmdline: "console=tty0 console=ttyS0"
init:
- linuxkit/init:c563953a2277eb73a89d89f70e4b6dcdcfebc2d1
- linuxkit/runc:83d0edb4552b1a5df1f0976f05f442829eac38fe
- linuxkit/containerd:326b096cd5fbab0f864e52721d036cade67599d6
onboot:
- name: dhcpcd
image: linuxkit/dhcpcd:v0.6
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
services:
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
env:
- INSECURE=true
trust:
org:
- linuxkit
```
</div>
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un
shell sur la machine !
On notera cependant que, positionné dans `services`, le shell que nous
obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un
accès entièrement privilégier. Pour déboguer, il faut placer cette image dans
la partie `init`, elle sera alors lancé comme un équivalent de
`init=/bin/sh`.[^infogetty]
[^infogetty]: Plus d'infos à
<https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>
## `namespaces`
Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourra
pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une
faille exploitable.
Néanmoins, contrairement à Docker qui va de base nous dissocier du maximum de
*namespaces*, `linuxkit` ne le fait pas pour les *namespaces* `net`, `ipc` et
`uts`. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les
conteneurs, tout comme les IPC et le nom de la machine.
Il reste possible de se dissocier également de ces namespaces, en précisant :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
net: new
```
</div>
Ou inversement, pour persister dans le namespace initial :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
pid: host
```
</div>
### Partage de `namespace`
Dans le cas où l'on souhaite que deux conteneurs partagent le même *namespace*,
il faut passer le chemin vers la structure du noyau correspondante.
On commence donc d'abord par créer le nouveau *namespace*, en prenant soin de
*bind mount* la structure du noyau à un emplacement connu :
<div lang="en-US">
```yaml
- name: getty
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
net: new
runtime:
bindNS: /run/netns/mynewnetns
```
</div>
À la création du *namespace* `net`, le lien vers la structure du noyau
correspondante sera *bind mount* sur `/run/netns/synchro`. On pourra alors
réutiliser plus tard ce chemin, en remplacement du mot clef `new` :
<div lang="en-US">
```yaml
- name: xxxx
image: linuxkit/xxxx:v0.6
net: /run/netns/mynewnetns
```
</div>
## Construction et démarrage
Toute la puissance de `linuxkit` repose dans son système de construction et
surtout de lancement. En effet, il peut construire des images pour un grand
nombre de plate-forme, mais il est également possible d'utiliser les API de ces
plates-formes pour aller y lancer des instances de cette image !
Pour construire l'image faite précédemment :
<div lang="en-US">
```bash
linuxkit build hello.yml
```
</div>
Cela va générer plusieurs fichiers dont un noyau (extrait de l'image de la
partie `kernel`) ainsi qu'une image. Exactement ce qu'attend QEMU ! Pour
tester, n'attendons pas davantage pour lancer :
<div lang="en-US">
```bash
linuxkit run qemu -gui hello
```
</div>
## Ajouter un service
Maintenant que notre machine fonctionne, que nous pouvons interagir avec elle,
tentons de se passer de l'interface de QEMU (option `-gui`) en ajoutant un
serveur SSH aux `services` :
<div lang="en-US">
```yaml
- name: sshd
image: linuxkit/sshd:c4bc89cf0d66733c923ab9cb46198b599eb99320
```
</div>
Comme nous n'avons définis aucun mot de passe, il va falloir utiliser une clef
SSH pour se connecter. Voilà un bon début d'utilisation de la section `files` :
<div lang="en-US">
```yaml
- path: root/.ssh/authorized_keys
source: ~/.ssh/id_rsa.pub
mode: "0600"
```
</div>
Ceci va aller chercher votre clef RSA sur votre machine, pour la placer dans
Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi
recopié, car le *package* `linuxkit/sshd` défini déjà cela :
[pkg/sshd/build.yml#L5](https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5).
## Interface réseau virtuelle
Lorsque l'on souhaite se dissocier d'un *namespace* `net` afin de s'isoler,
mais que l'on veut tout de même pouvoir communiquer, il est nécessaire de créer
une interface `virtual ethernet` :
<div lang="en-US">
```yaml
- name: db
image: mariadb:latest
net: new
runtime:
bindNS:
net: /run/netns/db
interfaces:
- name: vethin-db
add: veth
peer: veth-db
```
</div>
## Exercice {-}
Réalisez une recette `vault.yml` démarrant une instance du gestionnaire de
secrets [Hashicorp Vault](https://www.vaultproject.io/), utilisant une [base de
données au
choix](https://www.vaultproject.io/docs/configuration/storage/index.html)
(Consul, Etcd, MySQL, Cassandra, ...).
Au démarrage, Vault devra déjà être configuré pour parler à sa base de données,
qui devra se trouver dans un conteneur isolé et non accessible d'internet. Il
faudra donc établir un lien `virtual ethernet` entre les deux conteneurs ; et
ne pas oublier de le configurer (automatiquement au *runtime*, grâce à un
[`poststart`
*hook*](https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks)
ou bien à un conteneur issu du *package*
[`ip`](https://github.com/linuxkit/linuxkit/tree/master/pkg/ip)).
Les permissions étant généralement très strictes, vous aurez sans doute besoin
de les assouplir un peu en ajoutant des *capabilities* autorisées à vos
conteneurs, sans quoi vos conteneurs risquent d'être tués prématurément.
En bonus, vous pouvez gérer la [persistance des
données](https://github.com/linuxkit/linuxkit/blob/master/examples/swap.yml)
stockées dans Vault.

View File

@ -7,7 +7,7 @@ Formée en juin 2015, l'Open Container Initiative (OCI) a pour but d'établir le
standard commun aux programmes de contenerisation, afin d'éviter une
fragmentation de l'écosystème.
Trois spécifications ont été écrites :
Trois spécifications ont été écrites :
- [`runtime-spec`](https://github.com/opencontainers/runtime-spec/blob/master/spec.md#platforms): définit les paramètres du démarrage d'un conteneur ;
- [`image-spec`](https://github.com/opencontainers/image-spec/blob/master/spec.md): définit la construction, le transport et la préparation des images ;
@ -24,8 +24,8 @@ d'avoir un fichier de configuration `config.json` ainsi qu'un dossier où l'on
peut trouver la racine de notre conteneur.
Le plus gros de la spécification concerne le format de ce fichier de
configuration : il contient en effet l'ensemble des paramètres avec lesquels il
faudra créer le conteneur : tant d'un point de vue isolement (on peut par
configuration : il contient en effet l'ensemble des paramètres avec lesquels il
faudra créer le conteneur : tant d'un point de vue isolement (on peut par
exemple choisir de quel *namespace* on souhaite se dissocier, ou le(s)quel(s)
rejoindre), quelles *capabilities* resteront disponibles, quels nouveaux points
de montages, ... Voir [la
@ -45,8 +45,8 @@ fichiers, d'une configuration ainsi qu'un index d'image optionnel.
Le
[manifest](https://github.com/opencontainers/image-spec/blob/master/manifest.md)
est toujours le point d'entrée d'une image : il référence l'emplacement où
trouver les différents éléments : configuration et couches. Lorsqu'une même
est toujours le point d'entrée d'une image : il référence l'emplacement où
trouver les différents éléments : configuration et couches. Lorsqu'une même
image a des variation en fonction de l'architecture du processeur, du système
d'exploitation, ... dans ce cas [l'index
d'image](https://github.com/opencontainers/image-spec/blob/master/image-index.md)
@ -54,7 +54,7 @@ est utilisé pour sélectionner le bon manifest.
Le format des [couches de système de
fichiers](https://github.com/opencontainers/image-spec/blob/master/layer.md)
sont spécifiées : il est nécessaire de passer par des formats standards (comme
sont spécifiées : il est nécessaire de passer par des formats standards (comme
les tarballs), contenant éventuellement des fichiers et dossiers spéciaux
contenant les modifications, suppressions, ... éventuelles de la couche
représentée.
@ -64,14 +64,14 @@ La
contient l'ensemble des métadonnées qui serviront par exemple à construire le
`config.json` attendu par `runc` lorsqu'il faudra lancer l'image (c'est
là-dedans que finissent toutes les métadonnées que l'on inscrit dans nos
`Dockerfile` : `CMD`, `VOLUME`, `PORT`, ...). On y retrouve également l'ordre
`Dockerfile` : `CMD`, `VOLUME`, `PORT`, ...). On y retrouve également l'ordre
des couches du système de fichiers, ainsi que l'historique de l'image.
## `distribution-spec`
Dernière née de l'organisme, cette spécification fédère la notion de
*registre* : une API REST sur HTTP où l'on peut récupérer des images, mais
*registre* : une API REST sur HTTP où l'on peut récupérer des images, mais
aussi en envoyer.
@ -79,7 +79,7 @@ aussi en envoyer.
Si maintenant `docker` fait appel à un programme externe pour lancer
effectivement nos conteneurs, c'est que l'on peut changer cette implémentation
? la réponse dans l'article :
? la réponse dans l'article :
<https://ops.tips/blog/run-docker-with-forked-runc/>
Et `containerd` dans l'histoire ?

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -3,14 +3,14 @@
Registres
=========
**Outils nécessaires :** `curl`, `gunzip`, `jq`, `tar`.
**Outils nécessaires :** `curl`, `gunzip`, `jq`, `tar`.
* * * * *
Nous allons appréhender le fonctionnement d'un registre OCI,
et préparer le *rootfs* d'une image de base (Debian, Ubuntu, hello, ...) : en
et préparer le *rootfs* d'une image de base (Debian, Ubuntu, hello, ...) : en
nous préoccupant simplement de la couche la plus basse (qui ne contient pas de
modification ou de suppression : chaque fichier est normal).
modification ou de suppression : chaque fichier est normal).
## Authentification
@ -18,7 +18,7 @@ modification ou de suppression : chaque fichier est normal).
L'authentification est facultative et est laissée à l'appréciation du
fournisseur de service. Étant donné que nous allons utiliser le [Docker
Hub](https://hub.docker.com/), le registre par défaut de `docker`, nous allons
devoir nous plier à leur mécanisme d'authentification : chaque requête au
devoir nous plier à leur mécanisme d'authentification : chaque requête au
registre doit être effectuée avec un jeton, que l'on obtient en s'authentifiant
auprès d'un service dédié. Ce service peut délivrer un jeton sans authentifier
l'interlocuteur, en restant anonyme ; dans ce cas, on ne pourra accéder qu'aux
@ -26,8 +26,8 @@ images publiques. Ça tombe bien, c'est ce qui nous intéresse aujourd'hui !
Il n'en reste pas moins que le jeton est forgé pour un service donné (dans
notre cas `registry.docker.io`) et avec un objectif bien cerné (pour nous, on
souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
<span lang="en-US">`repository:hello-world:pull`</span>). Ce qui nous donne :
souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
<span lang="en-US">`repository:hello-world:pull`</span>). Ce qui nous donne :
[^quiddepot]: Dans un registre, les fichiers qui composent l'image forment un
dépôt (*repository*).
@ -50,7 +50,7 @@ souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
C'est le `token` qu'il faudra fournir lors de nos prochaines requêtes au
registre.
Avec `jq`, on peut l'extraire grâce à :
Avec `jq`, on peut l'extraire grâce à :
<div lang="en-US">
```bash
@ -58,7 +58,7 @@ Avec `jq`, on peut l'extraire grâce à :
```
</div>
**Attention :** le token expire ! Pensez à le renouveler régulièrement.
**Attention :** le token expire ! Pensez à le renouveler régulièrement.
En cas d'erreur inexplicable, vous pouvez ajouter un `-v` à la ligne de
commande `curl`, afin d'afficher les en-têtes. Prêtez une attention toute
@ -68,7 +68,7 @@ particulière à `Www-Authenticate`.
## Lecture de l'index d'images
Une fois en possession de notre jeton, nous pouvons maintenant demander l'index
d'images à notre registre :
d'images à notre registre :
<div lang="en-US">
```bash
@ -87,7 +87,7 @@ somme de contrôle.
## Lecture du manifest
Demandons maintenant le manifest correspondant à notre matériel et à notre
système d'exploitation :
système d'exploitation :
<div lang="en-US">
```bash
@ -109,7 +109,7 @@ répertoire `blobs`, il ne s'agit en effet plus de manifest. Si les manifests
sont toujours stockés par le registre lui-même, les blobs peuvent être délégués
à un autre service, par exemple dans le cloud, chez Amazon S3, un CDN, etc.
Pour récupérer la configuration de l'image :
Pour récupérer la configuration de l'image :
<div lang="en-US">
```bash
@ -120,7 +120,7 @@ curl -s --location \
</div>
Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment :
Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment :
<div lang="en-US">
```bash
@ -134,7 +134,7 @@ wget --header "Authorization: Bearer ${TOKEN}" \
Le type indiqué par le manifest pour cette couche était
`application/vnd.docker.image.rootfs.diff.tar.gzip`, il s'agit donc d'une
tarball compressée au format gzip :
tarball compressée au format gzip :
<div lang="en-US">
```bash
@ -143,7 +143,7 @@ tar xzf ${DL_LAYER} -C rootfs
```
</div>
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
<div lang="en-US">
```bash
@ -156,7 +156,7 @@ Hello from Docker!
## Exercice {-}
Réalisez un script pour automatiser l'ensemble de ces étapes :
Réalisez un script pour automatiser l'ensemble de ces étapes :
<div lang="en-US">
```bash

View File

@ -26,7 +26,7 @@ Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Voici une arborescence type (vous pourriez avoir des fichiers supplémentaires,
cela dépendra de votre avancée dans le projet) :
cela dépendra de votre avancée dans le projet) :
<div lang="en-US">
```

View File

@ -4,7 +4,7 @@
======
`runc` est le programme qui est responsable de la création effective du
conteneur : c'est lui qui va mettre en place toute la machinerie, les points de
conteneur : c'est lui qui va mettre en place toute la machinerie, les points de
montages ou volumes, ... Attention, son rôle reste limité à la mise en place de
l'environnement conteneurisé, ce n'est pas lui qui télécharge l'image, ni fait
l'assemblage des couches de système de fichiers, entre autres.
@ -21,17 +21,17 @@ essayer de lancer un shell `alpine` avec un volume dans notre home.
## Prérequis
Vous devriez avoir le binaire `runc` ou `docker-runc`. Si ce n'est pas le cas,
vous pouvez télécharger la dernière version :
vous pouvez télécharger la dernière version :
<https://github.com/opencontainers/runc/releases>. La 1.0.0-rc92 est Ok.
## Extraction du rootfs
À l'aide du script d'extraction de registre réalisé dans le TP 3, extrayons le
rootfs d'alpine : `library/alpine` dans le registre Docker.
rootfs d'alpine : `library/alpine` dans le registre Docker.
Si vous n'avez pas eu le temps de terminer le script d'extraction, vous pouvez
utiliser :
utiliser :
<div lang="en-US">
```bash
@ -44,7 +44,7 @@ docker image save alpine | tar xv -C rootfs
L'écriture complète d'un fichier `config.json` pour `runc` est plutôt
fastidieux et répétitif, nous allons donc gagner du temps et utiliser la
commande suivante, qui nous créera un modèle que nous adapterons un peu :
commande suivante, qui nous créera un modèle que nous adapterons un peu :
<div lang="en-US">
```bash
@ -52,7 +52,7 @@ runc spec
```
</div>
Pour savoir à quoi correspondent tous ces éléments, vous pouvez consulter :
Pour savoir à quoi correspondent tous ces éléments, vous pouvez consulter :
<https://github.com/opencontainers/runtime-spec/blob/master/config.md>
Nous verrons dans les prochains TP, plus en détails tout ce qui porte sur les
@ -61,7 +61,7 @@ aujourd'hui.
## Test brut
Voici comment nous pouvons tester le fonctionnement de notre *bundle* :
Voici comment nous pouvons tester le fonctionnement de notre *bundle* :
<div lang="en-US">
```
@ -75,7 +75,7 @@ rootfs/ config.json
Quelques informations sont disponibles, mais il ne faut pas s'attendre à
retrouver tout l'écosystème de `docker` ; ici il n'y a pas de gestion des
journaux, etc. :
journaux, etc. :
<div lang="en-US">
```bash
@ -92,7 +92,7 @@ virli1 12345 running /tmp/work/runctest 2012-12-12T12:12:12.123456789Z root
## Attacher notre `home`
Dans le modèle de `config.json`, il y a déjà de nombreux systèmes de fichiers
qui sont montés. Nous pouvons les filtrer avec :
qui sont montés. Nous pouvons les filtrer avec :
<div lang="en-US">
```bash
@ -110,7 +110,7 @@ qui sont montés. Nous pouvons les filtrer avec :
</div>
Pour avoir notre équivalent du `-v /home:/home` de `docker`, il va donc falloir
ajouter un élément à cette liste, demandant de *bind* :
ajouter un élément à cette liste, demandant de *bind* :
<div lang="en-US">
```json
@ -143,8 +143,8 @@ stocker les photos (dossier `/srv/images`)[^chmod].
simple pour l'instant serait d'attribuer les permissions `0777` à la
source, temporairement.
Pour ce TP, considérez que vous avez réussi si vous voyez s'afficher :
Pour ce TP, considérez que vous avez réussi si vous voyez s'afficher :
> `Ready, listening on :8080`
> `Ready, listening on :8080`
Il faudra attendre les TP suivants pour avoir du réseau dans notre conteneur.

View File

@ -0,0 +1,12 @@
## Exercice {-}
Déterminez le nombre de vulnérabilités dans les principales images officielles
du [Docker Hub](https://hub.docker.com/explore), notamment `nginx`, `golang`,
`redis`, ...
En utilisant l'outil de votre choix, exporter un rapport HTML (ou la sortie
standard de l'outil s'il n'exporte pas en HTML) de l'image **officielle**
contenant les vulnérabilités les plus inquiétantes. Vous placerez ce rapport
dans un fichier `${IMAGE}:${TAG}.html` ou `${IMAGE}:${TAG}.txt`.
Veillez à utiliser une image __différente__ de `mysql`, utilisée en exemple.

View File

@ -0,0 +1,259 @@
\newpage
Analyse de vulnérabilité
========================
Nous avons vu jusqu'à présent que Docker nous apportait un certain degré de
sécurité d'emblée, au lancement de nos conteneurs. Cela peut sans doute
paraître quelque peu rassurant pour une personne chargée d'administrer une
machine hébergeant des conteneurs, car cela lui apporte des garanties quant à
l'effort de cloisonnement mis en place.
Mais doit-on pour autant s'arrêter là et considérer que nous avons réglé
l'ensemble des problématiques de sécurité liées aux conteneurs ?
Évidemment, non : une fois nos services lancés dans des conteneurs, il ne sont
pas moins exposés aux bugs et autres failles applicatives ; qu'elles soient
dans notre code ou celui d'une bibliothèque, accessible par rebond, ...
Il est donc primordial de ne pas laisser ses conteneurs à l'abandon une fois
leur image créée et envoyée en production. Nos conteneurs doivent être
regénérés sitôt que leur image de base est mise à jour (une mise à jour d'une
image telle que Debian, Ubuntu ou Redhat n'apparaît que pour cela) ou bien
lorsqu'un des programmes ou l'une des bibliothèques que l'on a installés
ensuite est mise à jour.
Convaincu ? Cela sonne encore comme des bonnes pratiques difficiles à mettre en
œuvre, pouvant mettre en péril tout un système d'information. Pour s'en
protéger, nous allons avoir besoin de réaliser à intervalles réguliers une
analyse statique de nos conteneurs.
![Scan de vulnérabilités sur le Docker Hub](hubvuln.png){ width=90% }
Selon une étude[^dockerhubvulnerability] réalisée sur les images du Docker Hub,
elles présenteraient en moyenne 180 vulnérabilités, beaucoup ne sont pas mises
à jour depuis trop longtemps et les vulnérabilités ont surtout tendance à se
propager à cause de l'usage d'une image parente pas à jour.
[^dockerhubvulnerability]: <https://www.enck.org/pubs/shu-codaspy17.pdf>
Une mesure efficace consiste à reconstruire régulièrement (et surtout
automatiquement) les images que l'on publie sur un registre public, sans
oublier de mettre à jour l'image de base.
D'ailleurs, avez-vous vérifié qu'une mise à jour de l'image `nemunaire/youp0m`
n'était pas disponible depuis que vous avez commencé à l'utiliser ? Docker ne
vérifie jamais si une mise à jour des images que vous avez précédemment
téléchargées. Pensez donc régulièrement à appeler :
<div lang="en-US">
```
42sh$ docker pull IMAGE
```
</div>
## Docker Scan
Pour faire face à ces problèmes de sécurité qui prennent de l'ampleur, le
Docker Hub, dans son modèle payant, permet d'analyser régulièrement ses images,
pour avoir une idée sur la nécessité de les reconstruire.
Un plugin existe pour réaliser des scans d'images présentes sur votre machine,
à travers les données du Docker Hub. Cela nécessite d'avoir un compte Docker,
si vous n'en avez pas, nous verrons dans la section suivante `trivy` qui permet
de réaliser ses scans directement sur notre machine, sans passer par un
intermédiaire.
#### Attention {-}
Par cette méthode, vous êtes limité à 10 scans par mois.
### Installation du plugin
#### Windows et MacOS {-}
Avec Docker Desktop, le plugin est déjà installé. Il faut que vous vous soyez
préalablement connecté à votre compte Docker avec la commande `docker login`.
#### Linux {-}
Comme `docker scan` est un plugin, suivant la méthode d'installation que vous
avez suivie, il n'a pas forcément été installé. Si vous obtenez un message
d'erreur en lançant la commande, [voici comment récupérer le plugin et
l'installer manuellement :](https://github.com/docker/scan-cli-plugin#on-linux)
<div lang="en-US">
```
mkdir -p ~/.docker/cli-plugins
curl https://github.com/docker/scan-cli-plugin/releases/latest/download/docker-scan_linux_amd64 \
-L -s -S -o ~/.docker/cli-plugins/docker-scan
chmod +x ~/.docker/cli-plugins/docker-scan
```
</div>
### Utilisation
Une fois le plugin installé et la licence du service acceptée, nous pouvons
commencer notre analyse :
<div lang="en-US">
```
42sh$ docker scan nemunaire/fic-admin
Testing nemunaire/fic-admin...
Package manager: apk
Project name: docker-image|nemunaire/fic-admin
Docker image: nemunaire/fic-admin
Platform: linux/amd64
Base image: alpine:3.14.2
✓ Tested 16 dependencies for known vulnerabilities, no vulnerable paths found.
According to our scan, you are currently using the most secure version of the selected base image
```
</div>
<div lang="en-US">
```
$ docker scan mysql
Testing mysql...
✗ Low severity vulnerability found in util-linux/libuuid1
[...]
✗ High severity vulnerability found in gcc-8/libstdc++6
Description: Insufficient Entropy
Info: https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-469413
Introduced through: apt@1.8.2.3, mysql-community/mysql-community-client@8.0.26-1debian10, mysql-community/mysql-community-server-core@8.0.26-1debian10, mecab-ipadic@2.7.0-20070801+main-2.1, meta-common-packages@meta
From: apt@1.8.2.3 > gcc-8/libstdc++6@8.3.0-6
From: mysql-community/mysql-community-client@8.0.26-1debian10 > gcc-8/libstdc++6@8.3.0-6
From: mysql-community/mysql-community-server-core@8.0.26-1debian10 > gcc-8/libstdc++6@8.3.0-6
and 7 more...
Image layer: Introduced by your base image (mysql:8.0.26)
Package manager: deb
Project name: docker-image|mysql
Docker image: mysql
Platform: linux/amd64
Base image: mysql:8.0.26
Tested 135 dependencies for known vulnerabilities, found 79 vulnerabilities.
According to our scan, you are currently using the most secure version of the selected base image
```
</div>
Ce dernier exemple est sans appel : `mysql` est une image officielle, et sa
dernière version à l'écriture de ses lignes contient pas moins de 79
vulnérabilités dont 11 *high*.
## Trivy
Le principal outil pour chercher des vulnérabilités connues dans les images
Docker est [`trivy`](https://www.aquasec.com/products/trivy/), édité par Aqua
security.
À partir des informations mises à disposition par les équipes de sécurité des
principales distributions, `trivy` va générer un rapport, pour chacune de nos
images, indiquant les vulnérabilités connues au sein de l'image.
L'outil se présente sous la forme d'un binaire ou d'une image Docker, prenant
un certain nombre d'arguments, notamment le nom de l'image à analyser.
### Utilisation
Tentons à nouveau d'analyser l'image `mysql` :
<div lang="en-US">
```
42sh$ docker run --rm aquasec/trivy mysql
2021-09-22T10:27:46.509Z INFO Need to update DB
2021-09-22T10:27:46.509Z INFO Downloading DB... 100.00% 14.41 MiB p/s 2s
2021-09-22T10:27:56.556Z INFO Detected OS: debian
2021-09-22T10:27:56.556Z INFO Detecting Debian vulnerabilities...
2021-09-22T10:27:56.579Z INFO Number of language-specific files: 0
mysql (debian 10.10)
====================
Total: 158 (UNKNOWN: 5, LOW: 19, MEDIUM: 64, HIGH: 61, CRITICAL: 9)
```
</div>
Les résultats sont un peu différents qu'avec `docker scan`, mais on constate
que l'image `mysql` contient vraiment de nombreuses vulnérabilités. Même si
elles ne sont heureusement pas forcément exploitable directement.
Voyons maintenant s'il y a des différentes avec l'image `nemunaire/fic-admin` :
<div lang="en-US">
```
42sh$ docker run --rm aquasec/trivy nemunaire/fic-admin
2021-09-22T10:29:48.091Z INFO Need to update DB
2021-09-22T10:29:48.091Z INFO Downloading DB... 100.00% 15.98 MiB p/s 1s
2021-09-22T10:29:51.902Z INFO Detected OS: alpine
2021-09-22T10:29:51.902Z INFO Detecting Alpine vulnerabilities...
2021-09-22T10:29:51.903Z INFO Number of language-specific files: 1
2021-09-22T10:29:51.903Z INFO Detecting gobinary vulnerabilities...
nemunaire/fic-admin (alpine 3.14.2)
===================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
srv/admin (gobinary)
====================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
```
</div>
Nous pouvons remarque que Trivy, en plus de faire l'analyse statique des
vulnérabilités de l'image, a aussi fait une analyse des dépendances du binaire
`/srv/admin`.
Trivy est en effet capable de rechercher des vulnérabilités par rapport aux
dépendances connues de certains langages : Python, PHP, Node.js, .NET, Java,
Go, ...
### Usage du cache
Pour éviter de surcharger les serveurs de distributions de la base de données
de vulnérabilités, nous devrions utiliser un cache pour faire nos
analyses. Préférez lancer `trivy` avec les options suivantes :
<div lang="en-US">
```
42sh$ docker run --rm -v /tmp/trivy-cache:/tmp/trivy/ aquasec/trivy --cache-dir /tmp/trivy
```
</div>
### Format de génération du rapport
Lorsque nous appelons `trivy` directement, il génère un rapport au format texte
lisible directement dans notre terminal. Il peut être pourtant pratique de
pouvoir l'exporter pour l'afficher dans un navigateur (par exemple pour le
mettre à disposition des développeurs, lors d'une analyse automatique).
Pour ce faire, on peut ajouter les options suivantes à la ligne de commande de
notre conteneur :
<div lang="en-US">
```bash
--quiet --format template --template "@contrib/html.tpl"
```
</div>
En redirigeant la sortie standard vers un fichier, vous pourrez l'ouvrir dans
votre navigateur favori.
---
![Scan de vulnérabilités sur le registre Quay.io](quay-vulns.png){ width=90% }
## Clair

View File

@ -1,12 +1,12 @@
\newpage
Ma première image ... par `Dockerfile`
======================================
--------------------------------------
Pour construire une image, nous ne sommes pas obligés de passer par une série
de *commits*. Docker dispose d'un mécanisme permettant d'automatiser la
construction de nouvelles images. Nous pouvons arriver au même résultat que ce
que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant :
que l'on a réussi à faire précédemment en utilisant le `Dockerfile` suivant :
<div lang="en-US">
```dockerfile
@ -17,7 +17,7 @@ RUN apt-get install -y nano
```
</div>
La syntaxe d'un `Dockerfile` est simple : le premier mot de chaque ligne est
La syntaxe d'un `Dockerfile` est simple : le premier mot de chaque ligne est
l'intitulé d'une instruction (que l'on écrit généralement en majuscule), elle
est suivie de ses arguments.
@ -27,7 +27,7 @@ le conteneur, dans le but de le construire.
Pour lancer la construction de la nouvelle image, créons un nouveau dossier ne
contenant que votre fichier `Dockerfile`, plaçons-nous ensuite dedans, puis
lançons la commande `build` :
lançons la commande `build` :
<div lang="en-US">
```bash
@ -36,7 +36,7 @@ docker image build --tag=my_editor .
</div>
Une fois la construction de l'image terminée, nous pouvons la lancer et
constater l'existence de notre éditeur favori :
constater l'existence de notre éditeur favori :
<div lang="en-US">
```bash
@ -46,13 +46,13 @@ docker container run -it my_editor /bin/bash
</div>
## `RUN` dans le `Dockerfile`
### `RUN` dans le `Dockerfile`
Dans un `Dockerfile`, chaque ligne est exécutée indépendamment des autres et
correspondra à une nouvelle couche de notre image. Exactement comme on a
réalisé le script dans la partie précédente.
Cela signifie que l'exemple suivant **ne fonctionne pas** :
Cela signifie que l'exemple suivant **ne fonctionne pas** :
<div lang="en-US">
```dockerfile
@ -63,11 +63,11 @@ RUN mysql -u root -p toor virli < /db.sql
</div>
Cet exemple ne fonctionne pas car le serveur MySQL est bien lancé dans le
premier `RUN`{.dockerfile}, mais il se trouve brûtalement arrêté dès lors que
premier `RUN`{.dockerfile}, mais il se trouve brutalement arrêté dès lors que
la commande `service` se termine. En fait, à chaque instruction, Docker réalise
automatiquement l'équivalent un `docker run` suivi d'un `commit`. Et vous
pouvez constater par vous-même que, en créant l'image `tinysql` à partir d'un
simple `apt install mysql` :
simple `apt install mysql` :
<div lang="en-US">
```bash
@ -78,7 +78,7 @@ docker container run tinysql service mysqld start
rend la main directement, sans laisser de `mysqld` dans l'arborescence de
processus.
Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
<div lang="en-US">
```dockerfile
@ -95,9 +95,9 @@ run`. Seul la commande fournie par l'utilisateur ou la commande par défaut de
l'image sera exécutée au lancement d'un conteneur.
## Exposer des ports
### Exposer des ports
Construisons maintenant un conteneur avec un service web :
Construisons maintenant un conteneur avec un service web :
<div lang="en-US">
```dockerfile
@ -114,10 +114,10 @@ L'instruction `EXPOSE`{.dockerfile} sera traitée plus tard par le client Docker
(équivalent à l'argument `--expose`). Il s'agit d'une métadonnée qui sera
attachée à l'image (et à toutes ses images filles).
En précisant tous les ports qu'exposent une image dans ses métadonnées, ces
ports seront automatiquement exposés en utilisant l'option `-P` du `run` : cela
En précisant tous les ports qu'expose une image dans ses métadonnées, ces
ports seront automatiquement exposés en utilisant l'option `-P` du `run` : cela
assigne une redirection de port aléatoire sur la machine hôte vers votre
conteneur :
conteneur :
<div lang="en-US">
```
@ -132,13 +132,15 @@ Dans un autre terminal, lancer un `docker container ls` et consulter la colonne
Rendez-vous ensuite dans votre navigateur sur <http://localhost:49153/>.
**À vous de jouer :** utilisez l'instruction `COPY`{.dockerfile} pour afficher votre propre
#### À vous de jouer {-}
Utilisez l'instruction `COPY`{.dockerfile} pour afficher votre propre
`index.html` remplaçant celui installé de base par `nginx`. Si vous manquez
d'inspiration, utilisez [cette page de compte à
rebours](https://virli.nemunai.re/countdown.html).
## Les caches
### Les caches
Nous avons vu que chaque instruction de notre `Dockerfile` est exécutée dans un
conteneur, qui génère une image intermédiaire. Cette image intermédiaire sert
@ -164,19 +166,19 @@ Les couches du cache peuvent être partagées entre plusieurs conteneur, c'est
ainsi que vous pouvez partager facilement une plus grosse partie du système de
fichiers (rappelez-vous le principe d'union FS).
Pour profiter du cache, on va placer de préférences les étapes les plus
Pour profiter du cache, on va placer de préférence les étapes les plus
génériques (qui seraient les plus susceptibles d'apparaître dans d'autres
images), en haut du `Dockerfile`.
## Métadonnées pures
### Métadonnées pures
L'instruction `LABEL`{.dockerfile} permet d'ajouter une métadonnée à une image,
sous forme de clef/valeur.
Une métadonnée
[courante](https://github.com/nginxinc/docker-nginx/blob/master/mainline/stretch/Dockerfile#L3)
est d'indiquer le nom du mainteneur de l'image :
est d'indiquer le nom du mainteneur de l'image :
<div lang="en-US">
```dockerfile
@ -194,10 +196,10 @@ On le place dès le début, car comme c'est une information qui n'est pas amener
à changer, elle sera toujours retrouvée en cache.
## Commande par défaut
### Commande par défaut
Vous pouvez placer dans un `Dockerfile` une instruction `CMD`{.dockerfile} qui
sera exécutée si aucune commande n'est passée lors du `run`, par exemple :
sera exécutée si aucune commande n'est passée lors du `run`, par exemple :
<div lang="en-US">
```dockerfile
@ -218,7 +220,7 @@ retirez cette option pour voir ce qui ne va pas, ou utilisez la commande
`docker container logs`.
## Construire son application au moment de la construction du conteneur ?
### Construire son application au moment de la construction du conteneur ?
Comment faire lorsque l'on a besoin de compiler une application avant de
l'intégrer dans le conteneur ?
@ -230,14 +232,14 @@ versions de ces outils existent, laquelle choisir ? ... Ok c'est trop
compliqué.
D'un autre côté, si l'on fait cela dans un conteneur, celui-ci contiendra dans
ses couches des données inutiles à l'exécution : les sources, les produits
ses couches des données inutiles à l'exécution : les sources, les produits
intermédiaires de compilation, le compilateur, n'ont rien à faire dans les
couches de notre image.
Le meilleur des deux mondes se trouve dans les *Multi-stage builds* : au sein
Le meilleur des deux mondes se trouve dans les *Multi-stage builds* : au sein
du même `Dockerfile`, on va réaliser les opérations de préparation dans un ou
plusieurs conteneurs, avant d'agréger le contenu compilé au sein du conteneur
final :
final :
<div lang="en-US">
```dockerfile
@ -252,7 +254,7 @@ CMD ["/hello"]
```
</div>
Dans cet exemple, deux conteneurs distincts sont créés : le premier à partir de
Dans cet exemple, deux conteneurs distincts sont créés : le premier à partir de
l'image `gcc`, il contient tout le nécessaire pour compiler notre
`hello.c`. Mais l'image finale (le dernier `FROM`{.dockerfile} de notre
`Dockerfile`) est l'image vide, dans laquelle nous recopions simplement le
@ -261,12 +263,12 @@ produit de notre compilation.
L'image ainsi générée est minime, car elle ne contient rien d'autre que le
strict nécessaire pour s'exécuter.
### Étapes nommées
#### Étapes nommées\
Nous avons utilisé `--from=0` pour désigner la première image de notre
`Dockerfile`. Lorsque l'on réalise des montages plus complexe, on peut vouloir
`Dockerfile`. Lorsque l'on réalise des montages plus complexes, on peut vouloir
donner des noms à chaque image, plutôt que de devoir jongler avec les
numéros. Dans ce cas, on indiquera :
numéros. Dans ce cas, on indiquera :
<div lang="en-US">
```dockerfile
@ -281,9 +283,9 @@ CMD ["/hello"]
```
</div>
Par défaut la dernière étape du `Dockerfile` est retenu comme étant l'image que
Par défaut la dernière étape du `Dockerfile` est retenue comme étant l'image que
l'on souhaite `tagger`, mais il est possible de préciser quelle image
spécifiquement on souhaite construire avec l'option `--target` :
spécifiquement on souhaite construire avec l'option `--target` :
<div lang="en-US">
```
@ -297,13 +299,13 @@ sélectionnera ainsi avec l'option `--target` l'un ou l'autre en fonction de
l'environnement dans lequel on souhaite se déployer.
## D'autres instructions ?
### D'autres instructions ?
Consultez <https://docs.docker.com/engine/reference/builder/> pour la liste
complète des instructions reconnues.
## Exercice {-}
### Exercice {-}
Pour mettre en application tout ce que nous venons de voir, réalisons le
`Dockerfile` du service web [`youp0m`](https://you.p0m.fr/) que nous avons
@ -312,14 +314,14 @@ utilisé la semaine dernière.
Pour réaliser ce genre de contribution, on ajoute généralement un `Dockerfile`
à la racine du dépôt.
Vous pouvez cloner le dépôts de sources de `youp0m` à :
<https://gitea.nemunai.re/nemunaire/youp0m.git>
Vous pouvez cloner le dépôt de sources de `youp0m` à :
<https://git.nemunai.re/nemunaire/youp0m.git>
Pour compiler le projet, vous pouvez utiliser dans votre `Dockerfile`
<div lang="en-US">
```dockerfile
FROM golang:1.13
FROM golang:1.16
COPY . /go/src/git.nemunai.re/youp0m
WORKDIR /go/src/git.nemunai.re/youp0m
RUN go build -tags dev -v

View File

@ -1,31 +1,99 @@
\newpage
Personnalisation du point d'entrée du conteneur
===============================================
Le point d'entrée du conteneur
==============================
## Point d'entrée basique
Le point d'entrée ou l'*entrypoint* correspond à la ligne de commande qui sera
exécutée au lancement du conteneur. Deux paramètres de notre `Dockerfile`
permettent de changer cette ligne de commande : `CMD`{.dockerfile} et
`ENTRYPOINT`{.dockerfile}.
Afin de faire bénéficier à nos utilisateurs d'une immersion parfaite, nous
allons faire en sorte que notre image puisse être utilisée ainsi :
- **`CMD`{.dockerfile}** est la commande par défaut : lorsqu'au moment de
`run`, aucun paramètre n'est passé après le nom de l'image, le contenu du
dernier `CMD`{.dockerfile} rencontré sera utilisé.
- `ENTRYPOINT`{.dockerfile}, s'il est défini, sera réellement exécuté, qu'il y
ait ou non des arguments pour remplacer la ligne de commande. Lorsque des
arguments sont passés ou qu'un `CMD`{.dockerfile}, ceux-ci sont passés en
argument de l'`ENTRYPOINT`{.dockerfile}.
Par exemple, avec le `Dockerfile` suivant, construisant l'image `sample-echo` :
<div lang="en-US">
```dockerfile
FROM ubuntu
CMD ["world"]
ENTRYPOINT ["/bin/echo", "Hello"]
```
</div>
Nous obtenons les résultats suivants :
<div lang="en-US">
```bash
docker run -d -p 80:80 youp0m -bind :80
42sh$ docker run sample-echo
Hello world
```
</div>
Dans ce premier cas, il n'y a pas d'argument après le nom de l'image, c'est
donc le contenu de `CMD`{.dockerfile} qui est utilisé ; il est donc passé en
argument à l'`ENTRYPOINT`{.dockerfile}. Concrètement, la première ligne de
commande exécutée est :
<div lang="en-US">
```json
["/bin/echo", "Hello", "world"]
```
</div>
Essayons maintenant avec des arguments :
<div lang="en-US">
```sh
42sh$ docker run sample-echo $USER
Hello neo
```
</div>
Le contenu de la variable `$USER`, interprété par notre shell, est utilisé à la
place de `CMD`{.dockerfile}.
Si l'on a besoin d'exécuter un `ENTRYPOINT`{.dockerfile} différent, il reste la
possibilité de le surcharger au moyen d'un argument :
<div lang="en-US">
```sh
42sh$ docker run --entrypoint /bin/sh sample-echo
01abc345# _
```
</div>
## Personnalisation basique
Afin de faire bénéficier à nos utilisateurs d'une immersion parfaite, nous
allons faire en sorte que notre image puisse être utilisée ainsi :
<div lang="en-US">
```bash
docker run -d -p 80:80 youp0m -bind :80
```
</div>
Plutôt que de laisser l'utilisateur se débrouiller avec le chemin interne dans
lequel il va trouver le bon binaire :
lequel il va trouver le bon binaire :
<div lang="en-US">
```bash
docker run -d -p 80:80 youp0m /srv/youp0m -bind :80
docker run -d -p 80:80 youp0m /srv/youp0m -bind :80
```
</div>
Essayez les deux commandes, si vous avez utilisé l'instruction
`CMD`{.dockerfile} dans votre `Dockerfile` jusqu'à présent, vous devez vous
`CMD`{.dockerfile} dans votre `Dockerfile` jusqu'à présent, vous devriez vous
trouver dans le deuxième cas.
Pour améliorer la situation, définissez
@ -35,13 +103,13 @@ de votre image sur le binaire `/srv/youp0m`.
## Point d'entrée avancé
Dans certains cas, il peut être nécessaire au lancement d'un conteneur, de
Dans certains cas, il peut être nécessaire au lancement d'un conteneur de
faire un minimum d'étapes d'initialisation avant que le conteneur ne soit
opérationnel (rappelez-vous les options que l'on passait à l'image `mysql` pour
créer un utilisateur et une base).
Notre but, dans cette partie, sera de créer un utilisateur administrateur
(pouvant passer le contrôle d'accès <http://localhost:8080/admin/>) :
(pouvant passer le contrôle d'accès <http://localhost:8080/admin/>) :
<div lang="en-US">
```bash
@ -60,7 +128,7 @@ utilisation de ceux-ci, de leur modification, ...
À la fin d'un script d'`ENTRYPOINT`{.dockerfile}, afin de garder comme premier
processus du conteneur le programme qui nous intéresse, on réalise un
`execve(2)`, sans `fork(2)` :
`execve(2)`, sans `fork(2)` :
<div lang="en-US">
```bash
@ -68,7 +136,7 @@ exec /srv/youp0m $@
```
</div>
Dans cet exemple : `exec` est la commande interne à notre shell pour lui
Dans cet exemple : `exec` est la commande interne à notre shell pour lui
indiquer de remplacer son fil d'exécution par cette commande (sans `exec`, il
va `fork(2)` avant). `$@` est ici pour transmettre tel quel la liste des
arguments passés au script (il s'agit de ceux donnés par l'utilisateur, sur la
@ -78,8 +146,8 @@ l'utilisateur n'a rien précisé).
### Format du fichier `htpasswd`
Le format attendu est celui d'un fichier `htpasswd` typique d'Apache. Vous
pourriez obtenir un fichier valide avec :
Le format attendu est celui d'un fichier `htpasswd` typique d'Apache. Nous
pouvons obtenir un fichier valide avec :
<div lang="en-US">
```bash
@ -102,11 +170,12 @@ d'environnement, à la recherche de `YOUP0M_USERNAME` et `YOUP0M_PASSWORD` pour
initialiser le fichier `.htpasswd` qui sera ajouté à la liste des arguments à
passer au service.
Par exemple :
Par exemple :
<div lang="en-US">
```
42sh$ docker run -d -p 8081:8081 -e YOUP0M_USERNAME=admin -e YOUP0M_PASSWORD=admin youp0m -bind=:8081
42sh$ docker run -d -p 8081:8081 -e YOUP0M_USERNAME=admin \
-e YOUP0M_PASSWORD=admin youp0m -bind=:8081
42sh$ curl -u admin:badpasswd http://localhost:8081/admin/
You are not allowed to perform this request.

View File

@ -4,7 +4,7 @@ Premières étapes
================
Dans un premier temps, nous allons créer une image Docker comme si l'on
réalisait une installation sur une machine classique : en suivant une recette,
réalisait une installation sur une machine classique : en suivant une recette,
sans trop se préoccuper des fonctionnalités que propose Docker.
La machine (notre première image Docker) contiendra tout le nécessaire pour
@ -26,7 +26,7 @@ télécharger le paquet mis à disposition puis à l'installer via `dpkg -i`.
méthode, consultez la
[documentation d'installation](https://docs.influxdata.com/influxdb/v1.6/introduction/installation/#ubuntu-debian).
Deux solutions s'offrent à nous :
Deux solutions s'offrent à nous :
* télécharger le paquet hors du conteneur, le copier, puis l'installer.
* faire un `RUN` avec toutes ces opérations (sans oublier l'installation
@ -64,7 +64,7 @@ InfluxDB. Nous allons installer `telegraf` sur notre machine à l'aide de la
[documentation](https://docs.influxdata.com/telegraf/v1.8/introduction/installation/).
Ces quelques lignes devraient suffir à lancer la collecte, à condition que
votre InfluxDB écoute sur le port 8086 local :
votre InfluxDB écoute sur le port 8086 local :
<div lang="en-US">
```bash
@ -78,7 +78,7 @@ TELEGRAF_CONFIG_PATH=./telegraf/etc/telegraf/telegraf.conf ./telegraf/usr/bin/te
Rendez-vous ensuite dans [l'interface d'InfluxDB](http://localhost:8083/) pour
voir si la collecte se passe bien.
Dans l'interface sélectionnez la base `telegraf` puis explorez les valeurs :
Dans l'interface sélectionnez la base `telegraf` puis explorez les valeurs :
<div lang="en-US">
```sql

View File

@ -1,12 +1,12 @@
\newpage
Retour sur les bonnes pratiques
===============================
Les bonnes pratiques
--------------------
Pour chaque bonne pratique ci-dessous, vérifiez que vous la respectez
bien, faites les modifications nécessaires dans votre `Dockerfile`.
bien, faites les modifications nécessaire dans votre `Dockerfile`.
## Utilisez le fichier `.dockerignore`
### Utilisez le fichier `.dockerignore`
Dans la plupart des cas, vos `Dockerfile` seront dans des dossiers contenant
beaucoup de fichiers qui ne sont pas nécessaire à la construction de votre
@ -25,7 +25,7 @@ Pour plus d'informations, vous pouvez consulter la documentation accessible à
<https://docs.docker.com/engine/reference/builder/#dockerignore-file>.
## N'installez rien de superflu
### N'installez rien de superflu
Afin de réduire la quantité de dépendances à installer, n'installez pas de
paquets dont vous n'avez pas vraiment l'utilité : il n'y a pas de raison par
@ -33,19 +33,30 @@ exemple d'avoir un éditeur de texte dans un environnement qui sera utilisé
comme serveur web. Un autre conteneur pourra contenir cet éditeur de texte dans
les cas où vous avez besoin de modifier des données.
En plus, cela réduira le temps de build et la taille des images produites !
En plus, cela réduira le temps de construction et la taille des images
produites !
Avec `apt` par exemple, vous pouvez ajouter l'option `--no-install-recommends`
lors vous installer un paquet qui vient avec de nombreuses recommandations
inutiles. C'est le cas par exemple de `ffmpeg` ou de `gstreamer`, qui viennent
tous deux avec de nombreux *codecs*, mais peut-être que vous savez exactement
de quels *codecs* vous avez besoin.
## Minimisez le nombre de couches
### Minimisez le nombre de couches
Vous devez trouver l'équilibre idéal entre la lisibilité de votre `Dockerfile`
(qui assure la maintenabilité sur le long-terme) et le nombre de couches
(qui assure la maintenabilité sur le long terme) et le nombre de couches
créées.
Utilisez les constructions en plusieurs étapes pour n'en recopier que les
éléments utiles dans l'image finale. C'est le meilleur moyen de gagner de la
place.
## Ordonnez vos lignes de commandes complexes
### Allez à la ligne pour séparer les longues lignes de commandes complexes
### Ordonnez vos lignes de commandes complexes
#### Allez à la ligne pour séparer les longues lignes de commandes complexes
Aérez vos `Dockerfile` !
@ -64,7 +75,7 @@ RUN apt-get update && apt-get install -y \
Notez les backslashs à la fin des lignes, indiquant qu'elle n'est pas terminée.
### Triez les arguments par ordre alphabétique
#### Triez les arguments par ordre alphabétique
Lorsque c'est possible, ordonnez vos lignes suivant un ordre logique. Par
exemple :
@ -81,7 +92,7 @@ RUN apt-get update && apt-get install -y \
</div>
## Profitez du système de cache
### Profitez du système de cache
Le processus de construction de votre image Docker va lire les informations de
votre Dockerfile dans l'ordre. Pour chaque instruction, Docker va essayer de
@ -93,7 +104,7 @@ Il y a un certain nombre de règles à connaître pour bien utiliser ce mécanis
- En démarrant d'une image de base déjà présente dans le cache (`docker
images`), l'instruction suivante est comparée avec toutes les autres images
existantes qui en dérivent directement. Si aucune image correspondant n'est
trouvé pour l'instruction, le cache est invalidé pour le reste de cette
trouvée pour l'instruction, le cache est invalidé pour le reste de cette
construction.
- Dans la plupart des cas, Docker va simplement comparer l'instruction lue avec
le(s) différente(s) image(s) qui dérive(nt) de la commande précédente. Si
@ -106,7 +117,7 @@ Il y a un certain nombre de règles à connaître pour bien utiliser ce mécanis
dans le `Dockerfile` vont être exécutées.
## Concevez des conteneur éphémères
### Concevez des conteneur éphémères
Les conteneurs que vous générez doivent être aussi éphémères que possible : ils
devraient pouvoir être arrêtés, détruits et recréés sans nécessiter d'étape de
@ -114,23 +125,19 @@ reconfiguration. La configuration devrait se faire au lancement du conteneur ou
lors de sa construction.
## Cas d'`apt-get` et des gestionnaires de paquets
### Cas d'`apt-get` et des gestionnaires de paquets
- N'exécutez pas `apt-get update` seul sur une ligne. Cela risque de poser des
problèmes de cache, car la ligne ne va jamais changer et le cache sera
toujours utilisé. Vous risquez de récupérer des paquets qui ne sont pas à
jour.
- Évitez de mettre à jour le système fourni (via `apt-get upgrade` ou `apt-get
update`). Si l'image n'est pas à jour, contactez son mainteneur. Si vous avez
besoin d'une version à jour d'un paquet distribué avec l'image, préférez
l'utilisation d'`apt-get install -y foo` qui mettra à jour exclusivement le
paquet `foo`, sans altérer le reste du système.
- Pour assurer une bonne gestion du cache, n'hésitez pas à indiquer les
versions des programmes que vous voulez installer sur votre ligne de commande
`apt-get`.
`apt-get`. Lors d'un changement de version, vous changerez la ligne, le cache
ne sera donc pas utilisé.
## Exposez les ports standards
### Exposez les ports standards
La commande `EXPOSE`{.dockerfile} vous permet d'indiquer les ports sur lesquels
votre conteneur s'attend à recevoir des paquets venant de l'extérieur. Ces
@ -145,7 +152,7 @@ S'il y a un conflit sur la machine hôte, il sera toujours temps de créer une
redirection à ce moment là.
## La bonne utilisation de l'`ENTRYPOINT`
### La bonne utilisation de l'`ENTRYPOINT`
L'entrypoint peut être utilisé de deux manières différentes :
@ -185,7 +192,7 @@ exec "$@"
</div>
## `[""]`, `'` et sans `[]`
### `[""]`, `'` et sans `[]`
Les instructions `ENTRYPOINT`{.dockerfile} et `CMD`{.dockerfile} peuvent
prendre deux formes :
@ -200,13 +207,13 @@ Les commandes sous forme de tableau étant parsées par un parser JSON, vous ne
pouvez pas utiliser les simple quotes.
## Volumes
### Volumes
L'instruction `VOLUME`{.dockerfile} doit être utilisée pour exposer tous les
espaces de stockage de données, configuration,\ ...
## Réduisez les privilèges
### Réduisez les privilèges
Utilisez l'instruction `USER`{.dockerfile} dès que vous le pouvez, lorsqu'un
service ne réclame pas de privilège particulier.
@ -214,7 +221,7 @@ service ne réclame pas de privilège particulier.
Il vous faudra sans doute créer l'utilisateur et son groupe dans le Dockerfile.
## Profitez du système de liaison et de résolution de nom
### Profitez du système de liaison et de résolution de nom
Dès lors que vous effectuez un lien avec un autre conteneur, son nom (ou son
alias) est ajouté au fichier `/etc/hosts`. Cela signifie que lorsqu'un nom de
@ -226,7 +233,7 @@ Au moment du `docker run`, vous pouvez préciser d'autres noms d'ĥôtes
particuliers en utilisant l'option `--add-host`.
## Exécutez un seul processus par conteneur
### Exécutez un seul processus par conteneur
Dans la majorité des cas, vous ne devriez jamais lancer plus d'un seul
processus par conteneur. Il est préférable de répartir chaque application dans

View File

@ -1,9 +1,7 @@
\newpage
Modification interactive
========================
------------------------
Pour créer une image, commençons par entrer dans un nouveau conteneur :
Pour créer une image, commençons par entrer dans un nouveau conteneur :
<div lang="en-US">
```bash
@ -12,11 +10,11 @@ docker container run -it ubuntu /bin/bash
</div>
Nous voilà maintenant dans le conteneur ! Il est assez épuré, il n'y a rien de
superflu : même pas d'éditeur de texte : ni vim, ni emacs, même pas `vi` !
superflu : même pas d'éditeur de texte : ni vim, ni emacs, même pas `vi` !
La première chose à faire est de télécharger la liste des paquets. En effet,
afin de ne pas livrer de superflu, la liste des paquets et son cache ne sont
pas incluses dans le conteneur.
pas inclus dans le conteneur.
<div lang="en-US">
```bash
@ -31,14 +29,14 @@ jour automatiques et systématiques des éléments présents dans l'image,
qui apporte des changements peut altérer le comportement du conteneur,
en fonction de la date à laquelle on le construit.
[^SECURITY_UPDATE]: Voir cet article :
[^SECURITY_UPDATE]: Voir cet article :
<https://pythonspeed.com/articles/security-updates-in-docker/>
Si vous souhaitez disposez d'une nouvelle version de l'image, il est
plutôt recommandé de contacter le mainteneur de l'image pour qu'il la
mette à jour, en utilisant un nouveau tag s'il le juge nécessaire.
Installons maintenant un programme :
Installons maintenant un programme :
<div lang="en-US">
```bash
@ -50,7 +48,7 @@ Lorsque l'installation de `nano` est terminée, quittons l'image en tapant
`exit`.
Sauvegardons nos modifications en tant que nouvelle image Docker, avec
la commande `commit` :
la commande `commit` :
<div lang="en-US">
```bash
@ -69,7 +67,7 @@ dernière couche de notre nouvelle image.
![`docker commit`](commit.png)
Testons alors sans plus attendre notre nouvelle image :
Testons alors sans plus attendre notre nouvelle image :
<div lang="en-US">
```bash
@ -81,9 +79,9 @@ Vous constatez cette fois que vous pouvez lancer `nano`, alors que vous ne
pouvez toujours pas le faire dans un conteneur issu d'une image `ubuntu` !
## Scripté ?
### Scripté ?
On peut automatiser les étapes ci-dessus avec un script qui ressemblerait à ça :
On peut automatiser les étapes ci-dessus avec un script qui ressemblerait à ça :
<div lang="en-US">
```bash
@ -94,7 +92,7 @@ docker container commit $(docker container ls -lq) my_nano
```
</div>
On obtiendra de la même manière notre image `my_nano` :
On obtiendra de la même manière notre image `my_nano` :
<div lang="en-US">
```bash

View File

@ -0,0 +1,4 @@
\newpage
Créer une image
===============

View File

@ -0,0 +1,122 @@
\newpage
D'autres méthodes pour créer des images
---------------------------------------
Les images utilisées par Docker pour lancer les conteneurs répondent avant tout
aux spécifications OCI. Le format étant standard, il est normal que d'autres
outils puissent utiliser mais aussi créer des images. Nous allons voir dans
cette partie l'avenir des `Dockerfile` ou simplement d'autres outils plus
spécifiques.
### `buildx`
Docker `buildx` est un plugin qui apporte
[BuildKit](https://github.com/moby/buildkit). Tout en étant compatible avec la
syntaxe des `Dockerfile` existant, BuildKit apporte une gestion concurrente des
nœuds de construction : très utile lorsque l'on construit une image pour
plusieurs architectures.
#### Installation Windows et MacOS {-}
Avec Docker Desktop, le plugin est déjà installé, vous n'avez aucune action
supplémentaire à effectuer, vous pouvez commencer à l'utiliser.
#### Installation Linux {-}
En fonction de la méthode d'installation que vous avez suivie, vous avez
peut-être déjà le plugin installé. Si vous n'avez pas d'erreur en exécutant
`docker buildx`, mais que vous voyez l'aide de la commande, c'est bon. Sinon,
vous pouvez l'installer comme ceci :
<div lang="en-US">
```
mkdir -p ~/.docker/cli-plugins
curl https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64 \
-L -s -S -o ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.docker/cli-plugins/docker-buildx
```
</div>
#### Utilisation\
Nous pouvons réutiliser le `Dockerfile` que vous avez écrit pour `youp0m`, en
remplaçant simplement la ligne de `docker build` par celle-ci :
<div lang="en-US">
```
docker buildx build .
```
</div>
Nous ne rentrerons pas plus dans les détails de cette nouvelle commande, mais
sachez qu'on la retrouve particulièrement fréquemment dans les *GitHub
Actions* : <https://github.com/marketplace/actions/docker-setup-buildx>
#### Changer la syntaxe de nos `Dockerfile`\
Parfois on peut se sentir un peu frustré par la syntaxe des `Dockerfile` ou par
son manque d'évolutivité. Avec BuildKit, il est possible de préciser un parseur
à utiliser pour l'évaluation de la syntaxe du `Dockerfile`. Les parseurs
(*frontend* dans la documentation en anglais) sont des images Docker, on
indique leur nom dans un commentaire au tout début du fichier :
<div lang="en-US">
```dockerfile
# syntax=docker/dockerfile:1.2
FROM ubuntu
RUN apt-get update && apt-get install gimp
```
</div>
La possibilité d'avoir plusieurs implémentations de `Dockerfile` apporte pas mal
d'avantages :
- La version de l'image *frontend* est systématiquement comparée en ligne au
début du parsing du `Dockerfile`, ce qui assure la récupération des derniers
correctifs/versions, sans nécessiter une mise à jour du *daemon* Docker.
- On s'assure que chaque développeur/utilisateur utilise la même
implémentation, avec la même version.
- L'évolution de la syntaxe peut être plus souple car elle ne dépend plus de la
version de Docker installée, mais de la version déclarée dans le
`Dockerfile`.
- On peut même créer de nouvelles syntaxes facilement.
Les images de parseur sont chargées, à partir d'un fichier lisible et
compréhensible par un humain, de créer une représentation intermédiaire
(*LLB*). Cette représentation intermédiaire se compose d'une liste d'opérations
basiques (*ExecOp*, *CacheOp*, *SecretOp*, *SourceOp*, *CopyOp*, ...).
N'hésitez pas à jeter un œil aux autres langages de `Dockerfile` existants,
notamment :
- [Gockerfile](https://github.com/po3rin/gockerfile) : bien que dépassé faute
de mise à jour, cette implémentation est très simple à comprendre : elle a
pour but de créer une image contenant un unique binaire Go, à partir du nom
de son dépôt.
- [hlb](https://openllb.github.io/hlb/) : une syntaxe prometteuse, plus proche
pour les développeurs.
- [Earthly](https://earthly.dev/) : qui tend à regrouper `Makefile`,
`Dockerfile`, et autres scripts de CI et de tests.
### Des images sans Docker
Il est aussi possible de se passer complètement de Docker. La plupart des
outils qui sont capables de générer des images de machines virtuelles, sont
aussi capable de générer des images Docker. Citons notamment :
- [Hashicorp Packer](https://www.packer.io/docs/builders/docker)
- [Nix et Guix](https://nix.dev/tutorials/building-and-running-docker-images)
- [Kubler](https://github.com/edannenberg/kubler)
- et bien d'autres.
### Exercice {-}
Faites en sorte que le `Dockerfile` que vous avez créé pour `youp0m` indique un
`frontend` BuildKit à utiliser, tout en restant compatible avec la syntaxe du
`docker build` classique.

View File

@ -9,7 +9,7 @@ Projet
Avec l'aide d'un `Dockerfile` *multi-stage*, réalisez l'image la plus petite
possible (partant d'un `FROM scratch`{.dockerfile}), qui permette d'utiliser la
[page de compte à rebours](https://virli.nemunai.re/countdown.html) avec cette
configuration pour nginx :
configuration pour nginx :
<div lang="en-US">
```conf
@ -35,7 +35,7 @@ http {
Vous pouvez envisager dans un premier temps d'extraire de l'image `nginx`, le
binaire `nginx` lui-même et observer les différents problèmes. Vous pourrez
ensuite par exemple envisager de compiler `nginx` (vous trouverez les sources
du projet : <http://nginx.org/download>).
du projet : <http://nginx.org/download>).
Dans tous les cas, votre `Dockerfile` devra être facilement maintenable
(notamment en cas de nouvelle version du serveur web), et vous devrez apporter
@ -79,7 +79,7 @@ Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Voici une arborescence type (vous pourriez avoir des fichiers
supplémentaires) :
supplémentaires) :
<div lang="en-US">
```

View File

@ -13,7 +13,7 @@ abstract: |
Tous les éléments de ce TP (exercices et projet) sont à rendre à
<virli@nemunai.re> au plus tard le mercredi 23 octobre 2019 à 13
h 42. Consultez la dernière section de chaque partie pour plus
d'information sur les éléments à rendre.
d'informations sur les éléments à rendre.
En tant que personnes sensibilisées à la sécurité des échanges
électroniques, vous devrez m'envoyer vos rendus signés avec votre

View File

@ -0,0 +1,12 @@
\newpage
Construire des images
=====================
Jusqu'à maintenant, nous avons profité des images présentes sur les registres
pour utiliser Docker. Sur ces registres, on trouve d'ailleurs non seulement des
images officielles proposées directement par les éditeurs (`nginx`, `mysql`,
...), mais aussi des images conçues par les utilisateurs pour leurs propres
besoins.
Et si nous aussi, nous construisions nos propres images ? Ça vous dit ?