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

С расширением инфраструктуры перед компанией встает задача масштабирования. Если ваши веб-приложения обслуживаются Django, то эта статья для вас. В ней мы рассмотрим эффективный способ масштабирования и защиты приложений на этом фреймворке, а для удобства оформим материал в виде пошагового руководства. Добавим, что руководство предназначено для серверов разработки Django на Ubuntu. Данные решения были протестированы на Ubuntu 20.04, но теоретически могут работать и на других версиях.

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

Вводные замечания

Уровень отказов понижается, а пропускная способность системы, наоборот, увеличивается при использовании горизонтального масштабирования приложения Django. Для этого предоставляются дополнительные серверы, на которых запускается само приложение и интерфейс WSGI. Для распределения входящих запросов применяют обратный прокси. Этот тип прокси обрабатывает и перераспределяет клиентские запросы на сервер. В дополнение к балансировке рабочих нагрузок между серверами обратные прокси-серверы могут предоставлять услуги, не всегда предлагаемые серверами приложений, такие как кэширование, сжатие и шифрование SSL, а также прерывание TLS. Одним из вариантов такого прокси является Nginx.

С помощью Nginx развертываются SSL-сертификаты, обеспечивающие безопасное подключение через HTTPS. Также Nginx обеспечивает кэширование статического контента, чтобы минимизировать нагрузку на сервер. Конфигурирование каждого из этих компонентов в отдельности и обеспечение их связности может оказаться сложной задачей. Поэтому для запуска приложений Django задействуем Docker. Это упростит процесс настройки и обеспечит одинаковое поведение компонентов, причем безотносительно среды их развертывания.

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

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

Шаг 1. Готовим и настраиваем среду

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

  • Четыре сервера Ubuntu (желательно версии 20.04). Первый сервер будет запускать экземпляр базы данных: для примера рассмотрим БД на PostgreSQL. Конфигурации Postgres должны быть изменены, чтобы разрешить внешние подключения только с IP-адресов вашего сервера приложений. На втором и третьем серверах будут размещаться контейнеры для кода вашего приложения. Потребуется изменить брандмауэр одного из этих серверов, чтобы разрешить внешние подключения только с IP-адреса прокси-сервера. Четвертый сервер будет прокси-сервером отвечающим за балансировку нагрузки и распределение трафика между двумя контейнерами.
  • Установить Docker на два сервера приложений и на прокси-сервер.
  • Приобрести домен. Понадобится настроить его записи DNS так, чтобы они указывали на уникальный IP прокси-сервера. В демонстрационных целях будем прописывать test_domain.com.
  • Настроить службу хранилища объектов S3.
  • Настроить сервер с Postgres и базой данных. Для примера будем использовать БД с именем testdb.

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

Начнем с входа на наш первый сервер приложений и выполним команду git, чтобы клонировать ветку django-testdb-docker репозитория django-testdb. Затем перейдем в каталог django-testdb при помощи команды cd. В этом каталоге найдите Dockerfile, используемый для создания образа приложения, каталог с кодом приложения Python, и файл env с переменными, которые будут переданы в контейнер при запуске для изменения его поведения.

В Dockerfile определите зависимости пакетов Django через текстовый файл requirements.txt. Кроме того, нужно объявить порт 8000 для приема входящего трафика, и настроить его для запуска сервера Gunicorn. Можно собрать образ Docker по команде:

docker build -t django testdb:v1

После того как Docker создаст образ, выведем список доступных образов на сервере с помощью команды docker images.

Далее нам нужно изменить файл env, используемый при настройке среды выполнения. Он передается в контейнер Docker при запуске. Откройте файл env редактором nano. В файле env уже есть шаблонный текст, который нужно изменить и проставить там актуальные значения:

  • DJANGO_SECRET_KEY — генерирует рандомное значение (есть в документации Django). Поэтому используйте команду для генерации случайной строки и помещения ее в переменную.
  • DJANGO_ALLOWED_HOSTS — это значение используется для защиты нашего приложения от атак хоста. Можно установить там значение *, соответствующее всем хостам (например, при работе в режиме разработки). Но когда вы разворачиваете свое приложение в рабочей среде, установите в качестве значения имя вашего домена. Для нашего гайда это test_domain.com.
  • DB_DATABASE — здесь укажите имя БД PostgreSQL, возьмем за пример testdb.
  • DB_USERNAME — укажите имя пользователя, которое вы выбрали для своей БД.
  • DB_PASSWORD — установите пароль, который вы выбрали для своей базы данных.
  • DB_HOST — укажите хост, на котором запущен экземпляр вашей базы данных.
  • DB_PORT — укажите порт вашей базы данных.

После того как закончите редактирование, сохраните и закройте файл. С этими учетными данными мы можем создать схему базы данных, запустив контейнер и переопределив набор команд CMD в Dockerfile. Дополнительно о точке входа Dockerfile можно почитать в официальной документации. Далее выполните следующую команду:

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

Так запускается образ django-testdb:v1 и передается измененный ранее файл env. Код, начинающийся с sh, создает схему БД. Если вы выполняете команду в первый раз, то увидите вывод, указывающий на создание схемы базы данных.

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

docker run -i -t --env-file env django-testdb:v1 sh

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

python manage.py createsuperuser

Далее введите имя пользователя, адрес электронной почты и пароль, повторите его и нажмите Enter, чтобы создать пользователя.

Выйдите из оболочки и уничтожьте контейнер, нажав CTRL+D. Затем нужно снова запустить контейнер, переопределив команду по умолчанию инструкцией collectstatic. Команда сгенерирует статический контент и загрузит его в облачное хранилище MinIO. В результате файл после создания загружается в настроенную службу хранилища объектов. Теперь вы можете запустить приложение без указания какой-либо дополнительной инструкции для переопределения команды CMD, определенной в Dockerfile по умолчанию.

Посмотрите интерфейс приложения в своем браузере, обратившись к IP-адресу первого сервера в адресной строке: http://FIRST_SERVER_IP. Разумеется, высветится ошибка 404. Страница не найдена, потому что мы ничего не определили для пути /. Перейдите по адресу http://FIRST_SERVER_IP/testdb, а затем в панель администратора: http://FIRST_SERVER_IP/admin и введите установленные учетные данные, чтобы получить доступ к панели. Убедившись, что контейнер настроен должным образом, вы можете закрыть его, нажав CTRL+C в терминале.

Далее нужно сделать, чтобы контейнер работал автономно. Это необходимо сделать, чтобы мы могли выйти из сеанса SSH первого сервера. Это оставит контейнер в режиме detached. Для этого выполните команду с флагом -d, чтобы он мог продолжать работать в режиме detached. Также желательно дать контейнеру имя testdb (будет совпадать с именем нашей БД), чтобы видеть его при перечислении контейнеров.

Выйдите из сеанса SSH вашего первого сервера и перейдите по адресу http://FIRST_SERVER_IP/testdb в браузере, чтобы убедиться, что сервер работает должным образом. Если вы можете просматривать интерфейс testdb, значит первый сервер приложений настроен успешно. Теперь давайте настроим масштабируемое приложение на втором.

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

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

Сначала войдите в базу данных PostgreSQL под своим пользователем sudo без права root-доступа. Чтобы добавить правило UFW, выполните следующую команду: 

sudo ufw allow from SECOND_SERVER_IP to any port 5432

Затем выполните эту команду и добавьте IP-адрес второго сервера в файл аутентификации клиента PostgreSQL: 

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

Теперь добавьте эту строку в раздел hosts, указав свой IP-адрес: 

host all all SECOND_SERVER_IP/24 md5

После окончания редактирования сохраните файл, далее перезапустите службу PostgreSQL, чтобы изменения вступили в силу: 

sudo service postgresql restart

Выйдите из базы данных PostgreSQL и продолжите настройку. Войдите на второй сервер, используя SSH, а затем клонируйте django-testdb-branch из репозитория django-testdb с помощью: 

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

Зайдите в директорию django-testdb и создайте образ: 

docker build -t django-testdb:v1

Далее измените файл env с нужными значениями конфигурации (см. предыдущий шаг). Не забудьте изменить переменную DJANGO_ALLOWED_HOSTS.

Теперь обновите свои учетные данные MinIO в файле env и запустите контейнер в автономном режиме с помощью следующей команды: 

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

Команда запускает контейнер и поддерживает его работу в автономном режиме. Выйдите из SSH второго сервера и перейдите на http://SECOND_SERVER_IP/testdb в браузере, чтобы удостовериться, что всё работает должным образом. Теперь у нас есть два сервера, на которых запущена одна и та же копия масштабируемого приложения Django.

Шаг 4. Настройка Nginx Docker

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

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

Итак, зайдите на четвертый сервер Ubuntu, который вы настроили для использования в качестве прокси-сервера Nginx, и создайте каталог конфигурации командой mkdir conf. Теперь откройте конфигурацию внутри каталога: nano conf/nginx.conf и добавьте в файл следующее:

upstream django {
    server FIRST_SERVER_IP;
    server SECOND_SERVER_IP;
    }

server {
    listen 80 default_server;
    return 444;
    }

server {
    listen 80;
    listen [::]:80;
    server_name test_domain.com;
    return 301 https://$server_name$request_uri;
    }

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name test_domain.com;
    # SSL
    ssl_certificate /etc/letsencrypt/live/test_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/test_domain.com/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

Прежде всего у нас должна быть запись DNS A, указывающая на IP нашего прокси. Вы можете проверить это, запустив образ Certbot Docker и передав флаг --staging. Это загрузит образ Certbot и запустит его в интерактивном режиме. Он сопоставляет порт 80 хоста с портом 80 внутри контейнера. Мы используем флаг -v для добавления в контейнер двух хост-директорий: /etc/letsencrypt/ и /var/lib/letsencrypt/. Флаг --standalone указывает, что мы хотим, чтобы образ Certbot запускался без использования Nginx. Наконец, у нас есть флаг --staging, который заставит Certbot выполняться на промежуточных серверах и проверять ваше доменное имя.

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

Шаг 6: Защита внутренних серверов Django

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

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

Другой рекомендуемый способ — использование облачных брандмауэров. Здесь можно изменить настройки UFW так, чтобы заблокировать внешний доступ ко всем портам, которые могли быть открыты Docker. Поскольку в процессе настройки мы непреднамеренно открыли порт 80 на хосте, то можем и отключить этот доступ, изменив конфигурацию UFW. О том, как это сделать, подробно описано в readme репозитория UFW-Docker.

Дополнительные возможности

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

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

Telegram
VK
Скопировать ссылку

Зарегистрируйтесь и начните пользоваться
сервисами Timeweb Cloud прямо сейчас

15 лет опыта
Сосредоточьтесь на своей работе: об остальном позаботимся мы
165 000 клиентов
Нам доверяют частные лица и компании, от небольших фирм до корпораций
Поддержка 24/7
100+ специалистов поддержки, готовых помочь в чате, тикете и по телефону