Mon premier conteneur :)

Aujourd’hui, après avoir parcouru la documentation de Docker, j’ai plein d’idées d’infrastructure qui me viennent en tête 🙂

Un peu en vrac, j’aimerai monter une stack sur mon poste de développement pour une application web en PHP.

Mais avant de partir tout azimut sur quelque chose de compliquer, on va simplement prendre le temps de ce faire un petit conteneur qui fera tourner un serveur web (Apache ou NGINX, j’ai pas encore décidé ^^)

Ce qui m’intéresse de voir, c’est la manipulation du conteneur en ligne de commande, le monitoring de celui-ci, etc…

J’ai déjà vu que sur le hub de Docker, plein de conteneur faisant fonctionner un serveur web sont disponibles.
Le but étant l’apprentissage, on va partir « from scratch » 🙂

Pour commence, voici le DockerFile :

FROM ubuntu:18.04 AS bonjourdocker
RUN apt-get update -y
RUN apt-get install apache2 -y
EXPOSE 80
CMD service apache2 start

Ensuite on se positionne dans le dossier contenant le Dockerfile en ligne de commande et on lance le build :

docker build -t ajegu/bonjourdocker:1.0 .

Voici l’image construite :

docker image ls
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
ajegu/bonjourdocker   1.0                 04ea087ad669        5 seconds ago       215MB

Maintenant, on va la démarrer dans un conteneur :

docker run -d -p 8080:80 ajegu/bonjourdocker:1.0

Aie Aie! Ca casse sévèrement ! 🙁

CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                     PORTS               NAMES
1080f04847e2        ajegu/bonjourdocker:1.0   "/bin/sh -c 'service…"   5 seconds ago       Exited (0) 4 seconds ago                       angry_goldstine

Le conteneur ne démarre pas, voyons le log :

docker logs 1080f04847e2
 * Starting Apache httpd web server apache2
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
 *

Apache n’arrive pas à démarrer car il ne peut pas résoudre le nom de domain du conteneur.
Pour corriger cela, il faut ajouter une entrée dans le fichier hosts du conteneur.
On peut faire cela au démarrage du conteneur : 😉

docker run -d -p 8080:80 -h bonjourdocker.domain.local ajegu/bonjourdocker:1.0

stackoverflow

On check le log :

 * Starting Apache httpd web server apache2
 * 

Cool :), Apache a bien démarrer !
Par contre, un autre problème apparaît, mon nouveau conteneur est toujours à l’arrêt 👿

CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                     PORTS               NAMES
1ee10235ef40        ajegu/bonjourdocker:1.0   "/bin/sh -c 'service…"   7 seconds ago       Exited (0) 7 seconds ago                       distracted_sammet
1080f04847e2        ajegu/bonjourdocker:1.0   "/bin/sh -c 'service…"   8 minutes ago       Exited (0) 8 minutes ago                       angry_goldstine

L’histoire est la suivante:
Docker a besoin qu’un processus s’exécute au premier plan pour que le conteneur reste dans un état actif.

Pour cela, il faut exécuter Apache avec l’option -DFOREGROUND au démarrage du conteneur.

Voici le Dockerfile :

FROM ubuntu:18.04 AS bonjourdocker
RUN apt-get update -y
RUN apt-get install apache2 -y
CMD apachectl -DFOREGROUND
EXPOSE 80

stackoverflow

On relance un build, on exécute de nouveau le conteneur à partir de la nouvelle image :

docker build -t ajegu/bonjourdocker:1.1 .
docker run -d -p 8080:80 -h bonjourdocker.domain.local ajegu/bonjourdocker:1.1

Tout est ok, on peut voir que le serveur Apache installé sur le conteneur fonctionne correctement en allant sur l’url localhost:8080 :mrgreen:

Le bilan de cette première expérience serai la suivante :
– Se méfier du réseau, Docker applique son propre système de routage et cela peut perturber les applications qui en dépendent.
– Toujours spécifier un process qui s’exécutera en premier plan pour que le conteneur reste en vie

Maintenant que le conteneur tourne, j’ai bien envi d’aller voir 2-3 trucs :
– me connecter avec shell au conteneur
– lancer un « top » pour voir les process qui tourne dessus
– voir les logs Apache

Pour se connecter au conteneur :

docker exec -it ID_CONTENEUR bash

Aie 🙁 , premier truc qui me va pas quand je fais un top, c’est que je vois le process apache2ctl qui s’exécute en root.
J’aimerai que ce soit l’utilisateur www-data qui l’exécute.

Bon après investigations, j’ai l’impression que les services doivent s’exécuter en root dans les conteneurs.
N’hésitez pas à réagir là dessus en commentaire si je me trompe.

Bon !
Cette démystification de Docker m’a conforté dans mon idée de l’utiliser pour créer une véritable stack de développement.

Je vais la détailler pas à pas dans de futurs articles mais voici ce à quoi je pense :
– Loadbalancer HAProxy pour utiliser des urls pour joindre mes applis (ex: app1.local)
– Serveur web Apache
– Serveur web NGINX
C’est bien de pouvoir faire le switch de l’un à l’autre rapidement en fonction des projets
– Plusieurs serveurs PHP (5.6, 7.0, 7.1, 7.2)
– MySQL
– PostgreSQL
– Kibana pour recevoir les logs des applis mais aussi des services nginx, apache, php, mysql, etc…
– Jenkins
– Gitlabs
– SonarQube

L’idée s’est de faire fonctionner en symbiose !

Quelques cas d’utilisation en vrac :
Pouvoir check mes logs sur Kibana, switcher de Postgre à MySQL sur une app Symfony, idem pour les version de php et de serveur web, lancer mes tests unitaires, fonctionnels avec Jenkins, contrôler la qualité du code avec Sonar, etc…

Fonctionnement du Dockerfile

Présentation

Le Dockerfile définit comment votre environnement va fonctionner à l’intérieur du conteneur.

L’accès aux ressources comme les interfaces réseaux, les espaces disque sont virtualisées à l’intérieur de l’environnement, ce qui les isolent du reste du système.

Il est nécessaire de faire le mapping des ports pour y avoir accès depuis l’extérieur, de spécifier les fichiers qui seront copiés dans le conteneur, etc…

Après avoir fait cela, on peut supposer que notre application fonctionnera de la même manière, peut importe le système qui exécutera le conteneur.

Usage

$ docker build -f /path/to/a/Dockerfile .

Le « . » spécifie l’emplacement des répertoires/fichiers qui seront défini comme « contexte » pour le Dockerfile.

Il est possible de tester la syntaxe du Dockerfile

docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD

Les instructions du Dockerfile suivent la syntaxe suivante :

# Comment
INSTRUCTION arguments

Instructions

FROM : Spécifie l’image de base avec laquelle le conteneur sera construit

FROM <image>[:<tag>] [AS <name>]

ARG : Définit les variables qui peuvent être passer par l’utilisateur au démarrage du conteneur

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}

ENV : Permet de déclarer une variable d’environnement

RUN : Permet l’exécution d’une commande ou d’un script

CMD : Tout comme RUN, elle permet l’exécution d’une commande ou d’un script mais elle ne peut être déclaré qu’une seul fois dans un Dockerfile

LABEL : Ajoute des méta-données à une image

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

EXPOSE : Permet de spécifier à Docker les ports réseaux nécessaires à ouvrir au démarrage du conteneur

ADD : Copie des nouveaux fichiers, dossiers depuis la source spécifiée vers le système de fichier de l’image au chemin indiqué.

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

ADD permet également de récupérer des éléments depuis une URL ou de décompresser une archive.

COPY : Plus strict que ADD, cette instruction est préférée à celle-ci.

ENTRYPOINT : Similaire à l’instruction CMD mais ne peut pas être surchargé au démarrage du conteneur.

VOLUME : Créer un point de montage dans le conteneur

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

USER : Définit l’utilisateur qui sera utilisé pour les instructions RUN, CMD et ENTRYPOINT

    FROM microsoft/windowsservercore
    # Create Windows user in the container
    RUN net user /add patrick
    # Set it for subsequent commands
    USER patrick

WORKDIR : Définit le répertoire de travail pour les instructions RUN, CMD, ENTRYPOINT, COPY et ADD

ONBUILD : Ajoute à l’image un déclencheur qui sera exécuter lorsque l’image sera utilisée comme base de construction d’une autre image.

HEALTHCHECK : Permet de définit une commande qui vérifiera l’état de santé du conteneur lorsqu’il sera en exécution

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

SHELL : Modifie le shell par défaut

Mémo des commandes Docker

## Liste les commandes CLI de Docker
docker
docker container --help

## Affiche la version de Docker et ses infos
docker --version
docker version
docker info

## Exécute une image Docker
docker run hello-world

## Liste des images
docker image ls

## Liste les conteneurs Docker (running, all, all in quiet mode)
docker container ls
docker container ls --all
docker container ls -aq

## Affiche les méta-données d'une image
docker inspect
docker build -t friendlyhello .  # Create image using this directory's Dockerfile
docker run -p 4000:80 friendlyhello  # Run "friendlyname" mapping port 4000 to 80
docker run -d -p 4000:80 friendlyhello         # Same thing, but in detached mode
docker container ls                                # List all running containers
docker container ls -a             # List all containers, even those not running
docker container stop <hash>           # Gracefully stop the specified container
docker container kill <hash>         # Force shutdown of the specified container
docker container rm <hash>        # Remove specified container from this machine
docker container rm $(docker container ls -a -q)         # Remove all containers
docker image ls -a                             # List all images on this machine
docker image rm <image id>            # Remove specified image from this machine
docker image rm $(docker image ls -a -q)   # Remove all images from this machine
docker login             # Log in this CLI session using your Docker credentials
docker tag <image> username/repository:tag  # Tag <image> for upload to registry
docker push username/repository:tag            # Upload tagged image to registry
docker run username/repository:tag                   # Run image from a registry