# Docker
Notes globales et cheatsheets pour docker et docker-compose.
[TOC]
###### tags: `documentation`
## Lexique
https://docs.microsoft.com/fr-fr/dotnet/architecture/containerized-lifecycle/docker-terminology
**Dockerfile**: Fichier de configuration/paramétrage d'une image (ce qui pourrait s'approcher d'une class). Fichier texte qui contient des instructions pour la génération d’une image de l’ancrage. C’est comme un script de commandes, la première ligne indique l’image de base avec laquelle commencer, puis suivent les instructions pour installer les programmes requis, copier les fichiers, etc. jusqu’à obtenir l’environnement de travail dont vous avez besoin.
**Image conteneur (container image)**: package de toutes les dépendances et informations nécessaires pour créer un conteneur. Une image inclut toutes les dépendances (notamment les frameworks) ainsi que la configuration de déploiement et d’exécution à utiliser par le runtime du conteneur. En règle générale, une image est dérivée de plusieurs images de base qui sont empilées en couches pour former le système de fichiers du conteneur. Une image est immuable une fois qu’elle a été créée.
**Création (build)**: action de créer une image conteneur sur la base des informations et du contexte fournis par le fichier Dockerfile associé, plus des fichiers supplémentaires dans le dossier où l’image est créée. Vous pouvez créer des images à l’aide de la commande Dockr suivante:
```bash
docker build
```
**Conteneur (container)**: instance d’une image Docker. Un conteneur représente l’exécution d’une application, d’un processus ou d’un service. Il renferme une image Docker, un environnement d’exécution et un ensemble standard d’instructions. Pour mettre un service à l’échelle, vous créez plusieurs instances d’un conteneur à partir de la même image. Cela peut également être fait par un traitement par lots, qui passe des paramètres différents à chaque instance.
**Volumes**: offre un système de fichiers accessible en écriture que le conteneur peut utiliser. Dans la mesure où les images sont en lecture seule, mais que la plupart des programmes ont besoin d’écrire dans le système de fichiers, les volumes ajoutent une couche accessible en écriture, par-dessus l’image de conteneur, afin que les programmes aient accès à un système de fichiers accessible en écriture. Le programme ne sait pas qu’il accède à un système de fichiers en couches, il s’agit simplement du système de fichiers normal. Les volumes résident dans le système hôte et sont gérés par Docker.
**Balise (tag)**: marque ou étiquette que vous pouvez appliquer aux images pour identifier les différentes images ou versions de l’image initiale (selon le numéro de version de l’environnement cible).
**Dépôt (repository)**: collection d’images Docker associées, identifiées par une balise qui indique la version de chaque image. Certains dépôts contiennent plusieurs variantes d’une image spécifique, telles qu’une image contenant des kits de développement logiciel (lourd), une image contenant uniquement des runtimes (plus légère), etc. Ces variantes peuvent être marquées avec des balises. Un dépôt peut contenir des variantes de plateforme, comme une image Linux et une image Windows.
**Différence entre image et repository:** https://stackoverflow.com/a/46105265
**Registre (registry)**: service qui fournit l’accès aux dépôts. Le registre par défaut utilisé pour la plupart des images publiques est Docker Hub (propriété de l’organisation Docker). Un registre contient généralement des dépôts de plusieurs équipes. Les entreprises utilisent souvent des registres privés pour stocker et gérer les images qu’elles ont créées. Azure Container Registry est un autre exemple de registre.
**Docker Hub**: registre public dans lequel vous pouvez charger et manipuler des images. Docker Hub fournit un hébergement d’images Docker, des registres publics ou privés, des déclencheurs de build et des webhooks, et l’intégration avec GitHub et Bitbucket.
**Compose**: outil en ligne de commande et format de fichier YAML fournissant des métadonnées pour la définition et l’exécution d’applications multiconteneurs. Vous définissez une application basée sur plusieurs images avec un ou plusieurs fichiers .yml qui peuvent remplacer les valeurs en fonction de l’environnement. Après avoir créé les définitions, vous déployez l’application multiconteneur entière à l’aide d’une seule commande (docker-compose up) qui crée un conteneur par image sur l’hôte Docker.
**Cluster**: collection d’hôtes Docker exposés en tant qu’hôte Docker virtuel unique, ce qui permet la mise à l’échelle de l’application en fonction du nombre d’instances des services répartis entre les différents hôtes au sein du cluster. Vous pouvez créer des clusters Docker avec Kubernetes, Azure Service Fabric, Docker Swarm et Mesosphere DC/OS.
**Orchestrator**: outil qui simplifie la gestion des clusters et des hôtes Docker. Les orchestrateurs vous permettent de gérer les images, les conteneurs et les hôtes à l’aide d’une interface de ligne de commande (CLI) ou d’une interface graphique utilisateur. Vous pouvez gérer la mise en réseau des conteneurs, les configurations, l’équilibrage de charge, la découverte des services, la haute disponibilité, la configuration des hôtes Docker, et bien plus encore. Un orchestrateur gère l’exécution, la distribution, la mise à l’échelle et la réparation des charges de travail dans une collection de nœuds. En règle générale, les produits Orchestrator sont les mêmes produits qui fournissent l’infrastructure de cluster, comme Kubernetes et Azure Service Fabric, entre autres offres sur le marché.
## Container & Images
Les containers qu'on va créer seront basés sur des images. On peut créer une image de zéro ou partir d'une image préexistante.
https://hub.docker.com/search
`docker pull chialab/php-dev:5.6-apache` ([see Dockerfile](https://github.com/chialab/docker-php/blob/master/5.6/apache/Dockerfile))
Utiliser une image locale: https://stackoverflow.com/a/46036043
Les containers lancés se basent sur l'image. Les modifications faites **ne sont pas persistées**.
### Commands
* Importer une image/repo:
* `docker pull $(name[:tag|@digest])`
* Lancer une image (se charge aussi de l'importer si elle n'existe pas):
* `docker run debian`
* Avec le terminal ouvert:
* `docker -ti run debian`
* Plus d'infos:
* `docker run --help`
> `--help` fonctionne avec toutes les commandes docker
* Lister les images repository: `docker images || docker image ls`
* Lister les container (lancés ?): `docker ps`
* Se connecter à un container en console tty (plus généralement, sert à lancer une commande sur le container, ici on execute bash avec les options *tty* et *interactive*):
* `docker exec -it [containerId|containerName] /bin/bash`
* Inspecter les changements sur le fs d'un container: `docker diff $(premiers chrs distinctifs du containerId)`
* Créer une nouvelle image depuis les changements d'un container `docker commit $(idContainer) $(repository)` (see `docker images`)
* Exporter une image (préalablement enregistrée) localement: `docker save $(imageName) > path/to/tarFile.tar` (il ne s'agit pas d'un fichier texte, plusieurs mo pour une image)
* Kill un container et supprimer ses volumes: `docker rm -rf $(container name ? id ?)`
## Dockerfile
Documentation de référence: https://docs.docker.com/engine/reference/builder/
Best practices: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
### Commands
* Démarrer un container (créer l'image) basé sur le Dockerfile:
* `docker run [-p "portLocal:portDistant"] [-v /local/path/to/volume:/distant/path] hubUser/dfName`
et en théorie (**todo: à tester**):
* `docker run ./path/to/Dockerfile/dir`
(le startWith `./` a l'air important pour spécifier qu'on attend un fichier local. Je ne pense pas qu'il faille inclure le nom de fichier `Dockerfile`)
## Docker compose
Documentation de référence: https://docs.docker.com/compose/compose-file/
### YAML rappels
```yaml
version: "3.8"
# Add environment variables. You can use either an array or a dictionary. Any boolean values (true, false, yes, no) need to be enclosed in quotes to ensure they are not converted to True or False by the YML parser.
# Environment variables with only a key are resolved to their values on the machine Compose is running on, which can be helpful for secret or host-specific values.
environment:
RACK_ENV: development
SHOW: 'true'
SESSION_SECRET:
# OR
environment:
- RACK_ENV=development
- SHOW=true
- SESSION_SECRET
# Add environment variables from a file. Can be a single value or a list.
# If you have specified a Compose file with docker-compose -f FILE, paths in env_file are relative to the directory that file is in.
# Environment variables declared in the environment section override these values – this holds true even if those values are empty or undefined.
env_file: .env
# OR
env_file:
- ./common.env
- ./apps/web.env
- /opt/runtime_opts.env
services:
web:
build: .
# Express dependency between services. Service dependencies cause the following behaviors:
# - docker-compose up starts services in dependency order. In the following example, db and redis are started before web.
# - docker-compose up SERVICE automatically includes SERVICE’s dependencies. In the example below, docker-compose up web also creates and starts db and redis.
# - docker-compose stop stops services in dependency order. In the following example, web is stopped before db and redis.
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
```
### Variables & environement
Les variables d'environements doivent être passés aux services qui vont les utiliser. Un `Dockerfile` ne peut pas directement déclarer utiliser un `.env`.
**Rappel sur la portée:**

https://vsupalov.com/docker-arg-env-variable-guide/#the-dot-env-file-env
### Commands
* Builds, (re)creates,starts etc:
* `docker-compose build [--no-cache]`
L'option `--no-cache` est utilisée lors du développement afin de ne pas considérer les instructions terminées de la fois précédente comme pouvant être conservées (ex: Une image allant cloner un dépot qui a été mise à jour pour corriger un bug). Lire l'article: https://www.baeldung.com/linux/docker-build-cache
* `docker-compose up [-d]` (-d: mode détaché)
* [Lancer une commande sur un container](https://docs.docker.com/compose/reference/run/): `docker-compose` va démarrer le service (associé dans le fichier de conf), puis lancer la commande. Il est possible de supprimer le container juste après ou de prendre la main à la suite, en fonction des arguments.
* `docker-compose run [docer-compose run options] [-w /path/to/workdir] php-dev bin/console cache:clear [symfony console options] [--env=dev]` (`php-dev` correspond à l'identifiant de service dans le fichier `docker-compose.yml`)
* [Suppression d'un volume](https://docs.docker.com/storage/volumes/#remove-volumes): Il faut savoir que docker va essayer de conserver les volumes, même si le container associé change ou est supprimé. Idem lorsqu'on change la version d'un container (ça m'est arrivé avec mongodb), docker peut travailler avec l'ancien volume. **Ce fonctionnement est valable avec les volumes anonymes qui sont utilisés lorsqu'on en définit aucun dans la conf.**
Cumulés, ces points créent un contexte particulièrement accidentogène, la commande se révèle utile lors de ces manips.
* <span id="rm-volume-cmd"></span>`docker-compose rm -v $(containerName)`
## Démarrage d'une stack php/apache/mongo
On va voir petit à petit comment monter une stack en commençant par de simples images jusqu'au `docker-compose` final.
On va se servir de Planning pour cela.
### Container PHP/Apache
**NB**: Les explication de ce chapitre concernent le fichier `app-ca_php5.6_apache/Dockerfile`.
On va partir sur cette image:
`docker pull chialab/php-dev:5.6-apache` ([see Dockerfile](https://github.com/chialab/docker-php/blob/master/5.6/apache/Dockerfile))
L'image telle quelle ne peut pas être entièrement configurée via `docker-compose`. On va passer par un Dockerfile dans un dossier dédié à cette image.
Ce dossier accueillera tout ce qu'il faut pour faire fonctionner le container selons nos besoins (fichiers de conf qui seront envoyés sur le container lors du run)
#### Préparation des configurations
Elles seront envoyés sur le serveur grâce au Dockerfile.
Pour apache, on va indiquer au serveur de servir les fichiers depuis le dossier `/var/www/app-ca/web`. On va donc redéfinir un virtual host et remplacer celui par défaut.
```apache
# Local: ./conf/apache-virtualhost.conf
# Container: /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
ServerAdmin support@hopimedical.com
DocumentRoot /var/www/app-ca/web
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/app-ca/web/>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ServerSignature Off
</VirtualHost>
```
Au niveau de PHP, on va effectuer quelques ajouts dans le `php.ini`. Il suffit pour cela d'ajouter un nouveau fichier dans `/usr/local/etc/php/conf.d/`.
```ini
; Local: ./conf/php.ini
; Container: /usr/local/etc/php/conf.d/app-ca-php.ini
; Une timezone par défaut doit être définie
date.timezone = Europe/Paris
; Intégration de l'extension mongo
extension=mongo.so
```
Concernant l'extension' `mongo.so`, elle n'est pas encore disponible mais nous allons la récupérer grâce à PECL. D'après les tests, il est possible de lancher déjà apache dans cet état sans qu'il ne bloque à cause de cette extension absente.
**TODO: Dockerfile**
#### Installation des packages nécessaires à la solution
Nous allons avoir besoin des packages suivants (après update):
* L'extension mongo pour PHP (via `pecl install mongo`)
* Il est nécessaire d'avoir le package libssl-dev pour la compiler (via `apt install libssl-dev`)
* mercurial
Afin de ne pas bloquer le build, on va utiliser la commande `yes ''` afin de répondre automatiquement aux questions par la chaîne proposé en argument. Ici, une chaîne vide va se contenter de simuler un appui sur `[Entrée]`, acceptant ainsi les propositions par défaut.
Nous allons tout faire dans un seul `RUN`.
```bash
RUN apt-get update \
&& yes '' | apt-get install libssl-dev mercurial \
&& yes '' | pecl install mongo
```
#### Surcharge du php.ini
Ajout de `extension=mongo.so`
Définition du timeZone.
Voir le fichier `app-ca_php5.6_apache/conf/php.ini` dans le projet. Il est commenté pour l'occasion.
À noter d'ailleurs que les commentaires des `php.ini` via le caractère `#` sont dépréciés et provoquent des warnings.
Il faut utiliser le `;`.
#### Récupération du code
Il s'agit d'une application en Symfony étalée sur deux repositories mercurial.
1. Une application général. Le squelette Symfony avec un fichier `composer.lock` prêt pour une installation, le `AppKernel.php` précomplété etc. Ce repository va servir d'hôte pour faire fonctionner les sources du bundle sur lesquel on travaille.
2. Le bundle à proprement parler. Il va être placé dans le dossier src et sera récupéré via mercurial, lui aussi, après avoir cloné l'application.
* Nous procédons ainsi car ce bundle est utilisé dans différents contextes et est déjà versionné tel quel. De manière générale, il n'est pas possible de versionner une app symfony globale par bundle. L'idée étant aussi de se servir de ces recherches avec d'autres bundles. Cette organisation permettra de conserver la cohérence des autres bundles.
Pour ce qui est de la récupération du code, plusieurs possibilités s'offrent à nous:
* Une copie de fichiers
* Un volume partagé
* Une *"installation"* automatique lors du build.
C'est cette dernière option qui va être privilégiée.
Un article intéressant à ce sujet: https://dzone.com/articles/clone-code-into-containers-how
Il va plus loin et aborde le sujet de git(hub), des token OAuth ainsi que de l'utilisation d'une clé SSH.
Nous disposons d'une plateforme d'hébergement de dépôts privés (rhodecode). Il est nécessaire de se connecter et d'avoir les droits pour pouvoir pull ou push du code sur un dépôt.
Un utilisateur `docker` a donc été créé avec des droits en lecture sur les dépôts.
Il nous faut donc trouver un moyen de clôner ces dépôts sur le container avec les bonnes autorisations.
Afin de ne pas envoyer ces informations sensibles partout (commit des images docker par exemple) on va considérer que l'utilisateur doit les indiquer.
Nous allons utiliser pour cela le fichier [`.env`](https://docs.docker.com/compose/env-file/) à la racine du projet.
Ses valeurs pourront être utilisées telles quelles par `docker-compose.yml`
```yaml
# Fichier "".env.dist" permettant à l'utilisateur de savoir quoi indiquer dans le .env qu'il va créer
HG_USER=
HG_PASSWORD=
# Fichier .env rempli par l'utilisateur
HG_USER=username
HG_PASSWORD=pass
# docker-compose.yml
#...
services:
php-dev:
build:
context: app_php_apache
args:
HG_LOGIN: ${HG_USER}
HG_PASSWD: ${HG_PASSWORD}
#...
# Enfin, dans ./app_php_apache/Dockerfile:
# Déclaration des arguments pour cette image
ARG HG_LOGIN
ARG HG_PASSWD
# Utilisation dans une commande
RUN hg clone "https://${HG_LOGIN}:${HG_PASSWORD}@mercurai-server/project" /var/www/web-project
```
**Conclusion:** Il s'agit là d'un moyen plutôt simple, mais qui a l'air efficace étant donné que les instructions `RUN` ne sont pas loggées sur le container. Je pense qu'avec le temps et un plus grand recul sur le sujet, cette solution sera optimisée.
Il faudra sans doute passer par une connexion via certificat (comme avec avec un projet hébergé sur github par exemple). Un article traite du sujet et de la manière de sécuriser cela: https://janakerman.co.uk/docker-git-clone/
### Container Mongo
Celui-ci est très simple de base. On aurait juste besoin de charger une image dans le `docker-compose`.
```yaml
version: "3.8"
services:
mongodb:
image: mongo:3.2 # On utilise une version spécifique pour nos besoins
ports: # On pourrait potentiellement se passer des ports. C'est seulement pour y accéder de l'extérieur
- "27017:27017"
```
Si on ne spécifie pas de volume, docker va créer un volume anonyme qui peut suffire dans ce contexte.
Il faut juste penser à [le supprimer](#rm-volume-cmd) si on change de version de mongo et que les données ne sont pas compatibles.
#### Notes sur l'import/export via commands
Afin de ne pas avoir trop de fichiers à échanger avec le container, on va opter pour la création d'une archive contenant la ou les db nominales.
Cela permet d'échanger un seul fichier avec le container qui contiendra tout.
L'inconvénient est qu'il n'est plus possible de changer le nom des db ou des collections lors de l'import (du restore).
> **Précision:** L'option [`--archive`](https://docs.mongodb.com/manual/reference/program/mongodump/#cmdoption-mongodump-db) est disponible à partir de la version 3.2 uniquement. Cela signifie que les commandes mongodump et mongorestore doivent être à cette version.
> Par contre, cela fonctionne malgré tout avec un serveur plus ancien (testé avec un serveur en 2.6).
L'export a été fait avec Studio3T via:
* rightclick sur la db
* "Export collections"
* "mongodump" / Next
* "BSON - mongodump archive" (sans la case gzip, les données de test ne pèsent pas trop lourd) / Next
* Next / Start Export
Via une commande, on peut supposer que cela correspond à:
```
mongodump --db=<dbName> [--gzip] --archive=/path/to/archive
```
(si `[--db](https://docs.mongodb.com/manual/reference/program/mongodump/#cmdoption-mongodump-db)` n'est pas spécifié, un export de toutes les DB sera effectué)
Commande permettant d'effectuer l'import du fichier:
```
# C'est ici qu'il n'est pas possible de spécifier de db. Et ça ne dépend pas non plus du nom du fichier.
mongorestore.exe --archive ./planningdemo.archive
```
Si ***VRAIMENT*** le nom de la db doit être changé au niveau de l'archive, il est possible d'envisager un replacement binaire du terme via [`sed` (`bbe`)](https://stackoverflow.com/a/4999140). Il n'y a pas l'air d'y avoir de contraintes de taille fixe type index/offset sur addresse mémoire etc.
## Registry
Ca pourrait être pratique, à l'avenir, d'avoir notre propre serveur d'images Docker.
Surtout si certaines nécessitent d'y associer des données qu'il n'est pas possible de faire sortir.
Docker propose de monter un serveur registry directement via une image.
C'est ici que ça se passe: https://docs.docker.com/registry/deploying/
Un sujet à approfondir (sur VM ESXi par exemple, d'autant qu'une nouvelle machine va être mise en place pour du build).
## Autres liens et ressources
[Notary](https://docs.docker.com/notary/getting_started/): Outil de publication et de gestion de collections de contenus sécurisés. Les éditeurs peuvent signer numériquement les collections et les utilisateurs peuvent vérifier l'intégrité et l'origine du contenu.
[Play with Docker](https://www.docker.com/play-with-docker): Permet une prise en main de Docker directement depuis le web. À tester, ça pourrait être utile pour faire des tests rapides ou s'ils proposent des tutos interactifs.