Skip to content

Latest commit

 

History

History

demo-services-options

Démo sur les différentes options de compose

On repart de l'exemple donné dans Try Docker Compose de la doc officielle.

Créer votre propre projet et préparez-le.

YAML

Utiliser un linter pour détecter les erreurs, par exemple yamllint

yamllint compose.yml

up

docker compose up
docker compose ps
docker volume ls
#Voir quel volume est attaché à un conteneur
docker inspect <id conteneur> | grep Mounts -A 10

Regarder les conteneurs (le nom par défaut : nom du projet-nom du service-d'un identifiant incrémenté)

Si pas de nom de projet, nom du repertoire courant

On voit qu'un volume anonyme a été crée. Il a été crée par redis (Dockerfile de son image contient instruction VOLUME), aller voir le code source de son image (Dockerfile) pour vous en convaincre.

Développement : Watch des sources sans volume

---
services:
  web:
    build: .
    ports:
      - "8000:5000"
    #Plus besoin de ce hack ici
    # volumes: #volume anonyme
    # - .:/code
    develop:
    watch:
      - path: ./
        action: sync
        target: /code
    environment:
      FLASK_DEBUG: "true"
  redis:
    image: "redis:alpine"
#Lancer le projet
docker compose up -d
#Lancer le watch
docker compose watch

Puis modifier les sources, et requêter le serveur web pour tester le reload.

Variables d'environnement

Placer les variables d'environnement dans un fichier externe : plus simple à maintenir, allège le fichier compose, centralise les valeurs.

Créer un fichier d'env .env

Le fichier .env est chargé automatiquement par Docker Compose (comportement par défaut, override dans variable d'env COMPOSE_ENV_FILES)

MA_VARIABLE=FOO
FLASK_DEBUG=false
  web:
    build: .
    ports:
      - "8000:5000"
    # volumes: #volume anonyme
    # - .:/code
    environment:
      FLASK_DEBUG: ${FLASK_DEBUG}
      MA_VARIABLE: ${MA_VARIABLE}

Inspecter variable d'env sur le conteneur :

docker compose up
docker exec <nom> env

docker compose up recrée un conteneur si sa config (compose) change. Si la variable d'environnement interpolée change dans le .env, docker compose le détecte et recrée aussi le conteneur ! Cool.

Modifier le fichier d’environnement .env :

#FLASK_DEBUG=false
FLASK_DEBUG=true

puis relancer le projet :

docker compose up

Observer la reconstruction automatique du conteneur au changement d'une variable d'environnement.

La variable d'env du .env n'est injectée que si elle est utilisée dans le compose ou par le conteneur.

Sur un dépôt distant (remote), on créera un fichier .env.dist avec des configs par défaut. Chaque personne qui pull le dépôt fait une copie locale cp .env.dist .env et définit ses propres variables d’environnement en fonction de ses besoins. Si pousser des modifs : les mettre dans le .env.dist. (partager avec le reste de l'équipe). Sinon, ignorer le .env (local a la machine, au dev) en le mettant dans le .gitignore.

Utiliser docker compose config pour checker l'interpolation correcte des variables d'environnement.

Secrets

Créer un fichier password avec my-secret-password, la valeur à protéger le plus possible :

echo 'my-secret-password' > secret

~~~yaml
services:
  web:
    build: .
    ports:
      - "8000:5000"
    environment:
      FLASK_DEBUG: ${FLASK_DEBUG}
      MA_VARIABLE: ${MA_VARIABLE}
    secrets:
        - my_secret

  redis:
    image: "redis:alpine"
#Nouvelle section
secrets:
    my_secret:
        file: ./password

Modifier le code source :

import time
import os

import redis
from flask import Flask

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

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    # Chemin vers le fichier secret
    secret_file_path = "/run/secrets/my_secret"  # Ce chemin peut varier selon le système d'exploitation

    # Vérifier si le fichier secret existe
    if os.path.exists(secret_file_path):
        # Lire le contenu du fichier secret
        with open(secret_file_path, 'r') as secret_file:
            secret_value = secret_file.read().strip()
    else:
        secret_value = 'not found, sorry'
    return 'Coucou  ! I have seen this {} times. Voici le secret : {}\n'.format(count, secret_value)

Reconstruire l'image :

docker compose build web
docker compose up -d
#Test
curl localhost:8000

Utiliser les profiles (activation/désactivation de services)

Modifier le fichier compose.yaml :

---
services:
  web:
    build: .
    ports:
      - "8000:5000"
    # volumes: #volume anonyme
    # - .:/code
    environment:
      FLASK_DEBUG: ${FLASK_DEBUG}
      MA_VARIABLE: ${MA_VARIABLE}
    # deploy:
    #   mode: replicated
    #   replicas: 3
    secrets:
      - my_secret
    profiles: [frontend]
    develop:
      watch:
        - path: ./
          action: sync
          target: /code

  redis:
    image: "redis:alpine"

Tester :

#Les 2 s'activent (web et redis)
docker compose --profile frontend up
#Que redis
docker compose up

Rediriger les logs

#Rediriger les logs des events dans un fichier en tache de fond
docker compose events > dengine.log &

Override les variables d'environnement de Docker Compose

Voir les variables

Dans un fichier .env a la racine du projet :

COMPOSE_PROFILES=frontend

Gestion des environnements : un seul ou plusieurs fichiers ? Démo d'une méthode basée sur le merge

Nous utilisons la gestion des variables d'environnement basée sur le merge.

Créer un fichier compose.dev.yaml et compose.prod.yaml en plus du fichier de base compose.yaml.

#compose.dev.yaml
---
services:
  web:
    environment:
      MA_VARIABLE: ${MA_VARIABLE_DEV}
#compose.prod.yaml
---
services:
  web:
    environment:
      MA_VARIABLE: ${MA_VARIABLE_PROD}

Et toujours un de base compose.yaml (config par défaut, a ne jamais utiliser seul)

#compose.yaml
---
services:
  web:
    build: .
    ports:
      - "8000:5000"
    environment:
      FLASK_DEBUG: ${FLASK_DEBUG}
      MA_VARIABLE: ${MA_VARIABLE_PROD}
    secrets:
      - my_secret
    profiles: [frontend]
    develop:
      watch:
        - path: ./
          action: sync
          target: /code
  redis:
    image: "redis:alpine"
    profiles: [debug]
secrets:
  my_secret:
    file: ./password

Remarque : ici je n'ai qu'un fichier d'environnement (pour les besoins de la démo), mais il en faudrait deux : prod.env, dev.env, et chaque fichier compose ferait référence à son propre fichier d’environnement sous l'attribut env_file.

#Lancer en env de prod
docker compose -f compose.yml -f compose.prod.yml up -d
docker exec -it composetest-web-1 env
#Lancer en env de dev
docker compose -f compose.yml -f compose.dev.yml up -d
docker exec -it composetest-web-1 env

Attention, l'ordre est important : docker compose -f compose.prod.yml -f compose.yml est différent de docker compose -f compose.yml -f compose.prod.yml

Le mieux c'est d'essayer vous-même les différentes méthodes (héritage, include, merge, combinaison des mécanismes,...) :

  • Faites un mini projet minimal pour chaque méthode (avec 2 ou 3 services par exemple)
  • Simuler la variation d'un paramètre d'environnement. Faites les modifs nécessaires (port, path, etc.)
  • Regarder ce que vous devez modifier et quels fichiers vous allez modifier
  • Une fois terminés, regardez ce que vous allez push comme changements sur un depot (utiliser git, ou un outil de diff pour vous assister)
  • Regarder ce que vous allez pull comme changements depuis le dépôt;
  • Regardez les erreurs que vous pouvez rencontrer, commettre;
  • Regarder les commandes docker compose up que vous devez lancer, si vous devez les modifier

Choisir la méthode qui fait le plus sens pour vous et votre organisation. C'est une décision à débattre et trouver un compromis (chacun·e ses préférences).

Docker compose et l'orchestration

Au delà de docker compose up qui est capable de détecter n'importe quel rebuild à faire en fonction des changements des sources, compose offre également les options deploy, replica et restart :

services:
  web:
    build: .
    ports:
      - "8000:5000"
    # volumes: #volume anonyme
    # - .:/code
    environment:
      FLASK_DEBUG: "true"
    deploy:
      mode: replicated
      replicas: 3
docker compose up
docker compose ps

Si on combine deploy avec restart, on peut créer un pool de conteneurs issus d'un service dont le nombre est déterminé avec certitude à l'execution et dans le temps (disponibilité renforcée)

services:
  web:
    build: .
    ports:
      - "8000:5000"
    # volumes: #volume anonyme
    # - .:/code
    environment:
      FLASK_DEBUG: "true"
    deploy:
      mode: replicated
      replicas: 3
    restart: always #par exemple

Kill un conteneur du service web et observez. Vous devriez toujours en avoir 3 (un autre de crée). Regardez les stats ou les events pour mieux voir ce qu'il se passe.