Les Crash-Devs d'un Passionné

Conditionner le démarrage des services de docker compose

/Catégorie/docker

Temps de lecture : 5 minutes

Dans un fichier docker compose, nous retrouvons plusieurs services avec parfois des dépendances entre ces applications. Par exemple, une application backend pourrait avoir besoin d'une base de données disponible. On aurait une configuration de ce type

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  app1:
    build: .
    container_name: app1
    ports:
      - "8888:8888"
    volumes:
      - ./:/usr/src/app
    command: /bin/sh -c "poetry run fastapi dev app1.py --host 0.0.0.0 --port 8888" # Not do this in production
    depends_on:
      postgres

  postgres:
    image: postgres:latest
    container_name: postgres
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PORT=5432
      - POSTGRES_DB=app
      - POSTGRES_USER=debug
      - POSTGRES_PASSWORD=debug

L'instruction

depends_on
dans le service
app1
indique que ce dernier a besoin du service
postgres
. Cependant, il vérifie uniquement la présence du container (status
RUNNING
) et non sa disponibilité. En effet, le container
postgres
peut être démarré, mais pas encore opérationnel pour
app1
.

Cette configuration ci-dessus est très utilisée, car cette base de données démarre plus vite que le backend

app1
et on ne voit pas la subtilité.

Configuration healthy rabbitmq

Maintenant, nous allons prendre un exemple avec une base

rabbitmq
(qui met beaucoup plus de temps à démarrer que
postgres
), nous voulons attendre que ce service
rabbitmq
soit
healthy
(et pas seulement dans le status
RUNNING
) avant de lancer le service
app1

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
services:
  app1:
    build: .
    container_name: app1
    ports:
      - "8888:8888"
    volumes:
      - ./:/usr/src/app
    command: /bin/sh -c "poetry run fastapi dev app1.py --host 0.0.0.0 --port 8888" # Not do this in production
    depends_on:
      rabbitmq:
        condition: service_healthy

  rabbitmq:
    container_name: rabbitmq
    environment:
      - RABBITMQ_DEFAULT_USER=guest
      - RABBITMQ_DEFAULT_PASS=guest
    image: rabbitmq:latest
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]

Notre container

rabbitmq
a une propriété
healthcheck
qui contient une commande qui sera exécuté et lorsque cette dernière retournera une information,
docker compose
considèrera le service
rabbitmq
prêt (status
HEALTHY
)

Cette information sera envoyée au service app1 car dans sa configuration, il est précisé

clipboard
Copier le code
1
2
3
depends_on:
      rabbitmq:
        condition: service_healthy

Il vous reste à faire une

docker compose up
et vous remarquerez que
app1
ne démarre pas (status CREATED ou STARTING) tant que le service
rabbitmq
n'a pas fini de démarrer (status HEALTHY).

Configuration healthy custom

Dans l'exemple ci-dessus, on repose sur un outil proposé par

rabbitmq
mais si vous avez envie de créer votre propre vérification, il est tout à fait possible d'implémenter cette configuration.

Pour cela, on crée un autre service

app2
qui devra démarrer quand
app1
aura fini de démarrer (status HEALTHY) (et donc après
rabbitmq
)

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
services:
  app1:
    build: .
    container_name: app1
    ports:
      - "8888:8888"
    volumes:
      - ./:/usr/src/app
    command: /bin/sh -c "poetry run fastapi dev app1.py --host 0.0.0.0 --port 8888" # Not do this in production
    depends_on:
      rabbitmq:
        condition: service_healthy
    # For app2, await this app1 are avaialble
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://0.0.0.0:8888/health" ]


  app2:
    build: .
    container_name: app2
    ports:
      - "8889:8889"
    volumes:
      - ./:/usr/src/app
    command: /bin/sh -c 'poetry run fastapi dev app2.py --host 0.0.0.0 --port 8889' # Not do this in production
    depends_on:
      app1:
        condition: service_healthy

  rabbitmq:
    container_name: rabbitmq
    environment:
      - RABBITMQ_DEFAULT_USER=guest
      - RABBITMQ_DEFAULT_PASS=guest
    image: rabbitmq:latest
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]

Sur notre

app1
, on saisit la commande qui permettra de déterminer si ce service est opérationnel.

clipboard
Copier le code
1
2
healthcheck:
      test: [ "CMD", "curl", "-f", "http://0.0.0.0:8888/health" ]

Pour cela, nous faisons une requête http vers une route health qui a été configuré dans mon backend

La fonction retourne un simple message (exemple sur FastApi en python), la commande attend un retour

clipboard
Copier le code
1
2
3
@app.get("/health")
async def health():
    return {"status": "OK"}

Et on indique au service

app2
de démarrer uniquement quand
app1
est prêt.

clipboard
Copier le code
1
2
3
depends_on:
    app1:
      condition: service_healthy

Encore un

docker compose up
et vous remarquerez que
app2
ne démarre pas tant que le service
app1
n'a pas fini de démarrer (status HEALTHY)

Si vous continuez à regarder vos logs, vous allez remarquer votre route

/health
continue d'être appelée. Cela est dû à la configuration du paramètre
interval
qui est à 30 secondes par défaut. À chaque appel,
docker compose
vérifie que votre service est encore healthy et si au bout de X essaie (paramètre
retry
= 3 par défaut) où chacun durent la valeur du paramètre
interval
, alors le service change de status en
UNHEALTHY

Et via le dockerfile ?

On peut également mettre cette configuration dans le

Dockerfile

HEALTHCHECK --interval=10s --retry=2 CMD curl -f http://0.0.0.0:8888/health || exit 1

Il sera dans notre exemple utilisé pour

app1
et
app2

Conclusion

Et voilà 2 implémentations d'un health check avec

docker compose
:

  • En utilisant une commande fournie par l'éditeur (ici
    rabbitmq
    )
  • En configurant votre application avec une route à interroger

Le code complet est disponible sous mon compte Github