Applications multi-containers

Besoin

Pour concevoir et déployer des applications fondées sur plusieurs micro-services :

  • BD

  • NoSQL (Mongo etc.)

  • Applications

  • APIs

de nouveaux besoins apparaissent :

  • nécessité de communiquer entre containers

  • possibilité de créer des réseaux ad-hoc mais pas très facile à manipuler

  • besoin de pouvoir décrire dans une syntaxe simplifiée un système de containers - avec des images prédéfinies - ou spécifiées dans de Dockerfiles - communicant naturellement entre elles

Solution : docker-compose

La commande docker-compose repose sur un fichier docker-compose.yml, écrit au format YAML.

Format YAML

  • json en moins verbeux

  • plus lisible, avec indentation (2 caractères décalage suffisent)

  • bcp de fichiers de conf utilisent ce format

  • cf exemples

docker ou docker-compose ?

Créons un conteneur nginx avec la commande Docker :

docker run --name web -d -p 8000:80 nginx:alpine

Puis allez visiter http://localhost:8000

On peut vouloir monter un volume dans notre container pour publier une page de notre cru via nginx :

  • créer un dossier de base nommé compose-nginx

  • créer dedans dossier app contenant un fichier index.html :

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Nginx Docker</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<section class="section">
    <div class="container">
    <h1 class="Nginx via Docker">
        Hello World
    </h1>
    <p class="subtitle">
       Nginx  à l'intérieur d'un container <strong>Docker</strong>!
    </p>
    </div>
</section>
</body>
</html>
  • Puis relançons la commande en montant app au bon endroit :

docker run --name web -itd -p 8000:80 -v $(pwd)/app:/usr/share/nginx/html nginx:alpine

Nginx avec docker-compose

Faisons plus simple avec une description en yaml :

services:
    web:
        image: nginx:alpine
        ports:
            - "8000:80"
        volumes:
            - ./app:/usr/share/nginx/html

Explications

  • top level : services

  • ici un seul service : web, assuré par nginx

  • le volume local app sera visible dans le container, à l’emplacement /usr/share/nginx/html

  • le port 8000 de l’hôte sera redirigé vers le port 80 du container

  • Lancement : docker compose up -d en mode detached

Questions

  • Peut-on changer en direct le contenu du fichier index.html du dossier app pendant que le conteneur tourne ?

  • comment lister ce qui tourne ?

  • Quels volumes sont montés ?

  • Comment tout arrêter ?

Services

On peut évidemment placer de multiples services dans un docker-compose.

  • les différents services sont décrits avec le même niveau d’indentation.

  • les services peuvent se décrire avec une image préfabriquée (Dockerhub) * avec d’éventuels fichiers de configuration * avec d’éventuels paramètres

  • les services peuvent également faire référence à des images fabriquées avec des Dockerfile spécifiques

principales commandes docker-compose

Commandes

Utilisation

docker-compose build

build

docker-compose up

lancer l’app

docker-compose up -d

lancer en arrière plan

docker-compose ps

lister les containers de l’app

docker-compose logs nginx

visualiser les logs du container nginx

docker-compose pause

faire une pause en gardant les containers en l’état

docker-compose unpause

arrêter la pause

docker-compose stop

arrêter l’application en gardant les données associées

docker-compose down

arrêter l’application en enlevant les containers, réseaux et volumes associés

docker-compose down --rmi all

grand ménage ! (Attention données, etc.)

Testez-les !

Portainer

../_images/portainer.png

Portainer est un outil web qui permet de gérer les conteneurs Docker et Docker Compose. Il permet de gérer les conteneurs, les images, les volumes, les réseaux, les stacks etc. Il est disponible sous forme d’image Docker et peut donc s’installer via Docker ! On peut par exemple le configurer et le lancer à partir du docker-compose suivant (linux ou mac) :

services:
portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    volumes:
        - /etc/localtime:/etc/localtime:ro
        - /var/run/docker.sock:/var/run/docker.sock:ro
        - ./portainer-data:/data
    ports:
        - 9000:9000

On lance la commande suivante pour lancer le service portainer :

$ docker compose up -d

L’interface web est accessible via l’adresse suivante : http://localhost:9000. Lors du premier lancement, un assistant de configuration est lancé pour créer un compte administrateur, puis choisir l’option « local » pour la connexion à Docker. Il faut nommer le container et puis cliquer sur « connect ». Vous pouvez ensuite visualiser les conteneurs Docker et Docker Compose lancés sur la machine ainsi que leurs environments, volumes, ports, etc. La documentation est disponible ici : https://docs.portainer.io

Essayez le :

  • Visualisez les logs de conteneurs

  • Créez une application à l’aide d’un docker-compose que vous déposerez dans la section « stacks ».

  • Définissez des variables d’environnement pour votre stack.

Postgresql avec adminer

Une première application muti-container où l’on se contente d’images prédéfinies.

Le docker-compose

# Use postgres/example user/password credentials
services:
    db:
        image: postgres
        restart: always
        environment:
          POSTGRES_PASSWORD: example

    adminer:
        image: adminer
        restart: always
        ports:
          - 8080:8080
  • Faites un schéma représentant ce docker compose (un simple rectangle pour chaque service, le tout dans un rectangle représentant l’hôte ; représentez aussi les ports, etc.)

  • Placez le dans un répertoire séparé et testez ! (L’utilisateur dans l’interface adminer sera “postgres” et le mot de passe “example”)

  • Quels sont les services ?

  • A quoi correspondent les directives restart: always et environment ?

Exemple multi-containers avec Dockerfile : Flask et Redis

  • Exemple classique

  • Redis est un système clef/valeur efficace dans le Cloud

  • Flask est un micro-framework Python pour développer simplement des app Web

  • Tout le code de Flask est dans un seul fichier run.py

  • Créer un répertoire compose-flask-redis contenant le fichier suivant :

Le fichier run.py

from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host='redis', port=6379)

@app.route('/')
def hello():
    count = redis.incr('hits')
    return 'Hello for the {} time !\n'.format(count)

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug = True)

Conteneuriser ce service

  • installer les dépendances

  • lancer l’application automatiquement

Les dépendances sont gérées en Python à l’aide d’un fichier requirements.txt qui peut être utilisé pour créer un virtualenv ou un conteneur.

A minima :

flask
redis

(on peut préciser des versions aussi !)

On installe les requirements avec la commande : pip install -r requirements.txt

  • Écrire le Dockerfile pour le service Flask en partant de l’image python:3.11-slim

  • Ajouter le contenu du dossier courant au dossier /app du conteneur : ADD . /app

  • puis choisir /app comme répertoire de travail

  • installer les dépendances

  • exposer le port 5000

  • lancer run.py

  • testez !

le docker compose avec le service Redis

Ajouter à présent un docker-compose.yml dans votre dossier dont voici les éléments :

  • Redis étant une image standard on peut directement l’invoquer dans le docker-compose contenant deux services :

redis:
  image: "redis"
  • le service Flask étant décrit par

web:
    build: .
    ports:
    - "4000:5000"
  • Ecrivez le docker-compose correspondant

  • puis build et lancement

  • docker compose build

  • docker compose up -d

Visitez : http://127.0.0.1:4000

Observation de l’app avec les outils Docker

  • docker compose ps

  • docker compose logs ...

  • Quelles sont les tailles des images utilisées ?

  • Lister les réseaux ? Les volumes ?

  • Comment lancer un terminal interactif sur le container Flask ?

  • Comment arrêter tout ?

  • Faire le grand ménage ?

Améliorations du Dockerfile

  • On peut définir des variables d’environnement dans FLASK : FLASK_DEBUG à la valeur True si on souhaite être en mode DEBUG et FLASK_APP avec le nom de l’app à lancer.

  • puis lancer l’app avec la directive : flask run

Effectuez les petites modifs correspondantes. Tester !

Réduction des images

  • essayez les images python:3.11-alpine et python:3.11

  • comparez les tailles et les temps de build et de lancement

Fichier .env

De manière à ne pas versionner les données sensibles, on peut créer un fichier .env dans le dossier du docker-compose. Ce fichier a la forme suivante :

POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
...

On accède dans le docker-compose.yml avec la syntaxe : ${POSTGRES_USER} . Par exemple :

services:
    db:
        image: postgres
        environment:
            POSTGRES_USER: ${POSTGRES_USER}
            POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Portainer

Déployez le docker compose précédent en passant par portainer.