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

Flask, Celery, Redis и Docker

Docker-compose делает очень простым использование одного и того же Docker image для вашего приложения Flask и Celery работника(ов).

29 октября 2020
post main image
https://unsplash.com/@dillonjshook

Это сообщение о том, как я использую 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

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии (1)

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.

avatar

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