Flask, Celery, Redis и Docker
Docker-compose делает очень простым использование одного и того же Docker image для вашего приложения Flask и Celery работника(ов).
Это сообщение о том, как я использую Docker и Docker-composer для разработки и запуска моего сайта Flask с Celery и Redis. В интернете много статей об этом, и если вы ищете их, не забудьте поискать на сайте Github.com. Я просто взял кусочки и создал свою собственную установку. Перед тем, как вдаваться в подробности, я хочу упомянуть еще две вещи, которые возникли при добавлении Celery к Flask.
Прикладной шаблон Flask
Снова я должен обратиться к замечательному посту Miguel Grinberg об этом. Проблема в том, что мы должны создать и инициализировать объект Celery во время вызова create_app() , который устанавливает наше приложение Flask . Есть несколько решений, и я использовал одно из них, которое было описано в статье 'Flask + Celery = как', смотрите ссылки ниже. Фокус в том, чтобы использовать __init__.py для другой цели. В большинстве случаев мы используем __init__.py в качестве файла с функцией create_app() . Мы перемещаем весь этот код в новый файл factory.py и используем __init__.py для инстанцирования объекта Celery :
# __init__.py
from celery import Celery
def make_celery(app_name=__name__):
celery = Celery(__name__)
return celery
celery = make_celery()
Что произойдёт теперь, так это то, что мы выполним инстанцинацию объекта Celery во время создания приложения, а затем инициализируем его в create_app() с параметрами, которые мы указали в app.config. Для запуска нашего приложения мы изменяем файл run.py . Теперь мы передаём объект Celery в функцию create_app() , что-то вроде:
# run.py
import app
from app import factory
my_app = factory.create_app(celery=app.celery, ...)
Для подробностей смотрите статью, упомянутую выше.
Celery и часовые пояса
Я думал, что упомянул об этом, потому что боролся с этим. Я пытался установить часовой пояс для обоих Flask и Celery на что-то другое, чем UTC. Для часового пояса 'Европа/Амстердам' я продолжал получать сообщение (от Flower):
Substantial drift from celery@75895a6a62ab may mean clocks are out of sync. Current drift is 7200 seconds.
Я не решил эту проблему, но, к счастью, это не совсем проблема. Хорошей практикой является запуск веб-приложения с тайм-зоной UTC и преобразование в локальную тайм-зону только по запросу, например, при показе посетителю даты и времени. Чтобы избежать проблем, используйте UTC везде!
Использование Docker
Я использую Docker для разработки, тестирования, staging и производства. Поскольку мой производственный сервер работает под управлением ISPConfig с базой данных MariaDB , а также в моей системе разработки установлена база данных MariaDB , я не добавлял базу данных в конфигурацию Docker , а вместо этого подключался к базе данных MariaDB с помощью unix-разъема.
У меня есть общие файлы docker-compose , docker-compose_shared.yml и docker-compose для вариантов развертывания, docker-compose_ development.yml, docker-compose_production.yml, ... И каждый вариант установки имеет свой файл окружения.
Чтобы начать разработку, я запускаю:
docker-compose -f docker-compose_shared.yml -f docker-compose_development.yml up
И чтобы начать разработку, я запускаю:
docker-compose -f docker-compose_shared.yml -f docker-compose_production.yml up -d
используя один и тот же Docker image для web-service и celery-worker
До добавления Celery у меня был только один сервис: web. С Celery у меня есть как минимум еще три:
- Redis
- Один или несколько работников
- Flower
Redis и Flower тривиальны для добавления, но как нам добавить работника? После чтения в интернете я решил, что у рабочего должно быть то же самое изображение, что и у веб-изображения. Конечно, здесь много накладных расходов (мертвый код), но мы также делаем нашу жизнь проще, так как можем использовать рабочий код, который мы написали и протестировали ранее.
При сборке образа с docker-compose собирается образ для web-service . При работе с docker-compose и web-service , и celery-worker должны использовать этот образ.
Ниже находится файл docker-compose_shared.yml . Я показываю только важные строки. Некоторые переменные в файле .env для производства:
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"
...
Файл 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"
И файл 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
Ручной запуск и остановка работника во время разработки
При разработке приложения Flask мы используем опцию DEBUG . Затем приложение автоматически перезапускается, когда мы вносим изменения, такие как сохранение файла.
Рабочий - это отдельная программа, при запуске она не знает, что вы изменили код. Это означает, что мы должны остановиться и запустить работника после внесения изменений в задачу. В файле docker-compose_shared.yml есть команда запуска рабочего. Но в файле разработки я отменяю эту команду, используя ее:
command: echo "do not run"
В процессе разработки я открываю окно терминала и запускаю все, используя 'docker-compose up'. Теперь я вижу все (отладочные) сообщения и проблемы в приложении.
Работник не запускался. В другом окне терминала я ввожу рабочий контейнер Docker , используя 'docker-compose run sh'. Обратите внимание, что здесь я также могу использовать 'docker compose exec sh'.
Я уже создал скрипт оболочки start_workers.sh в каталоге своего проекта:
# start_workers.sh
celery -A celery_worker.celery worker -Q celery_worker1_queue -n celery_worker1 --loglevel=DEBUG --logfile=...
Теперь все, что мне нужно сделать, чтобы запустить работника, это ввести в оболочку контейнера:
./start_workers.sh
и Celery запускается.
Для остановки Celery я просто нажимаю CTRL-C. Ответ:
worker: Hitting Ctrl+C again will terminate all running tasks!
worker: Warm shutdown (MainProcess)
Здесь мы имеем возможность прервать любые запущенные задачи.
Также можно сделать грациозное завершение работы работника. Перейдите в терминальное окно рабочего и введите CTRL-Z для его остановки. Ответ:
[1]+ Stopped ./start_workers.sh
Затем введите:
kill %1
Теперь Celery отвечает (с последующим сообщением о завершении процесса):
worker: Warm shutdown (MainProcess)
[1]+ Terminated ./start_workers.sh
Celery использование рабочей памяти.
Представленное решение не является самым дружественным к памяти, но и не так уж и плохо. Для получения информации мы используем команду Docker :
docker stats
Ответ:
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
Я запустил web-service с 5 работниками Gunicorn . Есть один рабочий Celery с двумя потоками (--concurrency=2). Рабочий Celery здесь занимает около 120 Мб. Конечно, в некоторых случаях использование памяти будет увеличиваться, но пока большинство задач не очень интенсивно используют память, я не думаю, что стоит лишний раз разбирать код Celery -работника.
Резюме
Использование Docker с Docker-compose не только замечательно, потому что у нас есть (почти) идентичная система для разработки и производства. Также очень легко добавить такие сервисы как Redis и Flower. И использование одного и того же Docker image для нашего приложения и рабочего Celery также очень просто с Docker-compose. Как всегда есть и обратная сторона: настройка занимает время. Но результат наверстывает упущенное.
Ссылки / кредиты
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
Подробнее
Celery Docker Docker-compose Flask Redis
Оставить комментарий
Комментируйте анонимно или войдите в систему, чтобы прокомментировать.
Комментарии (1)
Оставьте ответ
Ответьте анонимно или войдите в систему, чтобы ответить.
Hi there thanks!
Is it possible for you to share/post a minimal github project with all these files?
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу