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

Flask, Celery, Redis und Docker

Docker-compose macht es sehr einfach, denselben Docker image für Ihre Flask -Anwendung und den/die Celery -Arbeiter zu verwenden.

29 Oktober 2020
post main image
https://unsplash.com/@dillonjshook

Dies ist ein Beitrag darüber, wie ich Docker und Docker-composer verwende, um meine Flask Website mit Celery und Redis zu entwickeln und zu betreiben. Es gibt viele Artikel im Internet darüber, und wenn Sie nach ihnen suchen, vergessen Sie nicht, auf Github.com zu suchen. Ich habe mir einfach den Krimskrams geschnappt und mein eigenes Setup erstellt. Bevor ich darauf eingehe, möchte ich zwei weitere Dinge erwähnen, die beim Hinzufügen von Celery zu Flask aufgetaucht sind.

Das Anwendungsmuster von Flask

Auch dazu muss ich auf den netten Beitrag von Miguel Grinberg verweisen. Das Problem ist, dass wir das Objekt Celery zu dem Zeitpunkt erstellen und initialisieren müssen, zu dem wir create_app() aufrufen, das unsere Anwendung Flask einrichtet. Es gibt mehrere Lösungen dafür, und ich habe eine verwendet, die im Artikel 'Flask + Celery = how to' beschrieben wurde, siehe Links unten. Der Trick ist die Verwendung von __init__.py für einen anderen Zweck. In den meisten Fällen verwenden wir __init__.py als Datei mit der Funktion create_app() . Was wir tun, ist, den gesamten Code in eine neue Datei factory.py zu verschieben und __init__.py zu verwenden, um das Objekt Celery zu instanziieren:

# __init__.py

from celery import  Celery

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

celery = make_celery()

Was jetzt passiert, ist, dass wir das Objekt Celery zur Zeit der Anwendungserstellung instanziieren und es später in create_app() mit den Parametern initialisieren, die wir in app.config angegeben haben. Um unsere Anwendung auszuführen, ändern wir die Datei run.py . Wir übergeben jetzt das Objekt Celery an die Funktion create_app() , etwa so:

#  run.py

import app
from app import factory

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

Einzelheiten finden Sie in dem oben erwähnten Artikel.

Celery und Zeitzonen

Ich dachte, ich erwähne das auch, weil ich damit zu kämpfen hatte. Ich versuchte, die Zeitzone sowohl für Flask als auch für Celery auf etwas anderes als UTC zu setzen. Für die Zeitzone 'Europa/Amsterdam' erhielt ich immer wieder die Nachricht (von Flower):

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

Ich habe dies nicht gelöst, aber glücklicherweise ist dies nicht wirklich ein Problem. Es ist eine gute Praxis, eine Webanwendung mit der Zeitzone UTC laufen zu lassen und nur auf Anfrage in eine lokale Zeitzone zu konvertieren, z.B. wenn einem Besucher Datum und Uhrzeit angezeigt werden. Um Probleme zu vermeiden, verwenden Sie überall UTC!

Verwendung von Docker

Ich verwende Docker für Entwicklung, Tests, staging und Produktion. Da auf meinem Produktionsserver ISPConfig mit einer MariaDB -Datenbank läuft und ich auch eine MariaDB -Datenbank auf meinem Entwicklungssystem installiert habe, habe ich meiner Docker -Konfiguration keine Datenbank hinzugefügt, sondern stattdessen über einen Unix-Socket eine Verbindung zur MariaDB -Datenbank hergestellt.

Ich habe eine gemeinsam genutzte docker-compose -Datei, docker-compose_shared.yml- und docker-compose -Dateien für Bereitstellungsoptionen, docker-compose_development.yml, docker-compose_production.yml, ... Und jede Einsatzoption hat ihre eigene Umgebungsdatei.

Um die Entwicklung zu starten, führe ich sie aus:

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

Und um die Produktion zu starten, starte ich::

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

Unter Verwendung der gleichen Docker image für die web-service und die celery-worker

Bevor ich Celery hinzufügte, hatte ich nur einen Dienst: web. Mit Celery habe ich mindestens drei weitere:

  • Redis
  • Ein oder mehrere Arbeitnehmer
  • Flower

Redis und Flower sind trivial hinzuzufügen, aber wie fügen wir einen Arbeitnehmer hinzu? Nachdem ich im Internet gelesen hatte, beschloss ich, dass der Arbeitnehmer dasselbe Bild wie das Web-Bild haben sollte. Natürlich gibt es hier eine Menge Overhead (toter Code), aber wir machen uns auch das Leben leichter, da wir funktionierenden Code verwenden können, den wir zuvor geschrieben und getestet haben.

Wenn ein Bild mit docker-compose erstellt wird, wird das Bild für web-service erstellt. Wenn es mit docker-compose ausgeführt wird, müssen sowohl der web-service - als auch der celery-worker-Dienst dieses Bild verwenden.

Unten sehen Sie die Datei docker-compose_shared.yml . Ich zeige nur die wichtigen Zeilen. Einige Variablen in der Datei .env für die Produktion:

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"
    ...

Die Datei 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"

Und die Datei docker-compose_Produktion.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

Manuelles Starten und Stoppen des Arbeiters während der Entwicklung

Bei der Entwicklung einer Flask -Anwendung verwenden wir die Option DEBUG . Dann wird die Anwendung automatisch neu gestartet, wenn wir Änderungen wie das Speichern einer Datei vornehmen.

Der Worker ist ein separates Programm, wenn es läuft, weiß es nicht, dass Sie den Code geändert haben. Das bedeutet, dass wir den Worker stoppen und starten müssen, nachdem wir Änderungen an einer Aufgabe vorgenommen haben. In der Datei docker-compose_shared.yml gibt es einen Befehl, der den Worker startet. Aber in der Entwicklungsdatei überstimme ich diesen Befehl mit

    command: echo "do not run"

Während der Entwicklung öffne ich ein Terminalfenster und starte alles mit 'docker-compose up'. Jetzt kann ich alle (Debug-)Meldungen und Probleme in der Anwendung sehen.

Der Arbeiter ist nicht gestartet. In einem anderen Terminal-Fenster gebe ich den Container Docker des Arbeiters mit 'docker-compose run sh' ein. Beachten Sie, dass ich hier auch 'docker compose exec sh' verwenden könnte.

Ich habe bereits ein Shell-Skript start_workers.sh in meinem Projektverzeichnis erstellt:

#  start_workers.sh

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

Jetzt muss ich nur noch in die Container-Shell tippen, um den Worker zu starten:

./start_workers.sh

und Celery startet.

Um Celery anzuhalten, drücke ich einfach CTRL-C. Antwort:

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

worker:  Warm shutdown  (MainProcess)

Hier haben wir die Möglichkeit, alle laufenden Aufgaben zu beenden.

Es ist auch möglich, ein anmutiges Herunterfahren des Arbeiters durchzuführen. Gehen Sie zum Terminal-Fenster des Arbeiters und geben Sie CTRL-Z ein, um den Arbeiter anzuhalten. Antwort: Antwort:

[1]+   Stopped                 ./start_workers.sh

Dann tippen Sie:

kill %1

Nun antwortet Celery mit (gefolgt von der Nachricht des beendenden Prozesses):

worker:  Warm shutdown  (MainProcess)

[1]+   Terminated              ./start_workers.sh

Celery Arbeitsspeicher-Nutzung

Die vorgestellte Lösung ist nicht die speicherfreundlichste Lösung, aber sie ist auch nicht so schlecht. Um einen Hinweis zu erhalten, verwenden wir den Befehl Docker :

docker stats

Antwort:

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

Ich habe den Befehl web-service mit 5 Mitarbeitern von Gunicorn gestartet. Es gibt einen Celery -Arbeiter mit zwei Threads (--concurrency=2). Der Mitarbeiter für Celery nimmt hier etwa 120 MB ein. Natürlich wird der Speicherverbrauch in einigen Fällen ansteigen, aber solange die meisten Aufgaben nicht sehr speicherintensiv sind, halte ich es nicht für lohnenswert, Code vom Celery -Worker zu strippen.

Zusammenfassung

Die Verwendung von Docker mit Docker-compose ist nicht nur deshalb großartig, weil wir ein (fast) identisches System für Entwicklung und Produktion haben. Es ist auch sehr einfach, Dienste wie Redis und Flower hinzuzufügen. Und die Verwendung desselben Docker image für unsere Anwendung und den Mitarbeiter Celery ist mit Docker-compose ebenfalls sehr einfach. Wie immer gibt es eine Kehrseite: Es braucht Zeit, dies einzurichten. Aber das Ergebnis macht vieles wieder wett.

Links / Impressum

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

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare (1)

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.

avatar

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