Flask, Celery, Redis en Docker
Docker-compose maakt het zeer eenvoudig om dezelfde Docker image te gebruiken voor uw Flask applicatie en de Celery medewerker(s).
Dit is een bericht over hoe ik Docker en Docker-composer gebruik om mijn Flask website met Celery en Redis te ontwikkelen en te beheren. Er zijn veel artikelen op het internet over dit onderwerp en vergeet niet te zoeken op Github.com als u ze zoekt. Ik heb gewoon de stukjes en beetjes gepakt en mijn eigen setup gemaakt. Voordat ik hier op inga wil ik nog twee andere dingen noemen die naar voren kwamen bij het toevoegen van Celery aan Flask.
Het toepassingspatroon van Flask
Ook hier moet ik weer verwijzen naar Miguel 's leuke post over dit onderwerp. Het probleem is dat we het Celery object moeten maken en initialiseren op het moment dat we create_app() aanroepen die onze Flask app opzet. Er zijn verschillende oplossingen hiervoor en ik heb er een gebruikt die beschreven is in het artikel 'Flask + Celery = hoe werkt het', zie onderstaande links. De truc is om __init__.py voor een ander doel te gebruiken. In de meeste gevallen gebruiken we __init__.py als bestand met de functie create_app() . Wat we doen is al deze code verplaatsen naar een nieuw bestand factory.py en __init__.py gebruiken om het Celery object te instantiëren:
# __init__.py
from celery import Celery
def make_celery(app_name=__name__):
celery = Celery(__name__)
return celery
celery = make_celery()
Wat er nu gebeurt is dat we het Celery object instantiëren bij het aanmaken van de app en het later initialiseren in create_app() met de parameters die we in app.config hebben opgegeven. Om onze app te draaien wijzigen we het run.py bestand. We geven nu het object Celery door aan de functie create_app() , zoiets als:
# run.py
import app
from app import factory
my_app = factory.create_app(celery=app.celery, ...)
Zie voor details het hierboven genoemde artikel.
Celery en tijdzones
Ik dacht dat ik dit ook vermeldde omdat ik er moeite mee had. Ik heb geprobeerd de tijdzone voor zowel Flask als Celery in te stellen op iets anders dan UTC. Voor tijdzone 'Europa/Amsterdam' bleef ik het bericht krijgen (van Flower):
Substantial drift from celery@75895a6a62ab may mean clocks are out of sync. Current drift is 7200 seconds.
Ik heb dit niet opgelost, maar gelukkig is dit niet echt een probleem. Het is een goede gewoonte om een webapplicatie met tijdzone UTC te draaien en alleen naar een lokale tijdzone te converteren wanneer daarom wordt gevraagd, bijvoorbeeld bij het tonen van een datum en tijd aan een bezoeker. Om problemen te voorkomen, gebruikt u UTC overal!
Gebruik Docker
Ik gebruik Docker voor ontwikkeling, testen, staging en productie. Omdat mijn productieserver ISPConfig met een MariaDB database draait en ik ook een MariaDB database op mijn ontwikkelingssysteem heb geïnstalleerd, heb ik geen database toegevoegd aan mijn Docker configuratie maar in plaats daarvan verbinding gemaakt met de MariaDB database via een unix socket.
Ik heb een gedeeld docker-compose bestand, docker-compose_shared.yml, en docker-compose bestanden voor deployment opties, docker-compose_development.yml, docker-compose_production.yml, ... En elke inzetoptie heeft een eigen omgevingsbestand.
Om de ontwikkeling te starten draai ik:
docker-compose -f docker-compose_shared.yml -f docker-compose_development.yml up
En om de productie te starten draai ik:
docker-compose -f docker-compose_shared.yml -f docker-compose_production.yml up -d
Met dezelfde Docker image voor de web-service en de celery-worker
Voordat ik Celery toevoegde, had ik maar één dienst: web. Met Celery heb ik er minstens drie meer:
- Redis
- Een of meer werknemers
- Flower
Redis en Flower zijn triviaal om toe te voegen, maar hoe voegen we een arbeider toe? Na het lezen op het internet heb ik besloten dat de werker hetzelfde beeld moet hebben als het web-beeld. Natuurlijk is er veel overhead (dode code) hier, maar we maken ons leven ook makkelijker omdat we werkende code kunnen gebruiken die we eerder hebben geschreven en getest.
Bij het bouwen van een afbeelding met docker-compose wordt de afbeelding voor web-service gebouwd. Bij het draaien met docker-compose moeten zowel de web-service als de celery-worker-service deze afbeelding gebruiken.
Hieronder staat het bestand docker-compose_shared.yml . Ik laat alleen de belangrijke regels zien. Enkele variabelen in het bestand .env voor productie:
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"
...
Het bestand 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"
En het bestand 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
Handmatig starten en stoppen van de werknemer tijdens de ontwikkeling
Bij de ontwikkeling van een Flask applicatie gebruiken we de optie DEBUG . Vervolgens start de applicatie automatisch opnieuw op wanneer we wijzigingen aanbrengen zoals het opslaan van een bestand.
De werker is een apart programma, als het draait weet het niet dat je de code hebt veranderd. Dit betekent dat we moeten stoppen en de werker moeten starten na het maken van wijzigingen aan een taak. In het docker-compose_shared.yml bestand staat een commando dat de werker start. Maar in het ontwikkelingsbestand overrulen we dit commando met behulp van:
command: echo "do not run"
Tijdens de ontwikkeling open ik een terminalvenster en start ik alles op met 'docker-compose up'. Nu kan ik alle (debug)berichten en problemen in de applicatie zien.
De werker is niet gestart. In een ander terminalvenster voer ik de werknemer Docker container in met 'docker-compose run sh'. Merk op dat ik hier ook 'docker compose exec sh' kan gebruiken.
Ik heb al een shell script start_workers.sh gemaakt in mijn project directory:
# start_workers.sh
celery -A celery_worker.celery worker -Q celery_worker1_queue -n celery_worker1 --loglevel=DEBUG --logfile=...
Nu hoef ik alleen nog maar in de container-shell te typen om de werker te starten:
./start_workers.sh
en Celery start.
Om Celery te stoppen druk ik gewoon op CTRL-C. Reactie:
worker: Hitting Ctrl+C again will terminate all running tasks!
worker: Warm shutdown (MainProcess)
Hier hebben we de optie om alle lopende taken te beëindigen.
Het is ook mogelijk om een sierlijke afsluiting van de werknemer te doen. Ga naar het terminalvenster van de werker en typ CTRL-Z om de werker te stoppen. Reactie:
[1]+ Stopped ./start_workers.sh
Typ dan:
kill %1
Nu antwoordt Celery met (gevolgd door het bericht van het afsluitproces):
worker: Warm shutdown (MainProcess)
[1]+ Terminated ./start_workers.sh
Celery werknemer geheugengebruik
De gepresenteerde oplossing is niet de meest geheugenvriendelijke oplossing, maar het is ook niet zo slecht. Om een indicatie te krijgen gebruiken we het commando Docker :
docker stats
Reactie:
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
Ik heb de web-service gestart met 5 Gunicorn medewerkers. Er is één Celery werker met twee draden (--concurrency=2). De Celery werker neemt hier ongeveer 120 MB in beslag. Natuurlijk zal het geheugengebruik in sommige gevallen toenemen, maar zolang de meeste taken niet erg geheugenintensief zijn denk ik niet dat het de moeite waard is om de code van de Celery werker te strippen.
Samenvatting
Het gebruik van Docker met Docker-compose is niet alleen geweldig omdat we een (bijna) identiek systeem hebben voor ontwikkeling en productie. Het is ook zeer eenvoudig om diensten als Redis en Flower toe te voegen. En het gebruik van dezelfde Docker image voor onze applicatie en de Celery medewerker is ook zeer eenvoudig met Docker-compose. Zoals altijd is er een keerzijde: het kost tijd om dit in te stellen. Maar het resultaat maakt veel goed.
Links / credits
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
Lees meer
Celery Docker Docker-compose Flask Redis
Laat een reactie achter
Reageer anoniem of log in om commentaar te geven.
Opmerkingen (1)
Laat een antwoord achter
Antwoord anoniem of log in om te antwoorden.
Hi there thanks!
Is it possible for you to share/post a minimal github project with all these files?
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's