Раздел помощи SpaceWeb

Масштабирование Django

26 дек, 2023

При расширении инфраструктуры возникает необходимость в масштабировании. Если вы используете Django, то эта статья предназначена именно для вас. В ней мы рассмотрим эффективные методы масштабирования и обеспечения безопасности на этом фреймворке.

Стоит отметить, что наше руководство ориентировано на серверы разработки Django, которые работают на операционной системе Ubuntu 20.04. 

Зачем нужно масштабировать Django

При использовании горизонтального масштабирования приложения Django наблюдается снижение уровня отказов и увеличение пропускной способности системы. Для реализации этого подхода вводятся дополнительные серверы, на которых запускаются само приложение и интерфейс WSGI. 

Распределение входящих запросов осуществляется через применение обратного прокси, который обрабатывает и перераспределяет клиентские запросы на сервер.

Обратные прокси-серверы не только балансируют рабочие нагрузки между серверами, но также предоставляют дополнительные услуги. Например:

  • кэширование, 
  • сжатие, 
  • шифрование SSL,
  • прерывание TLS. 

В таком контексте Nginx выступает в роли прокси.

Используя Nginx, мы развернем SSL-сертификаты для обеспечения безопасного подключения через HTTPS. Кроме того, Nginx обеспечивает кэширование статического контента, что помогает снизить нагрузку на сервер. 

Конфигурирование каждого из компонентов – это не самая простая задача, поэтому для упрощения процесса настройки и создания связи между компонентами в различных средах развертывания мы воспользуемся Docker.

В первую очередь мы подготовим серверы приложений с установленным Docker. Затем для обеспечения безопасности нашего приложения мы добавим SSL-сертификат Let's Encrypt. Для этого создадим дополнительный сервер с обратным прокси Nginx и контейнером Certbot, который управляет SSL-сертификатами Let’s Encrypt. 

Certbot автоматически обновляет и управляет сертификатами, обеспечивая повышенный уровень безопасности сайта. С поддержкой автоматического обновления SSL-сертификата ваш сайт всегда будет защищенным. 

Также обратный прокси будет находиться перед серверами с распределенной архитектурой и обрабатывать весь входящий трафик, обеспечивая дополнительный уровень безопасности. При этом серверы приложений останутся за брандмауэром – а значит, доступ к ним можно будет получить только через прокси-сервер.

Шаг 1. Подготовьтесь к масштабированию

Для правильной настройки требуется следующее:

  1. Четыре сервера с операционной системой Ubuntu. В нашем случае версия 20.04. 

Первый сервер будет отвечать за запуск экземпляра базы данных. В качестве примера возьмем базу данных PostgreSQL. Конфигурации Postgres потребуется изменить, чтобы разрешить внешние подключения только с IP-адресов сервера приложений. 

На втором и третьем серверах будут размещаться контейнеры Docker, содержащие код вашего приложения. Для обеспечения безопасности потребуется изменить настройки брандмауэра одного из этих серверов, разрешив внешние подключения только с IP-адреса прокси-сервера. 

Четвертый сервер будет выступать в роли прокси-сервера, ответственного за балансировку нагрузки и распределение трафика между двумя контейнерами.

  1. Установка Docker на два сервера приложений и на прокси-сервер.
  2. Приобретение домена. 

Необходимо настроить записи DNS так, чтобы они указывали на уникальный IP-адрес прокси-сервера. Например, в этом руководстве мы будем использовать домен tsdomain.ru.

  1. Настройка службы хранилища объектов S3.
  2. Настройка сервера с PostgreSQL и базы данных. Наша база данных называется mydb.

Шаг 2. Настройте первый сервер для приложения Django

  1. Зайдите на ваш первый сервер и выполните команду git для клонирования ветки django-mydb-docker из репозитория django-mydb.
  2. Перейдите в каталог django-mydb с использованием команды cd:

cd django-mydb

В этом каталоге вы увидите Dockerfile, который используется для создания образа приложения, а также каталог с кодом приложения на Python и файл «env» с переменными, передаваемыми в контейнер при его запуске для настройки его поведения.

  1. Откройте Dockerfile:

cat Dockerfile

Вывод:

FROM python:3.8.1-alpine3.11
ADD django-mydb/requirements.txt /app/requirements.txt
RUN set -ex \
    && apk add --no-cache --virtual .build-deps postgresql-dev build-base \
    && python -m venv /env \
    && /env/bin/pip install --upgrade pip \
    && /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
    && runDeps="$(scanelf --needed --nobanner --recursive /env \
        | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
        | sort -u \
        | xargs -r apk info --installed \
        | sort -u)" \
    && apk add --virtual rundeps $runDeps \
    && apk del .build-deps
ADD django-mydb/app
WORKDIR /app
ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH
EXPOSE 8000
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

Здесь объявим порт 8000 для приема входящего трафика и настроим его для запуска сервера Gunicorn. 

  1. Теперь вы можете собрать образ Docker с использованием команды:

docker build -t django_mydb:v1 .

  1. После создания Docker-образа выведите список доступных образов на сервере с помощью команды: 

docker images

Вывод будет примерно следующий:

REPOSITORY TAG IMAGE ID CREATED SIZE
 mydb  latest  80ec4f33aae1  1 week ago  209MB
 python  3.8.1-alpine3.11   f309434dea3a  11 months ago   101.3MB
  1. Далее необходимо внести изменения в файл «env», который используется при настройке среды выполнения. Этот файл передается в контейнер Docker при запуске. 

Откройте файл «env» с помощью редактора nano: 

nano env

В файле «env» уже есть шаблонный текст, который нужно изменить и заполнить актуальными значениями:

DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=mydb
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info

Где:

  • «DJANGO_SECRET_KEY» генерирует случайное значение. 
  • «DJANGO_ALLOWED_HOSTS» используется для защиты приложения от атак хоста. Можно установить значение «*» для всех хостов (например, при работе в режиме разработки), но в рабочей среде укажите значение вашего домена. В нашем примере – tsdomain.ru.
  • В «DB_DATABASE» укажите имя базы данных PostgreSQL, например, mydb.
  • В «DB_USERNAME» укажите имя пользователя, выбранное для базы данных.
  • В «DB_PASSWORD» установите пароль, выбранный для базы данных.
  • В «DB_HOST» укажите хост, на котором работает экземпляр базы данных.
  • В «DB_PORT» укажите порт базы данных.
  1. После завершения редактирования, сохраните и закройте файл. 

С использованием предоставленных учетных данных вы можете создать схему базы данных, выполнив запуск контейнера и переопределив набор команд CMD в Dockerfile. 

  1. Выполните следующую команду:

docker run --env-file env django_mydb:v1 sh -c "python manage.py makemigrations && python manage.py migrate"

Эта команда запускает образ «django_mydb:v1», передавая измененный файл «env». Код, который начинается с «sh», создает схему базы данных. При первом выполнении команды вы увидите вывод, подтверждающий создание схемы базы данных.

  1. После создания схемы базы данных создайте суперпользователя Django. Выполните следующую команду, чтобы запустить контейнер с интерактивной оболочкой:

docker run -i -t --env-file env django_mydb:v1 sh

Эта команда запускает контейнер для взаимодействия с оболочкой Python. 

  1. Теперь создадим суперпользователя с помощью следующей команды:

python manage.py createsuperuser

  1. Затем введите имя пользователя, адрес электронной почты и пароль, повторите его и нажмите Enter, чтобы создать пользователя.
  2. Выйдите из оболочки, завершив контейнер с помощью сочетания клавиш CTRL+D
  3. После этого необходимо снова запустить контейнер, переопределив команду по умолчанию с помощью «collectstatic». Эта команда сгенерирует статический контент и загрузит его в облачное хранилище MinIO:

docker run --env-file env mydb sh -c "python manage.py collectstatic --noinput"

  1. Таким образом, файл после создания будет загружен в настроенную службу хранилища объектов. 

Вы получите примерно такой вывод:

121 static files copied.

  1. Теперь вы можете запустить приложение без указания каких-либо дополнительных инструкций для переопределения команды CMD, заданной в Dockerfile по умолчанию:

docker run --env-file env -p 80:8000 mydb

  1. Проверьте интерфейс вашего приложения, обратившись к IP-адресу первого сервера в адресной строке браузера: 

http://IP_ПЕРВОГО_СЕРВЕРА. 

Вы увидите ошибку 404. Что неудивительно, так как мы еще не определили никаких настроек для пути "/". 

  1. Попробуйте перейти по адресу http://IP_ПЕРВОГО_СЕРВЕРА/mydb, а затем в панель администратора: http://IP_ПЕРВОГО_СЕРВЕРА/admin.
  2. Введите предварительно установленные учетные данные, чтобы получить доступ к панели администратора. 
  3. Убедившись в правильной настройке контейнера, вы можете закрыть его, нажав CTRL+C в терминале.
  4. Затем необходимо настроить контейнер для автономной работы, позволяя вам выйти из сеанса SSH на первом сервере. В этом вам поможет выполнение команды с флагом «-d» – так, контейнер продолжит работу в режиме detached.

Также рекомендуется присвоить контейнеру имя «mydb» (не забудьте заменить его на имя вашей БД), чтобы легче было его идентифицировать при просмотре списка контейнеров.

docker run -d --rm --name mydb --env-file env -p 80:8000 mydb

  1. После выхода из сеанса SSH на первом сервере перейдите по адресу http://IP_ПЕРВОГО_СЕРВЕРА/mydb в браузере, чтобы удостовериться, что сервер работает должным образом. 

Если вы увидите интерфейс mydb, значит, первый сервер приложений настроен корректно. Теперь мы можем перейти к настройке масштабируемого приложения на втором сервере.

Шаг 3. Настройте второй сервер для приложения Django

Перед тем как приступить к данному этапу, удостоверьтесь, что второй сервер запущен, на нем создан пользователь с правами sudo и установлен Docker. 

Следующий шаг – настройка соединения с сервером, на котором размещена база данных PostgreSQL. Для этого необходимо разрешить IP-адрес второго сервера в конфигурациях UFW и PostgreSQL.

  1. Начнем со входа в базу данных PostgreSQL под пользователем sudo без прав root-доступа. Для добавления правила в UFW выполните следующую команду:

sudo ufw allow from IP_ВТОРОГО_СЕРВЕРА to any port 5432

  1. Затем отредактируйте файл аутентификации клиента PostgreSQL, добавив IP-адрес второго сервера:

sudo nano /etc/postgresql/12/main/pg_hba.conf

  1. Добавьте строку в раздел hosts, указав свой IP-адрес:

host all all IP_ВТОРОГО_СЕРВЕРА/24 md5

  1. Сохраните изменения, после чего перезапустите службу PostgreSQL, чтобы они вступили в силу:

sudo service postgresql restart

  1. Выйдите из базы данных PostgreSQL и продолжите настройку. Войдите на второй сервер по SSH и склонируйте ветку «django-mydb-branch» из репозитория «django-mydb»:

git clone --single-branch --branch django-mydb-docker --depth 1

  1. Затем перейдите в директорию django-mydb и создайте Docker-образ:

docker build -t django-mydb:v1 

  1. Перейдите в директорию django-mydb:

cd django-mydb

  1. Создайте Docker-образ с использованием команды docker build:

docker build -t mydb 

  1. Откройте файл «env» с использованием nano или любого другого текстового редактора:

nano env

В нём вы увидите: 

DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=mydb
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info

Заполните пропущенные данные в соответствии с инструкцией из шага 2. Обязательно измените переменную DJANGO_ALLOWED_HOSTS.

  1. По завершении редактирования сохраните изменения и закройте файл.
  2. Обновите учетные данные MinIO в файле «env» и запустите контейнер в автономном режиме:

docker run -d --rm --name mydb --env-file env -p 80:8000 django-mydb:v1

Эта команда запускает контейнер и обеспечивает его работу в автономном режиме. 

  1. Выйдите из SSH на втором сервере и перейдите по адресу «http://IP_ВТОРОГО_СЕРВЕРА/mydb» в браузере, чтобы удостовериться, что все работает корректно. 

Теперь у нас есть два сервера, на которых запущена идентичная копия масштабируемого приложения Django.

Шаг 4. Настройте Docker для Nginx

Существует несколько подходов к реализации обратного прокси и балансировки нагрузки с использованием Nginx. Один из них – настройка обратного прокси-сервера Nginx отдельно от внутреннего сервера приложений. Именно им мы и воспользуемся. Этот метод предоставляет гибкость в масштабировании как на уровне прокси-сервера Nginx, так и на уровне приложения. Кроме того, вы также сможете добавить нескольких прокси Nginx или использовать облачного решения для балансировки.

Другой метод включает использование одного из внутренних серверов приложений в качестве прокси-сервера Nginx. Он позволит вам проксировать входящие запросы локально и на другие серверы приложений. При необходимости контейнер Nginx легко настраивается на всех внутренних серверах приложений, обеспечивая балансировку нагрузки облачных мощностей для получения и распределения входящего трафика на внутренние серверы приложений.

  1. Для этапа настройки зайдите на четвертый сервер Ubuntu, предназначенный для использования в качестве прокси-сервера Nginx.
  2. Создайте каталог конфигурации с помощью команды: 

mkdir conf

  1. Откройте конфигурацию внутри каталога с помощью команды: 

nano conf/nginx.conf 

Добавьте в файл следующее:

upstream django {
    server IP_ПЕРВОГО_СЕРВЕРА;
    server IP_ВТОРОГО_СЕРВЕРА;
}
server {
    listen 80 default_server;
    return 444;
}
server {
    listen 80;
    listen [::]:80;
    server_name tsdomain.ru;
    return 301 https://$server_name$request_uri;
}
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name tsdomain.ru;
    ssl_certificate /etc/letsencrypt/live/tsdomain.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tsdomain.ru/privkey.pem;
    ssl_session_cache shared:le_nginx_SSL:10m;
    ssl_session_timeout 1440m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
    client_max_body_size 4G;
    keepalive_timeout 5;
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://django;
    }
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
    }
}
 

В этом файле мы добавили блоки «server», «upstream» и «location», чтобы настроить Nginx на перенаправление HTTP-запросов на HTTPS и распределение запросов между двумя серверами приложений, настроенными на предыдущих этапах. 

Важно! Некоторые значения и пути могут отличаться от представленных в данном образце. Более подробную информацию о структуре файла конфигурации Nginx можно найти в официальной документации проекта. 

Шаг 5. Настройте автоматическое обновление Certbot

Certbot – это клиент, который позволяет получать бесплатные TLS-сертификаты от Let's Encrypt. Таким образом, браузеры могут идентифицировать ваши веб-серверы. 

Поскольку мы установили Docker на нашем прокси-сервере Nginx, то будем использовать Docker-образ Certbot для предоставления и обновления этих сертификатов.

Прежде всего, убедитесь, что у вас есть A-запись DNS, указывающая на IP вашего прокси. Вы можете проверить это, запустив Certbot Docker-образ с флагом «--staging». 

docker run -it --rm -p 80:80 --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly --standalone --staging -d ваш_домен.ru

Этот шаг загрузит и запустит Certbot в интерактивном режиме, а также установит соответствие между портом 80 хоста и портом 80 внутри контейнера. 

Мы использовали флаг «-v» для включения двух хост-директорий в контейнер: «/etc/letsencrypt/» и «/var/lib/letsencrypt/». 

Флаг «--standalone» указывает, что мы хотим запустить Certbot без использования Nginx. А флаг «--staging» устанавливается, чтобы Certbot выполнялся на тестовых серверах и проверял ваше доменное имя.

Важно! Сертификат нужно периодически обновлять до истечения срока его действия. Certbot может автоматически обновлять сертификат в фоновом режиме, однако, возможно, вам потребуется включить эту функцию. 

Шаг 6. Защитите внутренние серверы Django

Прокси-сервер декодирует SSL-соединение и передает незашифрованные пакеты на внутренние серверы приложений. Поскольку мы собираемся оградить внутренние серверы от внешнего доступа, как правило, этот уровень безопасности эффективен. 

Тем не менее, если вы развертываете приложения, обрабатывающие конфиденциальные данные, такие как банковская информация или медицинские данные, рекомендуется использовать end-to-end шифрование.

Удостоверьтесь, что все запросы проходят через прокси-сервер. Docker иногда сталкивается с проблемой обхода конфигурации брандмауэра UFW и открывания внешних портов, что может сделать вашу инфраструктуру уязвимой. Тем не менее доступ к веб-страницам все равно остается возможен при посещении любого общедоступного IP-адреса сервера в браузере. 

Одним из способов решения этой проблемы – прямое использование iptables, без UFW. 

На этом этапе вам необходимо внести изменения в конфигурацию UFW для блокировки внешнего доступа к портам хоста, открываемым Docker. 

При запуске Django на серверах приложения мы устанавливаем флаг -p 80:8000 для контейнера Docker, который мапит порт 80 хоста на порт 8000 контейнера. Это также открывает порт 80 для внешних клиентов, доступ к которым можно подтвердить, посетив http://IP_ПЕРВОГО_СЕРВЕРА.

  1. Сначала выполните вход на первый сервер приложения Django. Затем откройте файл «/etc/ufw/after.rules» с привилегиями суперпользователя. Вы можете использовать nano или любой другой редактор:

sudo nano /etc/ufw/after.rules

  1. Введите свой пароль при запросе и нажмите ENTER для подтверждения.

Вы увидите следующие правила ufw:

  1. Прокрутите страницу вниз и вставьте правила конфигурации UFW:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
 

Установленные правила ограничивают внешний доступ к портам, предоставляемым Docker, и разрешают доступ только из частных IP-диапазонов: 10.0.0.0/8, 172.16.0.0/12 и 192.168.0.0/16.

  1. По завершении внесения изменений сохраните и закройте файл.
  2. Чтобы применить новую конфигурацию, выполните перезапуск службы UFW с помощью следующей команды:

sudo systemctl restart ufw

  1. Откройте веб-браузер и перейдите по адресу http://IP_ПЕРВОГО_СЕРВЕРА. Убедитесь, что теперь доступ к серверу приложения через порт 80 невозможен.
  2. Повторите этот процесс для второго сервера приложения Django.

Что еще можно сделать 

  1. Одно из улучшений, которое можно внести, – это размещение вашего образа в репозитории (например, Docker Hub). Таким образом, вы упростите распространение образа на несколько серверов. 
  2. Также можно добавить конвейеры для непрерывной интеграции и развертывания, чтобы автоматически собирать, тестировать и разворачивать образы на серверах приложений при наступлении определенных событий. Например, событием может быть отправка нового кода в указанную ветку репозитория Git. 
  3. Кроме того, можно автоматизировать действия, которые выполняются при возникновении ошибок в контейнере. Для более подробной информации о том, как автоматизировать запуск контейнеров в случае ошибок или перезагрузки системы, можно обратиться к официальной документации Docker.

Заключение

В этом руководстве мы продемонстрировали, как использование контейнеров Docker обеспечивает масштабируемость приложений на основе Django. Наша инфраструктура включала отдельный сервер базы данных PostgreSQL, два внутренних сервера приложений и прокси Nginx для балансировки нагрузки и распределения трафика между ними. 

Несмотря на то, что мы использовали Django для создания нашего приложения, эту архитектуру также можно настроить с использованием других фреймворков, таких как Node.js, Laravel и других.