Introducción
Contenido
En este tutorial, proporcionaré una comprensión general de por qué las colas de mensajes de apio son valiosas junto con cómo utilizar el apio junto con Redis en una aplicación Django. Para demostrar los detalles de la implementación, construiré una aplicación de procesamiento de imágenes minimalista que genera miniaturas de las imágenes enviadas por los usuarios.
Se cubrirán los siguientes temas:
- Antecedentes de las colas de mensajes con apio y redis
- Configuración del desarrollador local con Django, Celery y Redis
- Creación de miniaturas de imágenes dentro de una tarea de apio
- Implementar en un servidor Ubuntu
El código para este ejemplo se puede encontrar en GitHub junto con las instrucciones de instalación y configuración si solo desea saltar directamente a una aplicación funcionalmente completa; de lo contrario, durante el resto del artículo, lo guiaré a través de cómo construir todo desde cero.
Antecedentes de las colas de mensajes con apio y redis
Celery es un paquete de software de cola de tareas basado en Python que permite la ejecución de cargas de trabajo computacionales asíncronas impulsadas por información contenida en mensajes que se producen en el código de la aplicación (Django en este ejemplo) destinado a una cola de tareas de Celery. El apio también se puede utilizar para ejecutar tareas repetibles, periódicas (es decir, programadas), pero ese no será el enfoque de este artículo.
El apio se utiliza mejor junto con una solución de almacenamiento que a menudo se denomina agente de mensajes. Un corredor de mensajes común que se utiliza con el apio es Redis, que es un almacén de datos de valor clave en memoria de alto rendimiento. Específicamente, Redis se usa para almacenar mensajes producidos por el código de la aplicación que describen el trabajo a realizar en la cola de tareas de Celery. Redis también sirve como almacenamiento de los resultados de las colas de apio que luego son recuperados por los consumidores de la cola.
Configuración del desarrollador local con Django, Celery y Redis
Comenzaré con la parte más difícil primero, que es la instalación de Redis.
Instalación de Redis en Windows
- Descargue el archivo zip de Redis y descomprímalo en algún directorio
- Busque el archivo llamado redis-server.exe y haga doble clic para iniciar el servidor en una ventana de comandos
- Del mismo modo, busque otro archivo llamado redis-cli.exe y haga doble clic en él para abrir el programa en una ventana de comando separada
- Dentro de la ventana de comandos que ejecuta el cliente cli, pruebe para asegurarse de que el cliente puede hablar con el servidor emitiendo el comando
ping
y, si todo va bien, sePONG
debe devolver una respuesta de
Instalación de Redis en Mac OSX / Linux
- Descargue el archivo tarball de Redis y extráigalo en algún directorio
- Ejecute el archivo make con
make install
para construir el programa - Abra una ventana de terminal y ejecute el
redis-server
comando - En otra ventana de terminal, ejecuta
redis-cli
- Dentro de la ventana de la terminal que ejecuta el cliente cli, pruebe para asegurarse de que el cliente pueda hablar con el servidor emitiendo el comando
ping
y, si todo va bien, sePONG
debe devolver una respuesta de
Instale Python Virtual Env y las dependencias
Ahora puedo pasar a crear un entorno virtual Python3 e instalar los paquetes de dependencia necesarios para este proyecto.
Para comenzar, crearé un directorio para albergar cosas llamado image_parroter y luego dentro de él crearé mi entorno virtual. Todos los comandos de aquí en adelante serán solo del tipo Unix, pero la mayoría, si no todos, serán iguales para un entorno Windows.
Te puede interesar:Python para PNL: creación de un chatbot basado en reglas$ mkdir image_parroter
$ cd image_parroter
$ python3 -m venv venv
$ source venv/bin/activate
Con el entorno virtual ahora activado, puedo instalar los paquetes de Python.
(venv) $ pip install Django Celery redis Pillow django-widget-tweaks
(venv) $ pip freeze > requirements.txt
- Pillow es un paquete de Python no relacionado con el apio para el procesamiento de imágenes que usaré más adelante en este tutorial para demostrar un caso de uso del mundo real para las tareas del apio.
- Django Widget Tweaks es un complemento de Django para proporcionar flexibilidad en cómo se procesan las entradas de formulario.
Configuración del proyecto Django
Continuando, creo un proyecto Django llamado image_parroter y luego una aplicación Django llamada thumbnailer.
(venv) $ django-admin startproject image_parroter
(venv) $ cd image_parroter
(venv) $ python manage.py startapp thumbnailer
En este punto, la estructura del directorio tiene el siguiente aspecto:
$ 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
Para integrar Celery dentro de este proyecto de Django, agrego un nuevo módulo image_parroter / image_parrroter / celery.py siguiendo las convenciones descritas en los documentos de Celery . Dentro de este nuevo módulo de Python, importo el os
paquete y la Celery
clase del paquete de apio.
El os
módulo se usa para asociar una variable de entorno Celery llamada DJANGO_SETTINGS_MODULE
con el módulo de configuración del proyecto Django. Después de eso, creo una instancia de la Celery
clase para crear la celery_app
variable de instancia. Luego actualizo la configuración de la aplicación Celery con configuraciones que pronto colocaré en el archivo de configuración del proyecto Django identificable con un prefijo ‘CELERY_’. Finalmente, le digo a la celery_app
instancia recién creada que descubra automáticamente las tareas dentro del proyecto.
El módulo celery.py completo se muestra a continuación:
# 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()
Ahora, en el módulo settings.py del proyecto, en la parte inferior, defino una sección para la configuración de apio y agrego la configuración que ve a continuación. Estas configuraciones le dicen a Celery que use Redis como el intermediario de mensajes, así como dónde conectarse a él. También le dicen a Celery que espere que los mensajes se pasen de un lado a otro entre las colas de tareas de Celery y el agente de mensajes de Redis para que estén en el tipo mime de 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'
A continuación, necesito asegurarme de que la aplicación de apio creada y configurada previamente se inyecte en la aplicación Django cuando se ejecute. Esto se hace importando la aplicación Celery dentro del script principal __init__.py del proyecto Django y registrándolo explícitamente como un símbolo de espacio de nombres dentro del paquete «image_parroter» de Django.
# image_parroter/image_parroter/__init__.py
from .celery import celery_app
__all__ = ('celery_app',)
Continúo siguiendo las convenciones sugeridas agregando un nuevo módulo llamado tasks.py dentro de la aplicación «thumbnailer». Dentro del módulo tasks.py, importo el shared_tasks
decorador de funciones y lo uso para definir una función de tarea de apio llamada adding_task
, como se muestra a continuación.
# image_parroter/thumbnailer/tasks.py
from celery import shared_task
@shared_task
def adding_task(x, y):
return x + y
Por último, necesito agregar la aplicación de miniaturas a la lista del INSTALLED_APPS
módulo settings.py del proyecto image_parroter. Mientras estoy allí, también debería agregar la aplicación «widget_tweaks» que se usará para controlar la representación de la entrada del formulario que usaré más adelante para permitir que los usuarios carguen archivos.
# 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',
]
Ahora puedo probar cosas usando algunos comandos simples en tres terminales.
En una terminal necesito tener el servidor redis ejecutándose, así:
$ 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
En una segunda terminal, con una instancia activa del entorno virtual Python instalado previamente, en el directorio del paquete raíz del proyecto (el mismo que contiene el módulo manage.py) lanzo el programa 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
En la tercera y última terminal, nuevamente con el entorno virtual Python activo, puedo iniciar el shell Django Python y probar mi adding_task
, así:
(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
Tenga en cuenta el uso del .delay(...)
método en el adding_task
objeto. Esta es la forma común de pasar los parámetros necesarios al objeto de tarea con el que se está trabajando, así como de iniciar su envío al intermediario de mensajes y la cola de tareas. El resultado de llamar al .delay(...)
método es un valor de retorno similar a una promesa del tipo celery.result.AsyncResult
. Este valor de retorno contiene información como la identificación de la tarea, su estado de ejecución y el estado de la tarea junto con la capacidad de acceder a cualquier resultado producido por la tarea a través del .get()
método como se muestra en el ejemplo.
Creación de miniaturas de imágenes dentro de una tarea de apio
Ahora que la configuración de la placa de la caldera para integrar una instancia de Celery respaldada por Redis en la aplicación Django está fuera del camino, puedo pasar a demostrar algunas funciones más útiles con la aplicación de miniaturas mencionada anteriormente.
De vuelta en el módulo tasks.py, importo la Image
clase del PIL
paquete, luego agrego una nueva tarea llamada make_thumbnails
, que acepta una ruta de archivo de imagen y una lista de dimensiones de ancho y alto de 2 tuplas para crear miniaturas.
# 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
La tarea de miniaturas anterior simplemente carga el archivo de imagen de entrada en una instancia de Pillow Image, luego recorre la lista de dimensiones que se pasó a la tarea creando una miniatura para cada una, agregando cada miniatura a un archivo zip mientras también limpia los archivos intermedios. Se devuelve un diccionario simple que especifica la URL desde la que se puede descargar el archivo zip de miniaturas.
Con la tarea de apio definida, paso a construir las vistas de Django para ofrecer una plantilla con un formulario de carga de archivos.
Para comenzar, le doy al proyecto Django una MEDIA_ROOT
ubicación donde pueden residir los archivos de imagen y los archivos zip (usé esto en la tarea de ejemplo anterior) y especifico desde MEDIA_URL
dónde se puede servir el contenido. En el módulo image_parroter / settings.py agrego el MEDIA_ROOT
, MEDIA_URL
, IMAGES_DIR
lugares ajustes a continuación proporcionan la lógica para crear estas ubicaciones si no existen.
# 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)
Dentro del módulo thumbnailer / views.py, importo la django.views.View
clase y la uso para crear una HomeView
clase que contiene métodos get
y post
, como se muestra a continuación.
El get
método simplemente devuelve una plantilla home.html, que se creará en breve, y le entrega FileUploadForm
un ImageField
campo compuesto por un campo como se ve arriba de la HomeView
clase.
El post
método construye el FileUploadForm
objeto usando los datos enviados en la solicitud, verifica su validez, luego, si es válido, guarda el archivo cargado en el IMAGES_DIR
e inicia una make_thumbnails
tarea mientras toma la tarea id
y el estado para pasar a la plantilla, o devuelve el formulario con su errores en la plantilla 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)
Debajo de la HomeView
clase, he colocado una TaskView
clase que se utilizará a través de una solicitud AJAX para verificar el estado de la make_thumbnails
tarea. Aquí notará que importé el current_app
objeto del paquete de apio y lo usé para recuperar el AsyncResult
objeto de la tarea asociado con el task_id
de la solicitud. Creo un response_data
diccionario del estado y la identificación de la tarea, luego, si el estado indica que la tarea se ha ejecutado correctamente, obtengo los resultados llamando al get()
método del AsynchResult
objeto y asignándolo a la results
clave del response_data
que se devolverá como JSON al solicitante HTTP.
Antes de que pueda crear la interfaz de usuario de la plantilla, necesito asignar las clases de vistas de Django anteriores a algunas URL sensibles. Comienzo agregando un módulo urls.py dentro de la aplicación de miniaturas y defino las siguientes 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"),
]
Luego, en la configuración de la URL principal del proyecto, necesito incluir las URL de nivel de la aplicación, así como también informar sobre la URL de los medios, así:
# 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)
A continuación, empiezo a crear una vista de plantilla simple para que un usuario envíe un archivo de imagen, así como para verificar el estado de las make_thumbnails
tareas enviadas e iniciar una descarga de las miniaturas resultantes. Para empezar, necesito crear un directorio para albergar esta plantilla única dentro del directorio de miniaturas, de la siguiente manera:
(venv) $ mkdir -p thumbnailer/templates/thumbnailer
Luego, dentro de este directorio templates / thumbnailer agrego una plantilla llamada home.html. Dentro de home.html empiezo cargando las etiquetas de plantilla «widget_tweaks», luego paso a definir el HTML importando un marco CSS llamado bulma CSS , así como una biblioteca JavaScript llamada Axios.js . En el cuerpo de la página HTML proporciono un título, un marcador de posición para mostrar un mensaje de resultados en progreso y el formulario de carga del archivo.
<!-- 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>
En la parte inferior del body
elemento, agregué JavaScript para proporcionar un comportamiento adicional. Primero creo una referencia al campo de entrada del archivo y registro un oyente de cambios, que simplemente agrega el nombre del archivo seleccionado a la interfaz de usuario, una vez seleccionado.
Luego viene la parte más relevante. Utilizo el if
operador lógico de plantilla de Django para verificar la presencia de un task_id
ser transmitido desde la HomeView
vista de clase. Esto indica una respuesta después de que make_thumbnails
se ha enviado una tarea. Luego uso la url
etiqueta de plantilla de Django para construir una URL de verificación de estado de tarea apropiada y comenzar una solicitud AJAX temporizada por intervalo a esa URL usando la biblioteca Axios que mencioné anteriormente.
Si el estado de una tarea se informa como «ÉXITO», inyecto un enlace de descarga en el DOM y hago que se active, lo que activa la descarga y borro el temporizador de intervalo. Si el estado es «FALLO», simplemente borro el intervalo, y si el estado no es «ÉXITO» ni «FALLO», no hago nada hasta que se invoca el siguiente intervalo.
En este punto, puedo abrir otra terminal, una vez más con el entorno virtual Python activo, e iniciar el servidor de desarrollo Django, como se muestra a continuación:
Te puede interesar:Python para PNL: Creación del modelo TF-IDF desde cero(venv) $ python manage.py runserver
- Los terminales de tareas de redis-server y apio descritos anteriormente también deben estar ejecutándose, y si no ha reiniciado el trabajador de Apio desde que agregó la
make_thumbnails
tarea, querráCtrl+C
detener el trabajador y luegocelery worker -A image_parroter --loglevel=info
volver a ejecutarlo para reiniciarlo. Los trabajadores de apio deben reiniciarse cada vez que se realiza un cambio de código relacionado con la tarea de apio.
Ahora puedo cargar la vista home.html en mi navegador en http: // localhost: 8000 , enviar un archivo de imagen y la aplicación debería responder con un archivo results.zip que contiene la imagen original y una miniatura de 128×128 píxeles.
Implementar en un servidor Ubuntu
Para completar este artículo, mostraré cómo instalar y configurar esta aplicación Django que utiliza Redis y Celery para tareas en segundo plano asincrónicas en un servidor Ubuntu v18 LTS.
Una vez SSH en el servidor, lo actualizo y luego instalo los paquetes necesarios.
# apt-get update
# apt-get install python3-pip python3-dev python3-venv nginx redis-server -y
También hago un usuario llamado «webapp», que me da un directorio de inicio para instalar el proyecto Django.
# adduser webapp
Después de ingresar los datos del usuario, agrego el usuario de la aplicación web a los grupos sudo y www-data, cambio al usuario de la aplicación web y luego cd
a su directorio de inicio.
# usermod -aG sudo webapp
# usermod -aG www-data webapp
$ su webapp
$ cd
Dentro del directorio de la aplicación web, puedo clonar el repositorio image_parroter GitHub, cd
en el repositorio, crear un entorno virtual de Python, activarlo y luego instalar las dependencias del archivo requirements.txt.
$ git clone https://github.com/amcquistan/image_parroter.git
$ python3 -m venv venv
$ . venv/bin/activate
(venv) $ pip install -r requirements.txt
Además de los requisitos que acabo de instalar, quiero agregar uno nuevo para el contenedor de la aplicación web uwsgi que servirá a la aplicación Django.
(venv) $ pip install uWSGI
Antes de continuar, sería un buen momento para actualizar el archivo settings.py para cambiar el valor DEBUG a False y agregar la dirección IP a la lista de ALLOWED_HOSTS
.
Después de eso, muévase al directorio del proyecto Django image_parroter (el que contiene el módulo wsgi.py) y agregue un nuevo archivo para guardar los ajustes de configuración de uwsgi, llamado uwsgi.ini, y coloque lo siguiente en él:
# 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
Antes de que me olvide, debo continuar y agregar el directorio de registro y otorgarle los permisos y la propiedad adecuados.
(venv) $ sudo mkdir /var/log/uwsgi
(venv) $ sudo chown webapp:www-data /var/log/uwsgi
A continuación, hago un archivo de servicio systemd para administrar el servidor de aplicaciones uwsgi, que se encuentra en /etc/systemd/system/uwsgi.service
y contiene lo siguiente:
# 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
Ahora puedo iniciar el servicio uwsgi, verificar que su estado sea correcto y habilitarlo para que se inicie automáticamente al arrancar.
(venv) $ sudo systemctl start uwsgi.service
(venv) $ sudo systemctl status uwsgi.service
(venv) $ sudo systemctl enable uwsgi.service
En este punto, la aplicación Django y el servicio uwsgi están configurados y puedo continuar con la configuración del servidor redis.
Personalmente prefiero usar los servicios systemd, por lo que editaré el /etc/redis/redis.conf
archivo de configuración estableciendo el supervised
parámetro igual a systemd
. Después de eso, reinicio el servidor redis, verifico su estado y lo habilito para que se inicie en el arranque.
(venv) $ sudo systemctl restart redis-server
(venv) $ sudo systemctl status redis-server
(venv) $ sudo systemctl enable redis-server
El siguiente paso es configurar el apio. Comienzo este proceso creando una ubicación de registro para Apio y le doy a esta ubicación los permisos y la propiedad adecuados, así:
(venv) $ sudo mkdir /var/log/celery
(venv) $ sudo chown webapp:www-data /var/log/celery
A continuación, agrego un archivo de configuración de Apio, llamado celery.conf, en el mismo directorio que el archivo uwsgi.ini descrito anteriormente, colocando lo siguiente en él:
# 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"
Para terminar de configurar apio, agrego su propio archivo de servicio systemd en /etc/systemd/system/celery.service
y coloco lo siguiente en él:
# 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
Lo último que debe hacer es configurar nginx para que funcione como un proxy inverso para la aplicación uwsgi / django, así como para servir el contenido en el directorio de medios. Hago esto agregando una configuración nginx en /etc/nginx/sites-available/image_parroter
, que contiene lo siguiente:
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;
}
}
A continuación, elimino la configuración nginx predeterminada que me permite usar server_name _;
para capturar todo el tráfico http en el puerto 80, luego creo un enlace simbólico entre la configuración que acabo de agregar en el directorio «sitios disponibles» al directorio «sitios habilitados» adyacente lo.
$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/image_parroter /etc/nginx/sites-enabled/image_parroter
Una vez hecho esto, puedo reiniciar nginx, verificar su estado y habilitarlo para que se inicie en el arranque.
$ sudo systemctl restart nginx
$ sudo systemctl status nginx
$ sudo systemctl enable nginx
En este punto, puedo apuntar mi navegador a la dirección IP de este servidor Ubuntu y probar la aplicación de miniaturas.
Conclusión
Este artículo describió por qué usar, así como cómo usar, Celery con el propósito común de iniciar una tarea asincrónica, que se activa y se ejecuta en serie hasta su finalización. Esto conducirá a una mejora significativa en la experiencia del usuario, reduciendo el impacto de las rutas de código de larga ejecución que impiden que el servidor de aplicaciones web maneje más solicitudes.
Te puede interesar:Python para NLP: Embeddings de palabras para el aprendizaje profundo en KerasHe hecho todo lo posible para proporcionar una explicación detallada del proceso de principio a fin desde la configuración de un entorno de desarrollo, la implementación de tareas de apio, la producción de tareas en el código de la aplicación Django y el consumo de resultados a través de Django y algo de JavaScript simple.
Gracias por leer y, como siempre, no dude en comentar o criticar a continuación.