Образовательный проект «SnakeProject» Михаила Козлова

Навигация

⇒ FreeBSD and Nix ⇐

CISCO

Voice(Asterisk\Cisco)

Microsoft

Powershell

Python

SQL\T-SQL

Общая

WEB Разработка

ORACLE SQL \ JAVA

Мото

Стрельба, пневматика, оружие

Саморазвитие и психология


Простейшее объяснение Docker Python Django Celery PostgreSQL Nginx


Простейшее объяснение Docker Python Django Celery PostgreSQL Nginx 


Пример проекта

Ссылка на код проекта будет в конце статьи

Создадим простое Django веб-приложение, которое добавит два числа в фоновом режиме

В базе данных будет модель назначения, в ней будут следующие поля:
 - first_term
 - second_term
 - sum

Значения first_term и second_term будут предоставлены REST API
Сумма будет вычислена в фоновом режиме с помощью Celery после создания объекта

Проект будет упакован с помощью docker-compose
Список контейнеров: ngnix, server, worker, redis, db


Настройка среды

Создайте новый каталог или новый репозиторий git и запустите там новую виртуальную среду:
# virtualenv venv
# source venv/bin/activate

Зависимости проекта, requirements.txt:
django
djangorestframework
markdown
django-filter
celery[redis]
psycopg2-binary

# pip install -r requirements.txt

На этом этапе предполагается, что у вас локально установлен и работает сервер redis:
# redis-cli

Если нет, то можете поставить по моей инструкции у Ubuntu:
https://snakeproject.ru/rubric/article.php?art=red25072023

Мы не будем сейчас использовать Postgres локально, проверим на SQLite


Запуск проекта Django

Запустим проект Django с помощью инструмента django-admin:
# django-admin startproject backend

Приведенная выше команда инициализирует пустой проект Django:
backend/
├── backend
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Добавим новое приложение Django:
# cd backend && python manage.py startapp assignments

Новая структура:
backend/
├── assignments
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── backend
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Запустите веб-сервер разработки Django с помощью Python manage.py:
# python manage.py runserver

По умолчанию сервер запускается на порту 8000 по адресу 127.0.0.1 


Модель базы данных назначения

Обновите переменную INSTALLED_APPS в backend/backend/settings.py:
# остальная часть кода ...
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",             # DRF 
    "assignments",                # new app
]
# остальная часть кода ...

Создадим модель бд для объектов назначения

Отредактируйте backend/assignments/models.py:
from django.db import models
class Assignment(models.Model):
    first_term = models.DecimalField(
        max_digits=5, decimal_places=2, null=False, blank=False
    )
    second_term = models.DecimalField(
        max_digits=5, decimal_places=2, null=False, blank=False
    )
    # сумма должна быть равна first_term + second_term
    # ее значение будет вычислено в Celery
    sum = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)

Нужно создать и применить миграции:
# python manage.py makemigrations
# python manage.py migrate

Мы будем использовать DRF ModelViewSet для создания CRUD REST API для наших моделей

Добавьте файл serializers.py в каталог backend/assignments:
from rest_framework import serializers
from assignments.models import Assignment
class AssignmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Assignment
        read_only_fields = ("id", "sum")
        fields = ("id", "first_term", "second_term", "sum")

Поля код и сумма, доступны только для чтения
Идентификатор устанавливается автоматически в базе данных
Значение суммы будет вычисляться celery

backend/assignments/views.py:
from django.db import transaction
from rest_framework import viewsets
from rest_framework.exceptions import APIException
from assignments.models import Assignment
from assignments.serializers import AssignmentSerializer
from assignments.tasks import task_execute
class AssignmentViewSet(viewsets.ModelViewSet):
    serializer_class = AssignmentSerializer
    queryset = Assignment.objects.all()
    def perform_create(self, serializer):
        try:
            with transaction.atomic():
                # сохранить экземпляр
                instance = serializer.save()
                instance.save()
                # создание параметров задачи
                job_params = {"db_id": instance.id}
                # отправить задачу для выполнения в фоновом режиме
                transaction.on_commit(lambda: task_execute.delay(job_params))
        except Exception as e:
            raise APIException(str(e))

ModelViewSet выполняет здесь работу с базовой реализацией CRUD
Мы перезаписываем функцию perform_create для отправки фоновой задачи после создания объекта
Создание объекта и отправка фоновых задач находятся внутри транзакции

Реализуйте функцию task_execute, добавьте tasks.py файл в каталог backend/assignments:
from celery import shared_task
from assignments.models import Assignment
@shared_task()
def task_execute(job_params):
    assignment = Assignment.objects.get(pk=job_params["db_id"])
    assignment.sum = assignment.first_term + assignment.second_term
    assignment.save()

task_execute - общая задача, она будет обнаружена Celery
Принимает один аргумент job_params, который имеет db_id, который является идентификатором объекта присваивания
Мы просто получаем объект по идентификатору, вычисляем сумму и сохраняем объект

Нужно связать конечные точки URL-адресов назначений, добавьте новый файл urls.py в backend/assignments:
from django.urls import re_path
from rest_framework.routers import DefaultRouter
from assignments.views import AssignmentViewSet
router = DefaultRouter()
router.register(r"assignments", AssignmentViewSet, basename="assignments")
assignments_urlpatterns = router.urls

Мы используем DRF DefaultRouter для генерации конечных точек CRUD API.

Добавьте assignments_urlpatterns в main urls.py, отредактируйте файл backend/backend/urls.py:
from django.contrib import admin
from django.urls import path
from assignments.urls import assignments_urlpatterns
urlpatterns = [
    path("admin/", admin.site.urls),
]
urlpatterns += assignments_urlpatterns


Настройка Celery

Нам нужно настроить Celery для работы с Django

Добавьте новый файл celery.py в backend/backend directory:
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
app = Celery("backend")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

Обновите backend/backend/__init__.py:
from .celery import app as celery_app
__all__ = ("celery_app",)

Нам нужно добавить переменные конфигурации в конце backend/backend/settings.py:
# остальная часть кода ...
# брокер celery и результат
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"


Локальный запуск

Запустите сервер разработки Django в первом терминале:
python manage.py runserver

Во втором терминале, запустите программу Celery worker:
# please run in the backend directory (the same dir as runserver command)
celery -A backend worker --loglevel=info --concurrency 1 -E

Запустите ее во внутреннем каталоге (тот же каталог, что и команда runserver)

Откройте веб-браузер по адресу 127.0.0.1:8000/assignments
Создайте новый объект Assignment, нажав кнопку POST
После создания объекта сумма должна быть равна нулю

Нажмите ПОЛУЧИТЬ в правом верхнем углу, чтобы получить список всех назначений
Вы должны увидеть вычисленное значение суммы

Это было вычислено в фоновом режиме с помощью Celery framework
Мы используем здесь очень простой пример, который выполняется быстро
В реальном мире выполнение фоновой задачи может занять даже часы - зависит от проекта


Создайте Dockerfile

Пришло время создать Dockerfile для сервера и worker

Сервер и worker будут работать в разных контейнерах, но у них будет один и тот же Dockerfile

Добавьте новые каталоги:
docker 
docker/backend

Добавьте новый файл Dockerfile в docker/backend:
FROM python:3.8.15-alpine
RUN apk update && apk add python3-dev gcc libc-dev
WORKDIR /app
RUN pip install --upgrade pip
RUN pip install gunicorn
ADD ./requirements.txt /app/
RUN pip install -r requirements.txt
ADD ./backend /app/backend
ADD ./docker /app/docker
RUN chmod +x /app/docker/backend/server-entrypoint.sh
RUN chmod +x /app/docker/backend/worker-entrypoint.sh

строка 1: мы используем python: 3.8.15-alpine image в качестве базы
строка 3: мы устанавливаем библиотеки разработки gcc и python для установки и сборки новых пакетов
строка 5: добавляем каталог приложения и устанавливаем его как рабочий каталог
строка 6: копируем requirements.txt файл в изображение
строки 8-10: обновляем pip, устанавливаем gunicorn и необходимые пакеты
строки 12-13: скопируйте каталоги серверной части и docker в образ docker
строки 15-16: добавьте права на выполнение скриптам entrypoint


Скрипты Entrypoint

Добавим скрипт, который запустит сервер gunicorn

Добавьте server-entrypoint.sh файл в директорию docker/backend:
#!/bin/sh
until cd /app/backend
do
    echo "Waiting for server volume..."
done
until python manage.py migrate
do
    echo "Waiting for db to be ready..."
    sleep 2
done
python manage.py collectstatic --noinput
# python manage.py createsuperuser --noinput
gunicorn backend.wsgi --bind 0.0.0.0:8000 --workers 4 --threads 4
# debug
#python manage.py runserver 0.0.0.0:8000

Пояснения к server-entrypoint.sh скрипту:
строки 3-6: ждем, пока будет готов каталог /app/backend.
строки 9-13: ждем, пока база данных будет готова, и запускаем миграцию.
строка 16: собираем статические файлы.
строка 20: запуск сервера gunicorn.
строка 23: Команда запуска сервера разработки(При использовании комментируйте строку 20)

У воркера будет отдельный скрипт в worker-entrypoint.sh:
#!/bin/sh
until cd /app/backend
do
    echo "Waiting for server volume..."
done
# run a worker
celery -A backend worker --loglevel=info --concurrency 1 -E

Скрипт запускает только одного воркера
Вы можете увеличить количество воркеров, изменив параметр --concurrency


Конфигурация Nginx

Сервер Nginx нужен для прокси-запросов к серверу gunicron (Django) и для обслуживания статических файлов
Будем использовать образ docker по умолчанию (nginx: 1.23-alpine)
Нам нужно предоставить файл конфигурации для настройки работы сервера

Cоздайте новый каталог ddocker/nginx и добавьте туда файл default.conf:
server {
    listen 80;
    server_name _;
    server_tokens off;
    client_max_body_size 20M;
    location / {
        try_files $uri @proxy_api;
    }
    location /admin {
        try_files $uri @proxy_api;
    }
    location @proxy_api {
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass   http://server:8000;
    }
    location /django_static/ {
        autoindex on;
        alias /app/backend/django_static/;
    }   
}

Сервер будет отправлять все запросы / и /admin в @proxy_api, являющимся gunicorn, обслуживающим Django
Запросы /django_static/ обслуживаются /app/backend/django_static/ - путь Django сбора статических файлов

 

Создаем docker-compose

У нас готов Dockerfile для сервера и воркера
Конфигурация Nginx ожидает запросов

Давайте добавим docker-compose.yml в наш проект:
version: '2'
services:
    nginx:
        restart: always
        image: nginx:1.23-alpine
        ports:
            - 80:80
        volumes:
            - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
            - static_volume:/app/backend/django_static
    server:
        restart: unless-stopped
        build:
            context: .
            dockerfile: ./docker/backend/Dockerfile
        entrypoint: /app/docker/backend/server-entrypoint.sh
        volumes:
            - static_volume:/app/backend/django_static
        expose:
            - 8000     
        environment:
            DEBUG: "True"
            CELERY_BROKER_URL: "redis://redis:6379/0"
            CELERY_RESULT_BACKEND: "redis://redis:6379/0"
            DJANGO_DB: postgresql
            POSTGRES_HOST: db
            POSTGRES_NAME: postgres
            POSTGRES_USER: postgres
            POSTGRES_PASSWORD: postgres
            POSTGRES_PORT: 5432
    worker:
        restart: unless-stopped
        build:
            context: .
            dockerfile: ./docker/backend/Dockerfile
        entrypoint: /app/docker/backend/worker-entrypoint.sh
        volumes:
            - static_volume:/app/backend/django_static
        environment:
            DEBUG: "True"
            CELERY_BROKER_URL: "redis://redis:6379/0"
            CELERY_RESULT_BACKEND: "redis://redis:6379/0"
            DJANGO_DB: postgresql
            POSTGRES_HOST: db
            POSTGRES_NAME: postgres
            POSTGRES_USER: postgres
            POSTGRES_PASSWORD: postgres
            POSTGRES_PORT: 5432
        depends_on:
            - server
            - redis
    redis:
        restart: unless-stopped
        image: redis:7.0.5-alpine 
        expose:
            - 6379
    db:
        image: postgres:13.0-alpine
        restart: unless-stopped
        volumes:
            - postgres_data:/var/lib/postgresql/data/
        environment:
            POSTGRES_DB: postgres
            POSTGRES_USER: postgres
            POSTGRES_PASSWORD: postgres
        expose:
            - 5432
volumes:
    static_volume: {}
    postgres_data: {}

строки 4-11 - это определение контейнера nginx.
В нем используется стандартный nginx: 1.23-alpine image.
Копируем файл конфигурации в строке 10.
Мы добавляем static_volume в строку 11.
Тот же static_volume будет смонтирован для серверного контейнера.
Все статические файлы будут отправлены туда.
За пределами docker доступен порт 80, к нему можно подключиться из внешнего мира.

строки 12-31 - это наш сервер Django.
Он создан с помощью /docker/backend/Dockerfile (строка 16).
В строке 19 установлен static_volume .
Он предоставляет порт 8000 - к нему можно получить доступ только внутри docker-compose.
У нас есть переменные среды, установленные в строках 22-31.
Контейнер выполнит server-netrypoint.sh скрипт (строка 17).

строки 32-52 - это рабочий контейнер.
Он не предоставляет никаких портов.
Это зависит от контейнеров redis и сервера (строки 50-52).

строки 53-57 - это контейнер Redis.
Он предоставляет внутренний порт 6379 (недоступен извне из docker-compose).

строки 58-68 - это база данных Postgres.
Он предоставляет внутренний порт 5432.
В строках 63-66 есть переменные окружения для инициализации базы данных при запуске.
Все файлы базы данных хранятся в томе postgres_data.

строки 70-72 - определение томов, используемых в docker-compose.

Мы почти готовы начать использовать docker-compose
Нам нужно обновить конфигурацию Django для чтения переменных среды

Отредактируйте backend/backend/settings.py:
import os
#
# rest of the code ...
#
# set variables
SECRET_KEY = os.environ.get(
    "SECRET_KEY", "django-insecure-6hdy-)5o6k6it_6x%s#u0#guc3(au!=v%%qb674(upu6rrht7b"
)
DEBUG = os.environ.get("DEBUG", True)
ALLOWED_HOSTS = ["127.0.0.1", "0.0.0.0"]
if os.environ.get("ALLOWED_HOSTS") is not None:
    try:
        ALLOWED_HOSTS += os.environ.get("ALLOWED_HOSTS").split(",")
    except Exception as e:
        print("Cant set ALLOWED_HOSTS, using default instead")
#
# rest of the code ...
#
# set database, it can be set to SQLite or Postgres
DB_SQLITE = "sqlite"
DB_POSTGRESQL = "postgresql"
DATABASES_ALL = {
    DB_SQLITE: {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    },
    DB_POSTGRESQL: {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": os.environ.get("POSTGRES_HOST", "localhost"),
        "NAME": os.environ.get("POSTGRES_NAME", "postgres"),
        "USER": os.environ.get("POSTGRES_USER", "postgres"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD", "postgres"),
        "PORT": int(os.environ.get("POSTGRES_PORT", "5432")),
    },
}
DATABASES = {"default": DATABASES_ALL[os.environ.get("DJANGO_DB", DB_SQLITE)]}
#
# rest of the code ...
#
# set static URL address and path where to store static files
STATIC_URL = "/django_static/"
STATIC_ROOT = BASE_DIR / "django_static"
#
# rest of the code ...
#
# celery broker and result
CELERY_BROKER_URL = os.environ.get("BROKER_URL", "redis://localhost:6379/0")
CELERY_RESULT_BACKEND = os.environ.get("RESULT_BACKEND", "redis://localhost:6379/0")

Структура проекта на этом этапе должна выглядеть следующим образом:
├── backend
│   ├── assignments
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── __init__.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── tasks.py
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── backend
│   │   ├── asgi.py
│   │   ├── celery.py
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── db.sqlite3
│   └── manage.py
├── docker
│   ├── backend
│   │   ├── Dockerfile
│   │   ├── server-entrypoint.sh
│   │   └── worker-entrypoint.sh
│   └── nginx
│       └── default.conf
├── docker-compose.yml
├── LICENSE
├── README.md
└── requirements.txt


Команды docker-compose

Запуск в главном каталоге проекта:
sudo docker-compose up --build -d

Остановка:
sudo docker-compose down

Для входа в запущенный контейнер:
sudo docker exec -it <container_name> bash

Введите адрес в свой веб-браузер 0.0.0.0


Краткие сведения

Мы создали простое веб-приложение с Django и Celery

Мы использовали базу данных Postgres и Redis для обеспечения связи между Django и Celery

Проект был переведен в контейнеры docker благодаря docker-compose

Весь код из этой статьи доступен в репозитории GitHub, удачи!

https://github.com/saasitive/docker-compose-django-celery-redis-postgres

 


Комментарии пользователей

Эту новость ещё не комментировалиНаписать комментарий
Анонимам нельзя оставоять комментарии, зарегистрируйтесь!

Контакты Группа ВК Сборник материалов по Cisco, Asterisk, Windows Server, Python и Django, SQL и T-SQL, FreeBSD и LinuxКод обмена баннерами Видео к IT статьям на YoutubeВидео на другие темы Смотреть
Мои друзья: Советы, помощь, инструменты для сис.админа, статическая и динамическая маршрутизация, FreeBSD

© Snakeproject.ru создан в 2013 году.
При копировании материала с сайта - оставьте ссылку.
Весь материал на сайте носит ознакомительный характер,
за его использование другими людьми, автор ответственности не несет.

Рейтинг@Mail.ru
Рейтинг@Mail.ru Яндекс.Метрика





Поддержать автора и проект