angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask, Celery, Redis et Docker

Docker-compose permet d'utiliser très facilement le même Docker image pour votre application Flask et le(s) travailleur(s) Celery .

29 octobre 2020
Dans Celery, Docker
post main image
https://unsplash.com/@dillonjshook

Ce billet explique comment j'utilise Docker et Docker-composer pour développer et faire fonctionner mon site web Flask avec Celery et Redis. Il existe de nombreux articles sur Internet à ce sujet et si vous les recherchez, n'oubliez pas de faire une recherche sur Github.com. J'ai juste pris les morceaux et j'ai créé ma propre installation. Avant d'entrer dans le vif du sujet, je voudrais mentionner deux autres choses qui sont apparues lorsque j'ai ajouté Celery à Flask.

Le modèle d'application Flask

Encore une fois, je dois me référer à l'article de Miguel Grinberg, qui est très intéressant à ce sujet. Le problème est que nous devons créer et initialiser l'objet Celery au moment où nous appelons create_app() qui met en place notre application Flask . Il existe plusieurs solutions à ce problème et j'en ai utilisé une qui est décrite dans l'article "Flask + Celery = comment faire", voir les liens ci-dessous. L'astuce consiste à utiliser __init__.py dans un autre but. Dans la plupart des cas, nous utilisons __init__.py comme fichier avec la fonction create_app() . Ce que nous faisons, c'est déplacer tout ce code dans un nouveau fichier factory.py et utiliser __init__.py pour instancier l'objet Celery :

# __init__.py

from celery import  Celery

def make_celery(app_name=__name__):
    celery =  Celery(__name__)
    return celery

celery = make_celery()

Ce qui se passe maintenant, c'est que nous instancions l'objet Celery au moment de la création de l'application et que nous l'initialisons ensuite dans create_app() avec les paramètres que nous avons spécifiés dans app.config. Pour exécuter notre application, nous modifions le fichier run.py . Nous passons maintenant l'objet Celery à la fonction create_app() , un peu comme :

#  run.py

import app
from app import factory

my_app = factory.create_app(celery=app.celery, ...)

Pour plus de détails, reportez-vous à l'article mentionné ci-dessus.

Celery et fuseaux horaires

Je pensais le mentionner aussi parce que j'ai eu du mal à le faire. J'ai essayé de régler le fuseau horaire pour Flask et Celery sur autre chose que UTC. Pour le fuseau horaire "Europe/Amsterdam", je continuais à recevoir le message (de Flower) :

Substantial drift from celery@75895a6a62ab may mean clocks are out of sync. Current drift is 7200 seconds.

Je n'ai pas résolu ce problème, mais heureusement, ce n'est pas vraiment un problème. Il est bon d'utiliser une application web avec le fuseau horaire UTC et de ne convertir vers un fuseau horaire local que lorsque cela est demandé, par exemple pour montrer une date et une heure à un visiteur. Pour éviter les problèmes, utilisez UTC partout !

Utilisation de Docker

J'utilise Docker pour le développement, les essais, staging et la production. Comme mon serveur de production exécute ISPConfig avec une base de données MariaDB et que j'ai également une base de données MariaDB installée sur mon système de développement, je n'ai pas ajouté de base de données à ma configuration Docker mais je me connecte à la base de données MariaDB en utilisant une socket unix.

Je partage les fichiers docker-compose , docker-compose_shared.yml, et docker-compose pour les options de déploiement, docker-compose_development.yml, docker-compose_production.yml, ... Et chaque option de déploiement a son propre fichier d'environnement.

Pour démarrer le développement, je lance :

docker-compose  -f  docker-compose_shared.yml  -f  docker-compose_development.yml up

Et pour démarrer la production, je lance :

docker-compose  -f  docker-compose_shared.yml  -f  docker-compose_production.yml up -d

En utilisant le même Docker image pour le web-service et le celery-worker

Avant d'ajouter Celery , je n'avais qu'un seul service : le web. Avec Celery , j'en ai au moins trois de plus :

  • Redis
  • Un ou plusieurs travailleurs
  • Flower

Les questions Redis et Flower sont triviales à ajouter, mais comment ajouter un travailleur ? Après avoir lu sur Internet, j'ai décidé que le travailleur devait avoir la même image que celle du site. Bien sûr, il y a beaucoup de surcharge (code mort) ici, mais nous rendons aussi notre vie plus facile car nous pouvons utiliser un code de travail que nous avons écrit et testé auparavant.

Lorsque l'on construit une image avec docker-compose, l'image pour web-service est construite. Lors de l'exécution avec docker-compose, le service web-service et le service celery-worker doivent tous deux utiliser cette image.

Le fichier docker-compose_shared.yml se trouve ci-dessous. Je ne montre que les lignes importantes. Quelques variables du fichier .env pour la production :

PROJECT_NAME=peterspython
PROJECT_CONFIG=production
DOCKER_IMAGE_VERSION=1.456
#  docker-compose_shared.yml

version: "3.7"

services:

  redis:
    image: "redis:5.0.9-alpine"
    ...

  web:
    image: ${PROJECT_NAME}_${PROJECT_CONFIG}_web_image:${DOCKER_IMAGE_VERSION}
    container_name: ${PROJECT_NAME}_${PROJECT_CONFIG}_web_container
    env_file:
      - ./.env
    build:
      context: ./project
      dockerfile:  Dockerfile
      args:
        ...
    ports:
      - "${SERVER_PORT_HOST}:${SERVER_PORT_CONTAINER}"
    environment:
      ...
    volumes:
      # connect to mysql via unix socket 
      - /var/run/mysqld:/var/run/mysqld
      # files outside the container:
      ...
    depends_on:
      - redis

  celery_worker1:
    image: ${PROJECT_NAME}_${PROJECT_CONFIG}_web_image:${DOCKER_IMAGE_VERSION}
    env_file:
      - ./.env
    restart: always
    environment:
      ...
    volumes:
      # connect to mysql via unix socket 
      - /var/run/mysqld:/var/run/mysqld
      # files outside the container:
      ...
    # for development we start the celery worker by hand after entering the container, this means we can stop and start after editing files
    # see  docker-compose_development.yml
    command: celery -A celery_worker.celery worker -Q ${CELERY_WORKER1_QUEUE} -n ${CELERY_WORKER1_NAME} ${CELERY_WORKER1_OPTIONS} --logfile=...
    depends_on:
      - web
      - redis

  flower:
    image: "mher/flower:0.9.5"
    ...

Le fichier docker-compose_development.yml :

#  docker-compose_development.yml

version: "3.7"

services:
  web:
    ports:
      - "${SERVER_PORT_HOST}:${SERVER_PORT_CONTAINER}"
    volumes:
      # development: use files outside the container
      - ./project:/home/flask/project/
    command: python3 run_all.py

  celery_worker1:
    restart: "no"
    volumes:
      # development: use files outside the container
      - ./project:/home/flask/project/
    command: echo "do not run"

Et le fichier docker-compose_production.yml :

#  docker-compose_production.yml

version: "3.7"

services:
  web:
    ports:
      - "${SERVER_PORT_HOST}:${SERVER_PORT_CONTAINER}"
    volumes:
    - /var/www/clients/${GROUP}/${OWNER}/web/static:/home/flask/project/sites/peterspython/static
    command: /usr/local/bin/gunicorn ${GUNICORN_PARAMETERS} -b :${SERVER_PORT_CONTAINER} wsgi_all:application

Démarrer et arrêter manuellement le travailleur pendant le développement

Lors du développement d'une application Flask , nous utilisons l'option DEBUG . Ensuite, l'application redémarre automatiquement lorsque nous apportons des modifications, par exemple en enregistrant un fichier.

Le travailleur est un programme séparé, lorsqu'il est en cours d'exécution, il ne sait pas que vous avez changé le code. Cela signifie que nous devons arrêter et redémarrer le travailleur après avoir apporté des modifications à une tâche. Dans le fichier docker-compose_shared.yml , il y a une commande qui permet de démarrer le travailleur. Mais dans le fichier de développement, j'annule cette commande en utilisant :

    command: echo "do not run"

Pendant le développement, j'ouvre une fenêtre de terminal et je démarre tout en utilisant "docker-compose up". Maintenant, je peux voir tous les messages (de débogage) et les problèmes dans l'application.

Le travailleur n'a pas démarré. Dans une autre fenêtre de terminal, je saisis le conteneur Docker du travailleur en utilisant "docker-compose run sh". Notez que je pourrais également utiliser "docker compose exec sh" ici.

J'ai déjà créé un script shell start_workers.sh dans le répertoire de mon projet :

#  start_workers.sh

celery -A celery_worker.celery worker -Q celery_worker1_queue -n celery_worker1 --loglevel=DEBUG  --logfile=...

Maintenant, tout ce que j'ai à faire pour démarrer le travailleur est de taper dans le shell du conteneur :

./start_workers.sh

et le script Celery démarre.

Pour arrêter Celery , il suffit d'appuyer sur CTRL-C. Réponse :

worker: Hitting Ctrl+C again will terminate all running tasks!

worker:  Warm shutdown  (MainProcess)

Ici, nous avons la possibilité de mettre fin à toute tâche en cours.

Il est également possible d'effectuer un arrêt gracieux du travailleur. Allez dans la fenêtre du terminal du travailleur et tapez CTRL-Z pour arrêter le travailleur. Réponse :

[1]+   Stopped                 ./start_workers.sh

Puis tapez :

kill %1

Maintenant, Celery répond avec (suivi du message du processus de fin) :

worker:  Warm shutdown  (MainProcess)

[1]+   Terminated              ./start_workers.sh

Celery utilisation de la mémoire du travailleur

La solution présentée n'est pas la plus favorable à la mémoire, mais elle n'est pas si mauvaise non plus. Pour obtenir une indication, nous utilisons la commande Docker :

docker stats

Réponse :

CONTAINER ID     NAME                                        CPU  %     MEM USAGE /  LIMIT      MEM %               NET I/O             BLOCK I/O           PIDS
d531bfb686d0      peterspython_production_flower_1           0.17%     32.03MiB / 7.791GiB   0.40%               6.18MB / 2.26MB     0B / 0B             6
a63af28ce411      peterspython_production_celery_worker1_1   0.30%     121.6MiB / 7.791GiB   1.52%               9.95MB / 10.4MB     0B / 0B             3
b8b9f080dc26      peterspython_production_web_container      0.02%     467.3MiB / 7.791GiB   5.86%               1.35MB / 55.7MB     0B / 0B             6
de4fb0ef253a      peterspython_production_redis_1            0.16%     9.059MiB / 7.791GiB   0.11%               12.6MB / 16.1MB     0B / 0B             4

J'ai lancé la commande web-service avec 5 travailleurs Gunicorn . Il y a un travailleur Celery avec deux fils (--concurrency=2). Le travailleur Celery prend ici quelque 120 Mo. Bien sûr, l'utilisation de la mémoire augmentera dans certains cas, mais tant que la plupart des tâches ne requièrent pas beaucoup de mémoire, je ne pense pas qu'il vaille la peine de retirer le code de l'employé Celery .

Résumé

Utiliser Docker avec Docker-compose n'est pas seulement formidable parce que nous avons un système (presque) identique pour le développement et la production. Il est également très facile d'ajouter des services comme Redis et Flower. Et utiliser le même Docker image pour notre application et le travailleur Celery est également très facile avec Docker-compose. Comme toujours, il y a un inconvénient : il faut du temps pour le mettre en place. Mais le résultat compense beaucoup.

Liens / crédits

Celery and the Flask Application Factory Pattern
https://blog.miguelgrinberg.com/post/celery-and-the-flask-application-factory-pattern/page/0

Celery in a Flask Application Factory
https://github.com/zenyui/celery-flask-factory

Dockerize a Flask, Celery, and Redis Application with Docker Compose
https://nickjanetakis.com/blog/dockerize-a-flask-celery-and-redis-application-with-docker-compose

Flask + Celery = how to.
https://medium.com/@frassetto.stefano/flask-celery-howto-d106958a15fe

start-celery-for-dev.py
https://gist.github.com/chenjianjx/53d8c2317f6023dc2fa0

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires (1)

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.

avatar

Hi there thanks!
Is it possible for you to share/post a minimal github project with all these files?