Вступление
В этом руководстве я дам общее представление о том, почему очереди сообщений сельдерея ценны, а также как использовать сельдерей вместе с Redis в приложении Django. Чтобы продемонстрировать особенности реализации, я создам минималистичное приложение для обработки изображений, которое генерирует эскизы изображений, отправленных пользователями.
Будут рассмотрены следующие темы:
- Справочная информация об очередях сообщений с Celery и Redis
- Настройка локального разработчика с Django, Celery и Redis
- Создание миниатюр изображений в задаче Celery
- Развертывание на сервере Ubuntu
Код для этого примера можно найти на GitHub вместе с инструкциями по установке и настройке, если вы просто хотите сразу перейти к функционально завершенному приложению, в противном случае в оставшейся части статьи я расскажу вам, как собрать все с нуля.
Справочная информация об очередях сообщений с Celery и Redis
Celery - это программный пакет для организации очередей задач на основе Python, который обеспечивает выполнение асинхронных вычислительных рабочих нагрузок, управляемых информацией, содержащейся в сообщениях, созданных в коде приложения (Django в этом примере), предназначенных для очереди задач Celery. Сельдерей также можно использовать для выполнения повторяющихся, периодических (то есть запланированных) задач, но это не будет предметом внимания в этой статье.
Сельдерей лучше всего использовать вместе с хранилищем, которое часто называют брокером сообщений. Распространенным брокером сообщений, который используется с сельдереем, является Redis, который является эффективным хранилищем данных типа "ключ-значение" в памяти. В частности, Redis используется для хранения сообщений, созданных кодом приложения, описывающих работу, которая должна быть выполнена в очереди задач Celery. Redis также служит хранилищем результатов, поступающих из очередей сельдерея, которые затем извлекаются потребителями очереди.
Настройка локального разработчика с Django, Celery и Redis
Я начну с самой сложной части - установки Redis.
Установка Redis в Windows
- Загрузите zip-файл Redis и разархивируйте в какой-нибудь каталог
- Найдите файл с именем redis-server.exe и дважды щелкните, чтобы запустить сервер в командном окне.
- Точно так же найдите другой файл с именем redis-cli.exe и дважды щелкните его, чтобы открыть программу в отдельном командном окне.
- В командном окне, в котором запущен клиент cli, проверьте, может ли
клиент разговаривать с сервером, выполнив команду
ping
и если все пойдет хорошо, должен быть возвращенPONG
Установка Redis в Mac OSX / Linux
- Загрузите tarball-файл Redis и распакуйте его в какой-нибудь каталог.
- Запустите файл make с помощью
make install
чтобы собрать программу. - Откройте окно терминала и запустите команду
redis-server
- В другом окне терминала запустите
redis-cli
- В окне терминала, в котором запущен клиент cli, проверьте, может ли
клиент разговаривать с сервером, выполнив команду
ping
и если все пойдет хорошо, должен быть возвращенPONG
Установите Python Virtual Env и зависимости
Теперь я могу перейти к созданию виртуальной среды Python3 и установке пакетов зависимостей, необходимых для этого проекта.
Для начала я создам каталог для размещения вещей под названием image_parroter, а затем внутри него я создам свою виртуальную среду. Все команды, начиная с этого момента, будут иметь только версию Unix, но большинство из них, если не все, будут одинаковыми для среды Windows.
$ mkdir image_parroter
$ cd image_parroter
$ python3 -m venv venv
$ source venv/bin/activate
Теперь, когда виртуальная среда активирована, я могу установить пакеты Python.
(venv) $ pip install Django Celery redis Pillow django-widget-tweaks
(venv) $ pip freeze > requirements.txt
- Pillow - это не связанный с сельдереем пакет Python для обработки изображений, который я буду использовать позже в этом руководстве для демонстрации реального варианта использования сельдерея.
- Django Widget Tweaks - это плагин Django, обеспечивающий гибкость в отображении входных данных формы.
Настройка проекта Django
Далее я создаю проект Django с именем image_parroter, а затем приложение Django с именем thumbnailer.
(venv) $ django-admin startproject image_parroter
(venv) $ cd image_parroter
(venv) $ python manage.py startapp thumbnailer
На данный момент структура каталогов выглядит следующим образом:
$ tree -I venv
.
└── image_parroter
├── image_parroter
│  ├── __init__.py
│  ├── settings.py
│  ├── urls.py
│  └── wsgi.py
├── manage.py
└── thumbnailer
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│  └── __init__.py
├── models.py
├── tests.py
└── views.py
Чтобы интегрировать Celery в этот проект Django, я добавляю новый модуль
image_parroter / image_parrroter / celery.py в соответствии с
соглашениями, описанными в
документации
Celery. В этом новом модуле Python я импортирую пакет os
Celery
из
пакета celery.
Модуль os
используется для связывания переменной среды
DJANGO_SETTINGS_MODULE
под названием DJANGO_SETTINGS_MODULE с модулем
настроек проекта Django. После этого я создаю экземпляр экземпляра
Celery
класса для создания celery_app
переменного экземпляра. Затем
я обновляю конфигурацию приложения Celery настройками, которые вскоре
помещаю в файл настроек проекта Django, идентифицируемый с помощью
префикса CELERY_. Наконец, я приказываю только что созданному
celery_app
автоматически обнаруживать задачи в проекте.
Завершенный модуль celery.py показан ниже:
# image_parroter/image_parroter/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'image_parroter.settings')
celery_app = Celery('image_parroter')
celery_app.config_from_object('django.conf:settings', namespace='CELERY')
celery_app.autodiscover_tasks()
Теперь в модуле settings.py проекта, в самом низу, я определяю раздел для настроек сельдерея и добавляю настройки, которые вы видите ниже. Эти настройки говорят Celery использовать Redis в качестве брокера сообщений, а также где к нему подключиться. Они также говорят Celery ожидать, что сообщения будут передаваться между очередями задач Celery и брокером сообщений Redis в mime-типе application / json.
# image_parroter/image_parroter/settings.py
... skipping to the bottom
# celery
CELERY_BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
Затем мне нужно убедиться, что ранее созданное и настроенное приложение для сельдерея внедряется в приложение Django при его запуске. Это делается путем импорта приложения Celery в основной сценарий __init__.py проекта Django и явной регистрации его в качестве символа пространства имен в пакете Django "image_parroter".
# image_parroter/image_parroter/__init__.py
from .celery import celery_app
__all__ = ('celery_app',)
Я продолжаю следовать предложенным соглашениям, добавляя новый модуль с
именем tasks.py в приложение «thumbnailer». Внутри модуля tasks.py я
импортирую shared_tasks
и использую его для определения функции задачи
сельдерея с именем adding_task
, как показано ниже.
# image_parroter/thumbnailer/tasks.py
from celery import shared_task
@shared_task
def adding_task(x, y):
return x + y
Наконец, мне нужно добавить приложение thumbnailer в список
INSTALLED_APPS
в модуле settings.py проекта image_parroter. Пока я
там, я должен также добавить приложение "widget_tweaks", которое будет
использоваться для управления отображением ввода формы, которое я буду
использовать позже, чтобы позволить пользователям загружать файлы.
# image_parroter/image_parroter/settings.py
... skipping to the INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'thumbnailer.apps.ThumbnailerConfig',
'widget_tweaks',
]
Теперь я могу все проверить, используя несколько простых команд на трех терминалах.
В одном терминале мне нужно запустить redis-сервер, например:
$ redis-server
48621:C 21 May 21:55:23.706 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
48621:C 21 May 21:55:23.707 # Redis version=4.0.8, bits=64, commit=00000000, modified=0, pid=48621, just started
48621:C 21 May 21:55:23.707 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
48621:M 21 May 21:55:23.708 * Increased maximum number of open files to 10032 (it was originally set to 2560).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 4.0.8 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 48621
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
48621:M 21 May 21:55:23.712 # Server initialized
48621:M 21 May 21:55:23.712 * Ready to accept connections
Во втором терминале с активным экземпляром виртуальной среды Python, установленным ранее, в корневом каталоге пакета проекта (том самом, который содержит модуль manage.py) я запускаю программу celery.
(venv) $ celery worker -A image_parroter --loglevel=info
-------------- [email protected] v4.3.0 (rhubarb)
---- **** -----
--- * *** * -- Darwin-18.5.0-x86_64-i386-64bit 2019-05-22 03:01:38
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: image_parroter:0x110b18eb8
- ** ---------- .> transport: redis://localhost:6379//
- ** ---------- .> results: redis://localhost:6379/
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. thumbnailer.tasks.adding_task
В третьем и последнем терминале, снова с активной виртуальной средой
Python, я могу запустить оболочку Django Python и протестировать мою
adding_task
, например:
(venv) $ python manage.py shell
Python 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 11:07:29)
>>> from thumbnailer.tasks import adding_task
>>> task = adding_task.delay(2, 5)
>>> print(f"id={task.id}, state={task.state}, status={task.status}")
id=86167f65-1256-497e-b5d9-0819f24e95bc, state=SUCCESS, status=SUCCESS
>>> task.get()
7
Обратите внимание на использование метода .delay(...)
для объекта
adding_task
Это обычный способ передать все необходимые параметры
объекту задачи, с которым работаете, а также инициировать отправку его
брокеру сообщений и очереди задач. Результатом вызова .delay(...)
является возвращаемое значение типа celery.result.AsyncResult
типа
celery.result.AsyncResult. Это возвращаемое значение содержит такую
информацию, как идентификатор задачи, ее состояние выполнения и статус
задачи, а также возможность доступа к любым результатам, полученным
задачей через метод .get()
как показано в примере.
Создание миниатюр изображений в задаче Celery
Теперь, когда настройка шаблонной пластины для интеграции экземпляра Celery с поддержкой Redis в приложение Django больше не актуальна, я могу перейти к демонстрации некоторых более полезных функций с помощью ранее упомянутого приложения для миниатюр.
Вернувшись в модуль tasks.py, я импортирую класс Image
PIL
, затем
добавляю новую задачу с именем make_thumbnails
, которая принимает
путь к файлу изображения и список двух кортежей ширины и высоты для
создания миниатюр.
# image_parroter/thumbnailer/tasks.py
import os
from zipfile import ZipFile
from celery import shared_task
from PIL import Image
from django.conf import settings
@shared_task
def make_thumbnails(file_path, thumbnails=[]):
os.chdir(settings.IMAGES_DIR)
path, file = os.path.split(file_path)
file_name, ext = os.path.splitext(file)
zip_file = f"{file_name}.zip"
results = {'archive_path': f"{settings.MEDIA_URL}images/{zip_file}"}
try:
img = Image.open(file_path)
zipper = ZipFile(zip_file, 'w')
zipper.write(file)
os.remove(file_path)
for w, h in thumbnails:
img_copy = img.copy()
img_copy.thumbnail((w, h))
thumbnail_file = f'{file_name}_{w}x{h}.{ext}'
img_copy.save(thumbnail_file)
zipper.write(thumbnail_file)
os.remove(thumbnail_file)
img.close()
zipper.close()
except IOError as e:
print(e)
return results
Вышеупомянутая задача миниатюр просто загружает файл входного изображения в экземпляр изображения подушки, затем перебирает список измерений, переданный задаче, создавая миниатюру для каждого, добавляя каждую миниатюру в zip-архив, а также очищая промежуточные файлы. Возвращается простой словарь с указанием URL-адреса, с которого можно загрузить zip-архив миниатюр.
Определив задачу с сельдереем, я перехожу к созданию представлений Django для обслуживания шаблона с формой загрузки файла.
Для начала я даю проекту Django место MEDIA_ROOT
котором могут
находиться файлы изображений и zip-архивы (я использовал это в
приведенном выше примере задачи), а также указываю MEDIA_URL
откуда
может быть предоставлен контент. В модуле image_parroter / settings.py я
добавляю MEDIA_ROOT
, MEDIA_URL
, IMAGES_DIR
затем предоставляю
логику для создания этих местоположений, если они не существуют.
# image_parroter/settings.py
... skipping down to the static files section
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.abspath(os.path.join(BASE_DIR, 'media'))
IMAGES_DIR = os.path.join(MEDIA_ROOT, 'images')
if not os.path.exists(MEDIA_ROOT) or not os.path.exists(IMAGES_DIR):
os.makedirs(IMAGES_DIR)
Внутри модуля thumbnailer / views.py я импортирую django.views.View
и
использую его для создания класса HomeView
get
и post
, как
показано ниже.
Метод get
просто возвращает шаблон home.html, который будет создан в
ближайшее время, и передает ему FileUploadForm
состоящий из
ImageField
как показано выше класса HomeView
Метод post
FileUploadForm
используя данные, отправленные в запросе,
проверяет его действительность, затем, если он действителен, сохраняет
загруженный файл в IMAGES_DIR
и make_thumbnails
задачу
make_thumbnails, захватывая id
задачи и статус для передачи в шаблон,
или возвращает форму с ошибками в шаблон home.html.
# thumbnailer/views.py
import os
from celery import current_app
from django import forms
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from .tasks import make_thumbnails
class FileUploadForm(forms.Form):
image_file = forms.ImageField(required=True)
class HomeView(View):
def get(self, request):
form = FileUploadForm()
return render(request, 'thumbnailer/home.html', { 'form': form })
def post(self, request):
form = FileUploadForm(request.POST, request.FILES)
context = {}
if form.is_valid():
file_path = os.path.join(settings.IMAGES_DIR, request.FILES['image_file'].name)
with open(file_path, 'wb+') as fp:
for chunk in request.FILES['image_file']:
fp.write(chunk)
task = make_thumbnails.delay(file_path, thumbnails=[(128, 128)])
context['task_id'] = task.id
context['task_status'] = task.status
return render(request, 'thumbnailer/home.html', context)
context['form'] = form
return render(request, 'thumbnailer/home.html', context)
class TaskView(View):
def get(self, request, task_id):
task = current_app.AsyncResult(task_id)
response_data = {'task_status': task.status, 'task_id': task.id}
if task.status == 'SUCCESS':
response_data['results'] = task.get()
return JsonResponse(response_data)
Под HomeView
я разместил TaskView
который будет использоваться через
запрос AJAX для проверки статуса задачи make_thumbnails
Здесь вы
заметите, что я импортировал current_app
AsyncResult
и использовал
его для получения объекта AsyncResult задачи, связанного с task_id
из
запроса. Я создаю response_data
статуса и идентификатора задачи, а
затем, если статус указывает, что задача выполнена успешно, я получаю
результаты, вызывая метод get()
AsynchResult
присваивая его ключу
results
response_data
должен быть возвращен как JSON в запросчик
HTTP.
Прежде чем я смогу создать пользовательский интерфейс шаблона, мне нужно сопоставить указанные выше классы представлений Django с некоторыми разумными URL-адресами. Я начинаю с добавления модуля urls.py в приложение thumbnailer и определяю следующие URL-адреса:
# thumbnailer/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.HomeView.as_view(), name='home'),
path('task/<str:task_id>/', views.TaskView.as_view(), name='task'),
]
Затем в основной конфигурации URL-адреса проекта мне нужно включить URL-адреса уровня приложения, а также сообщить ему URL-адрес мультимедиа, например:
# image_parroter/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('thumbnailer.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Затем я начинаю создавать простое представление шаблона, чтобы
пользователь мог отправить файл изображения, а также проверить статус
отправленных make_thumbnails
и инициировать загрузку полученных
эскизов. Для начала мне нужно создать каталог для размещения этого
единственного шаблона в каталоге миниатюр, как показано ниже:
(venv) $ mkdir -p thumbnailer/templates/thumbnailer
Затем в этот каталог templates / thumbnailer я добавляю шаблон с именем home.html. Внутри home.html я начинаю с загрузки тегов шаблона "widget_tweaks", а затем перехожу к определению HTML, импортировав фреймворк CSS под названием bulma CSS , а также библиотеку JavaScript под названием Axios.js . В теле HTML-страницы я предоставляю заголовок, заполнитель для отображения сообщения о ходе выполнения и форму загрузки файла.
<!-- templates/thumbnailer/home.html -->
{% load widget_tweaks %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Thumbnailer</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script defer src="https://use.fontawesome.com/releases/v5.0.7/js/all.js"></script>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
Thumbnailer
</a>
</div>
</nav>
<section class="hero is-primary is-fullheight-with-navbar">
<div class="hero-body">
<div class="container">
<h1 class="title is-size-1 has-text-centered">Thumbnail Generator</h1>
<p class="subtitle has-text-centered" id="progress-title"></p>
<div class="columns is-centered">
<div class="column is-8">
<form action="{% url 'home' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="file is-large has-name">
<label class="file-label">
{{ form.image_file|add_class:"file-input" }}
<span class="file-cta">
<span class="file-icon"><i class="fas fa-upload"></i></span>
<span class="file-label">Browse image</span>
</span>
<span id="file-name" class="file-name"
style="background-color: white; color: black; min-width: 450px;">
</span>
</label>
<input class="button is-link is-large" type="submit" value="Submit">
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<script>
var file = document.getElementById('{{form.image_file.id_for_label}}');
file.onchange = function() {
if(file.files.length > 0) {
document.getElementById('file-name').innerHTML = file.files[0].name;
}
};
</script>
{% if task_id %}
<script>
var taskUrl = "{% url 'task' task_id=task_id %}";
var dots = 1;
var progressTitle = document.getElementById('progress-title');
updateProgressTitle();
var timer = setInterval(function() {
updateProgressTitle();
axios.get(taskUrl)
.then(function(response){
var taskStatus = response.data.task_status
if (taskStatus === 'SUCCESS') {
clearTimer('Check downloads for results');
var url = window.location.protocol + '//' + window.location.host + response.data.results.archive_path;
var a = document.createElement("a");
a.target = '_BLANK';
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = 'results.zip';
a.click();
document.body.removeChild(a);
} else if (taskStatus === 'FAILURE') {
clearTimer('An error occurred');
}
})
.catch(function(err){
console.log('err', err);
clearTimer('An error occurred');
});
}, 800);
function updateProgressTitle() {
dots++;
if (dots > 3) {
dots = 1;
}
progressTitle.innerHTML = 'processing images ';
for (var i = 0; i < dots; i++) {
progressTitle.innerHTML += '.';
}
}
function clearTimer(message) {
clearInterval(timer);
progressTitle.innerHTML = message;
}
</script>
{% endif %}
</body>
</html>
Внизу body
элемента я добавил JavaScript, чтобы обеспечить
дополнительное поведение. Сначала я создаю ссылку на поле ввода файла и
регистрирую прослушиватель изменений, который просто добавляет имя
выбранного файла в пользовательский интерфейс после его выбора.
Далее идет более важная часть. Я использую логический if
в шаблоне
Django для проверки наличия task_id
, передаваемого из HomeView
класса HomeView. Это указывает на ответ после make_thumbnails
задачи
make_thumbnails. Затем я использую Django url
тег шаблона для создания
соответствующего статуса задачи проверки URL и начать запрос AJAX
интервального , приуроченный к этому URL , используя библиотеку AXIOS я
упоминал ранее.
Если статус задачи сообщается как «УСПЕШНО», я вставляю ссылку для загрузки в DOM и вызываю ее запуск, инициируя загрузку и очищая интервальный таймер. Если статус - «ОТКАЗ», я просто очищаю интервал, а если статус не «УСПЕШНО» или «ОТКАЗ», я ничего не делаю, пока не будет активирован следующий интервал.
На этом этапе я могу открыть еще один терминал, снова с активной виртуальной средой Python, и запустить сервер разработки Django, как показано ниже:
(venv) $ python manage.py runserver
- Терминалы задач redis-server и celery, описанные ранее, также должны
быть запущены, и если вы не перезапустили рабочий
make_thumbnails
вам нужноCtrl+C
чтобы остановить рабочего, а затем выполнитьcelery worker -A image_parroter --loglevel=info
раз, чтобы перезапустить его. Рабочие группы Celery должны перезапускаться каждый раз, когда выполняется изменение кода, связанного с задачей celery.
Теперь я могу загрузить представление home.html в свой браузер по адресу http: // localhost: 8000 , отправить файл изображения, и приложение должно ответить архивом results.zip, содержащим исходное изображение и миниатюру размером 128x128 пикселей.
Развертывание на сервере Ubuntu
В завершение этой статьи я продемонстрирую, как установить и настроить это приложение Django, которое использует Redis и Celery для асинхронных фоновых задач на сервере LTS Ubuntu v18.
После подключения по SSH к серверу я обновляю его, а затем устанавливаю необходимые пакеты.
# apt-get update
# apt-get install python3-pip python3-dev python3-venv nginx redis-server -y
Я также создаю пользователя с именем «webapp», который дает мне домашний каталог для установки проекта Django.
# adduser webapp
После ввода пользовательских данных я затем добавляю пользователя webapp
в группы sudo и www-data, переключаюсь на пользователя webapp, затем
cd
в его домашний каталог.
# usermod -aG sudo webapp
# usermod -aG www-data webapp
$ su webapp
$ cd
Внутри каталога веб-приложения я могу клонировать репозиторий
image_parroter GitHub, cd
в репо, создать виртуальную среду Python,
активировать ее, а затем установить зависимости из файла
requirements.txt.
$ git clone https://github.com/amcquistan/image_parroter.git
$ python3 -m venv venv
$ . venv/bin/activate
(venv) $ pip install -r requirements.txt
В дополнение к требованиям, которые я только что установил, я хочу добавить новый для контейнера веб-приложения uwsgi, который будет обслуживать приложение Django.
(venv) $ pip install uWSGI
Прежде чем двигаться дальше, самое время обновить файл settings.py,
чтобы изменить значение DEBUG на False и добавить IP-адрес в список
ALLOWED_HOSTS
.
После этого перейдите в каталог проекта Django image_parroter (тот, который содержит модуль wsgi.py) и добавьте новый файл для хранения параметров конфигурации uwsgi с именем uwsgi.ini и поместите в него следующее:
# uwsgi.ini
[uwsgi]
chdir=/home/webapp/image_parroter/image_parroter
module=image_parroter.wsgi:application
master=True
processes=4
harakiri=20
socket=/home/webapp/image_parroter/image_parroter/image_parroter/webapp.sock
chmod-socket=660
vacuum=True
logto=/var/log/uwsgi/uwsgi.log
die-on-term=True
Прежде чем я забуду, я должен пойти дальше и добавить каталог журналов и дать ему соответствующие разрешения и права собственности.
(venv) $ sudo mkdir /var/log/uwsgi
(venv) $ sudo chown webapp:www-data /var/log/uwsgi
Затем я создаю служебный файл systemd для управления сервером приложений
uwsgi, который находится в /etc/systemd/system/uwsgi.service
и
содержит следующее:
# uwsgi.service
[Unit]
Description=uWSGI Python container server
After=network.target
[Service]
User=webapp
Group=www-data
WorkingDirectory=/home/webapp/image_parroter/image_parroter
Environment="/home/webapp/image_parroter/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin"
ExecStart=/home/webapp/image_parroter/venv/bin/uwsgi --ini image_parroter/uwsgi.ini
[Install]
WantedBy=multi-user.target
Теперь я могу запустить службу uwsgi, проверить ее статус и включить ее, чтобы она запускалась автоматически при загрузке.
(venv) $ sudo systemctl start uwsgi.service
(venv) $ sudo systemctl status uwsgi.service
(venv) $ sudo systemctl enable uwsgi.service
На этом этапе приложение Django и служба uwsgi настроены, и я могу перейти к настройке redis-server.
Я лично предпочитаю использовать службы systemd, поэтому я отредактирую
/etc/redis/redis.conf
конфигурации /etc/redis/redis.conf, установив
параметр supervised
systemd
. После этого я перезапускаю
redis-server, проверяю его статус и включаю запускаться при загрузке.
(venv) $ sudo systemctl restart redis-server
(venv) $ sudo systemctl status redis-server
(venv) $ sudo systemctl enable redis-server
Далее нужно настроить сельдерей. Я начинаю этот процесс с создания места ведения журнала для Celery и даю этому месту соответствующие разрешения и права собственности, например:
(venv) $ sudo mkdir /var/log/celery
(venv) $ sudo chown webapp:www-data /var/log/celery
После этого я добавляю файл конфигурации Celery с именем celery.conf в тот же каталог, что и файл uwsgi.ini, описанный ранее, помещая в него следующее:
# celery.conf
CELERYD_NODES="worker1 worker2"
CELERY_BIN="/home/webapp/image_parroter/venv/bin/celery"
CELERY_APP="image_parroter"
CELERYD_MULTI="multi"
CELERYD_PID_FILE="/home/webapp/image_parroter/image_parroter/image_parroter/%n.pid"
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
CELERYD_LOG_LEVEL="INFO"
Чтобы завершить настройку сельдерея, я добавляю его собственный
служебный файл systemd в /etc/systemd/system/celery.service
и помещаю
в него следующее:
# celery.service
[Unit]
Description=Celery Service
After=network.target
[Service]
Type=forking
User=webapp
Group=webapp
EnvironmentFile=/home/webapp/image_parroter/image_parroter/image_parroter/celery.conf
WorkingDirectory=/home/webapp/image_parroter/image_parroter
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
[Install]
WantedBy=multi-user.target
Последнее, что нужно сделать, это настроить nginx для работы в качестве
обратного прокси для приложения uwsgi / django, а также для обслуживания
содержимого в каталоге мультимедиа. Я делаю это, добавляя конфигурацию
nginx в /etc/nginx/sites-available/image_parroter
, которая содержит
следующее:
server {
listen 80;
server_name _;
location /favicon.ico { access_log off; log_not_found off; }
location /media/ {
root /home/webapp/image_parroter/image_parroter;
}
location / {
include uwsgi_params;
uwsgi_pass unix:/home/webapp/image_parroter/image_parroter/image_parroter/webapp.sock;
}
}
Затем я удаляю конфигурацию nginx по умолчанию, что позволяет мне
использовать server_name _;
чтобы перехватить весь http-трафик на порт
80, я создаю символическую ссылку между конфигурацией, которую я только
что добавил в каталог "sites-available", с каталогом "sites-enabled"
рядом с ним.
$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/image_parroter /etc/nginx/sites-enabled/image_parroter
Как только это будет сделано, я могу перезапустить nginx, проверить его статус и разрешить запуск при загрузке.
$ sudo systemctl restart nginx
$ sudo systemctl status nginx
$ sudo systemctl enable nginx
На этом этапе я могу указать своему браузеру IP-адрес этого сервера Ubuntu и протестировать приложение для миниатюр.
{.ezlazyload .img-responsive}
Заключение
В этой статье описывается, почему и как использовать Celery для общей цели запуска асинхронной задачи, которая запускается и выполняется последовательно до завершения. Это приведет к значительному улучшению взаимодействия с пользователем, уменьшив влияние длинных путей кода, которые не позволяют серверу веб-приложений обрабатывать дальнейшие запросы.
Я приложил все усилия, чтобы предоставить подробное объяснение процесса от начала до конца, начиная с настройки среды разработки, реализации задач сельдерея, создания задач в коде приложения Django, а также получения результатов через Django и некоторого простого JavaScript.
Спасибо за чтение и, как всегда, не стесняйтесь комментировать или критиковать ниже.