Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.
Кратко о задаче
Уровень отказов понижается, а пропускная способность системы, наоборот, увеличивается при использовании горизонтального масштабирования приложения 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_ по умолчанию.
Далее нужно сделать, чтобы контейнер работал автономно. Это необходимо сделать, чтобы мы могли выйти из сеанса SSH первого сервера. Это оставит контейнер в режиме _detached_. Для этого выполните команду с флагом _-d_ , чтобы он мог продолжать работать в режиме _detached_. Также желательно дать контейнеру имя _testdb_ (будет совпадать с именем нашей БД), чтобы видеть его при перечислении контейнеров.
Выйдите из сеанса SSH вашего первого сервера и перейдите по адресу _http://FIRST_SERVER_IP/testdb_ в браузере, чтобы убедиться, что сервер работает должным образом. Если вы можете просматривать интерфейс _testdb_ , значит первый сервер приложений настроен успешно. Теперь давайте**** настроим масштабируемое приложение на втором.
Перед началом этого этапа у вас должен быть запущен второй сервер, добавлен пользователь _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_ и добавьте в файл следующее:
В этом файле мы добавляем блоки _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 содержат детальное руководство по автоматическому запуску контейнеров в случае ошибок или перезагрузки системы.
Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.