Миллионы пользователей выходят в Интернет для доступа к информации в различных целях, включая обучение, развлечения, новости и обмен успехами в своей жизни’ с друзьями. Поэтому при развертывании приложения в ваших интересах внедрить высокобезопасную и масштабируемую инфраструктуру для вашего приложения. Облако предлагает различные способы защиты и масштабирования Django приложения. Горизонтальное масштабирование — это один из методов, позволяющих запускать несколько копий вашего приложения. Это обеспечивает его большую отказоустойчивость и высокую доступность. Это также повышает его производительность для одновременной обработки нескольких запросов.
Горизонтальное масштабирование приложения Django
Вы можете горизонтально масштабировать приложение Django, подготовив несколько серверов приложений, на которых работает приложение Django и его WSGI HTTP-сервер (например, Gunicorn или uWSGI). Затем вам нужно будет настроить инфраструктуру для распределения входящих запросов между этими серверами приложений. Балансировщик нагрузки и обратный прокси-сервер, такой как Nginx, могут помочь вашей инфраструктуре с распределением трафика. Nginx может развертывать SSL-сертификаты, обеспечивая безопасное подключение к вашему приложению по протоколу HTTPS. Наконец, Nginx также может обеспечивать кэширование статического контента для минимизации нагрузки на ваш сервер.
Настройка этих различных компонентов по отдельности и обеспечение их взаимодействия может оказаться сложной задачей. К счастью, использование Docker упрощает процесс настройки и гарантирует, что различные компоненты будут вести себя одинаково независимо от того, где они развернуты.
Что вы будете делать в этом руководстве
В этом руководстве вы узнаете, как горизонтально масштабировать контейнеризированное приложение Django, обслуживаемое WSGI HTTP-сервером Gunicorn. Вы подготовите два сервера приложений, на каждом из которых установлен Docker, на которых будет запущена одна и та же копия контейнера приложения Django и Gunicorn.
Вы также защитите свое приложение с помощью Let’s Encrypt SSL-сертификата, подготовив и настроив третий прокси-сервер, на котором будет запущен контейнер обратного прокси-сервера Nginx и контейнер клиента Certbot. Certbot — это пакет, который помогает управлять SSL-сертификатами от центра сертификации Let’s Encrypt. Он получает сертификат, настраивает блоки серверов Nginx с указанием расположения сертификата и управляет автоматическим продлением. Он делает это путем настройки задачи cron для периодической проверки того, истекает ли срок действия сертификата и требует ли он продления. Благодаря своевременному обновлению SSL-сертификата ваш сайт всегда будет иметь высокий рейтинг безопасности на SSL Labs.
The Третий прокси-сервер находится перед вашей распределенной архитектурой и принимает весь входящий внешний трафик. Затем он распределяет трафик по вашим серверам приложений. Серверы приложений находятся за брандмауэром, который разрешает доступ к ним только прокси-серверу.
Это руководство является вторым в серии из трех руководств по работе с Django, Docker и Kubernetes. Сначала вам следует выполнить шаги, описанные в руководстве по Созданию приложения Django и Gunicorn с помощью Docker на Ubuntu. В этом руководстве мы настроили базовый код проекта, Dockerfile и подключили приложение к MinIo Simple Storage Service (S3) для обслуживания наших статических файлов.
Предварительные требования
Для выполнения этого руководства вам понадобится следующее:
- Четыре сервера Ubuntu 20.04:
Если вы выполнили шаги из предварительного руководства, Создание приложения Django и Gunicorn с помощью Docker на Ubuntu, у вас уже есть два из четырех серверов:
-
На первом сервере будет запущен экземпляр базы данных PostgreSQL. Выполните шаги 1 и 2 руководства: Создание приложения Django и Gunicorn с помощью Docker на Ubuntu, чтобы настроить базу данных. Конфигурации Postgres должны быть изменены так, чтобы разрешать внешние подключения только с IP-адресов ваших серверов приложений.
-
На втором и третьем серверах будут размещены контейнеры для кода вашего приложения. У вас уже должен быть запущен второй сервер из предварительного руководства. Мы изменим его брандмауэр, чтобы разрешить внешние подключения только с IP-адреса прокси-сервера. Вы можете выполнить шаги с 1 по 4 этого пошагового руководства, которое поможет вам настроить сервер Ubuntu на CloudSigma.
-
The Четвертым сервером будет прокси-сервер, обрабатывающий балансировку нагрузки и распределение трафика на два контейнера серверов приложений.
-
Docker должен быть установлен на двух серверах приложений и прокси-сервере.
После выполнения шагов из предварительного руководства, у вас уже должен быть установлен Docker на одном из серверов. Вы можете выполнить шаги 1, 2 и 3 нашего руководства по установке и работе с Docker. Не забудьте добавить созданного выше пользователя sudo в группу Docker.
- Приобретите зарегистрированное доменное имя и настройте его DNS-записи так, чтобы они указывали на публичный IP-адрес прокси-сервера. В демонстрационных целях мы будем использовать example_domain.com.
-
Настройте службу объектного хранилища S3. Мы использовали MinIO в качестве службы хранения в предварительном руководстве. Поэтому следуйте объяснениям в Шаге 5 of the предварительного руководства, чтобы настроить ваш MinIO бакет хранилища.
Шаг 1. Проверка работы первого сервера приложений Django
Как объяснялось в разделе Предварительные требования, это руководство следует за руководством по Созданию приложения Django и Gunicorn с помощью Docker на Ubuntu. Если вы перешли из этого руководства и уже выполнили все шаги, у вас должен быть запущен первый сервер. Код приложения основан на руководстве по созданию приложения для опросов из документации Django. Важно прочитать эти шаги, чтобы иметь представление о первоначальной настройке. Если вы выполнили шаги из руководства, вы можете пропустить этот первый шаг.
В противном случае вы можете просто клонировать контейнеризированную ветку на свой сервер. Начните с входа на свой первый сервер приложений и выполните следующую команду git для клонирования ветки django-polls-docker репозитория django-polls:
|
1 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Затем перейдите в каталог django-polls directory:
cd django-polls
В этом каталоге вы найдете файл Dockerfile используемый Docker для сборки образа приложения, каталог django-polls, который содержит код приложения на Python, и файл env, содержащий список переменных окружения, которые будут переданы в контейнер при запуске для изменения его поведения. В Dockerfile, мы определяем зависимости пакетов Django через файл requirements.txt. Кроме того, нам нужно объявить порт 8000, который будет использоваться для приема входящего трафика, и настроить его на запуск сервера gunicorn с 3 воркерами. Чтобы узнать больше об инструкциях Dockerfile, ознакомьтесь с Шагом 7 руководства Building a Django and Gunicorn Application with Docker on Ubuntu.
Вы можете собрать образ Docker с помощью команды:
docker build -t django-polls:v1 .
После того как Docker соберет образ, вы можете вывести список доступных образов на сервере с помощью следующей команды:
docker images
Вот вывод после запуска команды:
Затем нам нужно изменить файл env, используемый для настройки среды выполнения. Этот файл передается в контейнер Docker при его запуске. Откройте файл env в редакторе nano:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DJANGO_SECRET_KEY=ваш_секретный_ключ DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=IP_адрес_вашего_сервера DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=пароль_вашей_базы_данных DB_HOST=хост_вашей_базы_данных DB_PORT=порт_вашей_базы_данных STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=your_minio_access_key MINIO_SECRET_KEY=your_minio_secret_key MINIO_URL=your_minio_url:your_minio_port |
Файл env содержит текст-заполнитель, который вам необходимо изменить и заполнить правильными значениями:
-
DJANGO_SECRET_KEY: Сгенерируйте уникальное, непредсказуемое значение, как описано в документации Django. Вы можете использовать эту команду для генерации случайной строки и присвоения её переменной: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'.
-
DJANGO_ALLOWED_HOSTS: это значение используется для защиты вашего приложения от атак на заголовок HTTP Host. Вы можете установить его в *, подстановочный знак, соответствующий всем хостам в режиме разработки. При развертывании приложения в рабочей среде установите здесь свое зарегистрированное доменное имя. Для нашей демонстрации это example_domain.com.
-
DB_DATABASE: установите здесь имя базы данных PostgreSQL, созданной вами в разделе Предварительные требования section, для нашего случая это polls_db.
-
DB_USERNAME: установите здесь имя пользователя, которое вы выбрали для своей базы данных.
-
DB_PASSWORD: установите здесь пароль, который вы выбрали для своей базы данных.
-
DB_HOST: установите здесь хост, на котором запущен ваш экземпляр базы данных, как вы настроили в разделе Предварительные требования section. Это описано в шагах 1 и 2 руководства Создание приложения Django и Gunicorn с помощью Docker на Ubuntu по настройке базы данных.
-
DB_PORT: установите здесь порт вашей базы данных.
Сохраните и закройте файл после завершения редактирования. Имея учетные данные базы данных, мы можем создать схему базы данных, запустив контейнер и переопределив команду CMD, заданную в Dockerfile. Дополнительную информацию можно найти в официальной документации по точке входа Dockerfile. Далее выполните следующую команду:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py makemigrations && python manage.py migrate" |
В этой команде мы запускаем образ django-polls:v1 и передаем файл env, измененный ранее. Часть: sh -c "python manage.py makemigrations && python manage.py migrate” создает схему базы данных, определенную кодом приложения. Если вы выполняете команду впервые, вы должны увидеть аналогичный вывод, указывающий на создание схемы базы данных:
После создания схемы мы можем создать суперпользователя Django. Выполните следующую команду, чтобы запустить контейнер с интерактивной оболочкой:
|
1 |
docker run -i -t --env-file env django-polls:v1 sh |
Команда запускает контейнер с приглашением командной строки, которое вы можете использовать для взаимодействия с оболочкой Python. Давайте создадим пользователя с помощью следующей команды:
|
1 |
python manage.py createsuperuser |
Следуйте подсказкам, чтобы ввести имя пользователя, адрес электронной почты и пароль. Повторно введите пароль и нажмите Enter для создания пользователя. Выйдите из оболочки и остановите контейнер, нажав CTRL+D.
Далее нам нужно снова запустить контейнер, переопределив команду по умолчанию командой Django collectstatic . Эта команда сгенерирует статические файлы для приложения и загрузит их в облачное хранилище MinIO:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py collectstatic --noinput" |
Команда генерирует и загружает файлы в настроенную службу объектного хранения. Вот вывод:
Теперь вы можете запустить приложение без указания какой-либо дополнительной команды для переопределения команды CMD по умолчанию, определенной в Dockerfile:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
Docker запускает команду по умолчанию, определенную в Dockerfile, запускает контейнер с сервером gunicorn, открывает порт контейнера 8000 и сопоставляет его с портом Ubuntu 80. Теперь вы можете просмотреть интерфейс приложения в своем браузере, перейдя по IP-адресу первого сервера в адресной строке: http://FIRST_SERVER_IP.
Вы получите ошибку 404 Page Not Found, так как мы ничего не определили для / путь. Перейдите по адресу http://FIRST_SERVER_IP/polls, чтобы увидеть интерфейс Polls:
Посетите административный интерфейс, чтобы создать несколько опросов: http://FIRST_SERVER_IP/admin:
Укажите учетные данные, которые вы настроили с помощью команды createsuperuser выше, чтобы получить доступ к административному интерфейсу:
Если вы посмотрите исходный код страницы, вы заметите, что статические файлы извлекаются из бакета хранилища, как и было определено. Убедившись, что контейнер обслуживает приложение должным образом, вы можете остановить контейнер, нажав CTRL+C в терминале.
Далее нам нужно оставить контейнер работать в фоновом режиме, чтобы мы могли выйти из SSH-сессии первого сервера. Это оставит контейнер работать в фоновом режиме. Выполните следующую команду:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Флаг -d запускает контейнер в фоновом режиме, чтобы он мог продолжать работу в фоновом режиме. Флаг --rm очищает файловую систему контейнера после его завершения. Мы даем контейнеру имя polls, чтобы мы могли видеть его при выводе списка контейнеров.
Выйдите из SSH-сессии вашего первого сервера и перейдите по адресу http://FIRST_SERVER_IP/polls в вашем браузере, чтобы убедиться, что оно работает должным образом. Если вы видите интерфейс опросов, значит, ваш первый сервер приложений успешно настроен. Давайте настроим второй сервер приложений на следующем шаге.
Шаг 2. Настройка второго сервера приложений
Мы будем клонировать контейнеризированную ветку приложения, созданную нами в руководстве Building a Django and Gunicorn Application with Docker on Ubuntu . Более подробную информацию об используемых здесь командах вы можете найти в этом руководстве или в краткой версии в Шаге 1.
У вас должен быть запущен второй сервер, добавлен пользователь sudo без прав root и установлен Docker, как описано в разделе Предварительные требования.
Следующим шагом является настройка этого сервера для подключения к экземпляру сервера PostgreSQL. Как объясняется в шаге 1 руководства Building a Django and Gunicorn Application with Docker on Ubuntu, вам необходимо разрешить IP-адрес второго сервера через ufw и конфигурации PostgreSQL.
Сначала войдите на сервер базы данных PostgreSQL под своим пользователем sudo без прав root. Чтобы добавить правило ufw, выполните следующую команду:
|
1 |
sudo ufw allow from SECOND_SERVER_IP to any port 5432 |
Затем выполните эту команду и добавьте IP-адрес второго сервера в файл аутентификации клиентов PostgreSQL:
|
1 |
sudo nano /etc/postgresql/12/main/pg_hba.conf |
Прочитайте комментарии, чтобы лучше понять конфигурацию. Затем добавьте эту строку в раздел hosts, указав свой IP-адрес:
|
1 |
host all all SECOND_SERVER_IP/24 md5 |
Сохраните и закройте файл после завершения редактирования.
Затем перезапустите службу PostgreSQL, чтобы изменения вступили в силу:
|
1 |
sudo service postgresql restart |
Выйдите из сервера базы данных PostgreSQL и перейдите к настройке второго экземпляра сервера приложений.
Войдите на второй сервер приложений с помощью ssh. Затем клонируйте ветку django-polls-ветку репозитория django-polls с помощью следующей команды:
|
1 2 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Перейдите в каталог django-polls:
|
1 |
cd django-polls |
После этого соберите образ с помощью следующей команды:
|
1 |
docker build -t django-polls:v1 . |
После завершения процесса сборки образа измените файл env значениями конфигурации, как описано в Шаге 1. Откройте файл с помощью nano:
|
1 |
nano env |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DJANGO_SECRET_KEY=ваш_секретный_ключ DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=IP_адрес_вашего_сервера DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=пароль_вашей_базы_данных DB_HOST=хост_вашей_базы_данных DB_PORT=порт_вашей_базы_данных STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=ваш_ключ_доступа_minio MINIO_SECRET_KEY=ваш_секретный_ключ_minio MINIO_URL=ваш_url_minio:ваш_порт_minio |
Замените текст-заполнитель фактическими значениями, которые вы добавили на Шаге 1. Не забудьте изменить DJANGO_ALLOWED_HOSTS соответствующим образом. Сохраните и закройте файл, когда закончите. Обновите свои учетные данные MinIO в env файле, как вы делали на предыдущем шаге.
Теперь вы можете запустить контейнер приложения в фоновом режиме с помощью следующей команды:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Эта команда запускает контейнер и оставляет его работать в фоновом режиме. Выйдите из ssh-сессии второго сервера приложений и перейдите по адресу http://SECOND_SERVER_IP/polls в вашем браузере, чтобы убедиться, что он работает должным образом. Вы должны увидеть интерфейс опросов, если все прошло успешно.
Теперь у вас есть два сервера приложений, на которых запущена одна и та же копия вашего приложения. На следующем шаге вы настроите контейнер Nginx для работы в качестве обратного прокси-сервера.
Шаг 3. Настройка Docker-контейнера Nginx
Nginx — один из самых популярных в мире веб-серверов с открытым исходным кодом. Он отвечает за обеспечение доступности и масштабируемости сайтов с самым высоким трафиком в Интернете. Он гарантирует безопасность и очень универсален. Вы можете использовать его для обратного проксирования, кэширования и балансировки нагрузки. Мы настроили наше приложение на использование отдельного сервиса объектного хранилища для обработки его статических и медиафайлов. Следовательно, мы не будем использовать функции кэширования Nginx. Вместо этого мы будем использовать возможности обратного проксирования и балансировки нагрузки Nginx. Внешний сервер Nginx будет принимать входящий трафик и распределять его по серверам приложений бэкенда. Затем он обеспечит безопасное соединение между клиентом и сервером, защитив трафик с помощью SSL-сертификатов, полученных от Let’s Encrypt.
Существует несколько способов реализации обратного проксирования и балансировки нагрузки Nginx. Один из способов — настроить обратный прокси-сервер Nginx отдельно от сервера приложений бэкенда, как мы и сделали в этом руководстве. Такая конфигурация является гибкой и позволяет масштабировать как прокси-слой Nginx, так и слой приложений. Вы можете добавить несколько прокси-серверов Nginx или внедрить облачный балансировщик нагрузки. Другой способ реализации обратного проксирования — использование одного из серверов приложений бэкенда в качестве прокси-сервера Nginx. В этом случае вы можете проксировать входящие запросы локально и на другие серверы приложений. При желании вы можете настроить контейнер Nginx на всех серверах приложений бэкенда и установить внешний облачный балансировщик нагрузки для приема входящего трафика и его распределения по серверам приложений бэкенда.
Давайте начнем настройку прокси-сервера. Войдите на четвертый сервер Ubuntu, который вы настроили для использования в качестве прокси-сервера Nginx, и создайте каталог конфигурации:
|
1 |
mkdir conf |
Откройте конфигурационный файл с помощью nano внутри каталога:
|
1 |
nano conf/nginx.conf |
Затем добавьте в файл следующую конфигурацию:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } server { listen 80 default_server; return 444; } server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example_domain.com; # SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_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 и распределять запросы между двумя серверами приложений, которые мы настроили на Шаге 1 и Шаге 2. Вы можете найти общую информацию о структуре конфигурационного файла Nginx в их официальной документации.
Мы изучили конфигурационные файлы, предоставленные Docker Hub документацией к образу Nginx, Certbot, и Gunicorn, чтобы составить этот минимальный конфигурационный файл Nginx. Хотя это предназначено только для демонстрационных целей и запуска нашей настройки, вы можете свободно исследовать и экспериментировать с другими конфигурациями, следуя руководствам Nginx.
Блок upstream используется для определения группы серверов, которые будут обрабатывать входящие запросы. Группе присваивается имя и вызывается директивой proxy_pass. Мы назвали блок django и указали IP-адреса двух серверов приложений бэкенда:
|
1 2 3 4 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } |
Мы также определили 3 блока server. Первый блок server перехватывает все запросы, не соответствующие вашему домену, и возвращает код 444 (закрывает соединение без отправки ответа клиенту, тем самым отклоняя вредоносные или некорректные запросы). Прямой HTTP-запрос к IP-адресу вашего сервера обрабатывается этим блоком, так как он определен как default_server:
|
1 2 3 4 |
server { listen 80 default_server; return 444; } |
The Второй блок server обрабатывает входящие HTTP-запросы (порт 80) и перенаправляет их на HTTPS (порт 443) с помощью перенаправления HTTP 301:
|
1 2 3 4 5 6 |
server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } |
Теперь третий блок server обрабатывает запросы. Он содержит несколько директив, и ниже мы объясним их важность.
У нас есть две директивы, определяющие пути к TLS-сертификату и ключу, предоставленным Certbot. Сертификаты монтируются в контейнер Nginx при его запуске:
|
1 2 3 |
# SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; |
Далее идут настройки безопасности SSL по умолчанию, рекомендованные Certbot. Вы можете узнать больше в официальной документации Nginx по модулю ngx_http_ssl_module. Mozilla также предлагает дополнительную информацию о безопасности на стороне сервера. Значение ssl_ciphers в файле conf взято со страницы Mozilla:
|
1 2 3 4 5 6 |
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"; |
В следующих двух директивах мы определим максимально допустимый размер тела запроса клиента и установим тайм-аут для соединений keep-alive с клиентом. Nginx закроет соединения с клиентом по истечении секунд, указанных вами в директиве keepalive_timeout. Вы можете найти дополнительную информацию о конфигурациях Nginx для развертывания Gunicorn в официальной документации:
|
1 2 |
client_max_body_size 4G; keepalive_timeout 5; |
В конфигурационном файле мы также определили два блока location. Первый блок обрабатывает проксирование запросов, как определено директивами proxy. Входящие запросы проксируются на серверы upstream django, определенные ранее:
|
1 2 3 4 5 6 7 |
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; } |
Вы можете найти дополнительную информацию о директивах проксирования в модуле Nginx ngx_http_proxy_module и документации по развертыванию сервера Gunicorn.
Во втором блоке location мы определяем путь: /well-known/acme-challenge/. Обычно он используется Certbot для проверки вашего доменного имени в Let’s Encrypt перед предоставлением или продлением SSL-сертификата:
|
1 2 3 |
location ^~ /.well-known/acme-challenge/ { root /var/www/html; } |
На этом настройка конфигурационного файла Nginx завершена. Теперь вы можете сохранить и закрыть файл после завершения редактирования.
Определенный вами конфигурационный файл можно использовать для запуска контейнера Nginx. Однако запуск завершится ошибкой, так как мы еще не получили SSL-сертификаты от Let’s Encrypt. В этом руководстве мы будем использовать nginx:1.20.2 Docker-образ версии 1.20.2 из официального репозитория образов Nginx на Docker Hub.
Вы можете запустить команду ниже, чтобы загрузить образ и убедиться, что все работает правильно:
|
1 2 3 4 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Эта команда создает контейнер с именем nginx и сопоставляет порты 80 и 443 между хост-системой и контейнером. Флаг --rm удаляет все промежуточные контейнеры после успешной сборки. Мы используем флаг -v для монтирования конфигурационного файла в контейнер по пути /etc/nginx/conf.d/nginx.conf, который является каталогом конфигураций Nginx по умолчанию. Он монтируется в режиме «только для чтения» с помощью флага ro, чтобы контейнер Nginx не мог его изменить. Мы устанавливаем корневой каталог веб-сайта по умолчанию webroot и монтируем его как /var/www/html. В завершение мы указываем Docker использовать образ nginx:1.20.2 для этой сборки. На следующем шаге давайте получим TLS/SSL-сертификат и ключ от Let’s Encrypt.
Шаг 4. Получение SSL/TLS-сертификата от Let’s Encrypt и настройка автопродления Certbot
Certbot помогает получать бесплатные TLS-сертификаты от Let’s Encrypt, а также управлять их автоматическим продлением до истечения срока действия. Это повышает безопасность ваших веб-сайтов и обеспечивает их работу по протоколу HTTPS. Чтобы сохранить нашу архитектуру контейнеризированной, мы будем использовать Docker-образ Certbot для получения SSL/TLS-сертификатов и настройки автопродления. Убедитесь, что у вас установлен Docker на вашем прокси-сервере в соответствии с инструкциями в разделе Предварительные требования.
У вас также должна быть настроена DNS-запись типа A для вашего зарегистрированного доменного имени, указывающая на IP-адрес вашего прокси-сервера. Вы можете выполнить проверку, запустив Docker-образ certbot и передав флаг --staging флаг:
|
1 2 3 4 |
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 example_domain.com |
Эта команда загрузит образ Certbot и запустит его в интерактивном режиме. Это означает, что он запустится с оболочкой (shell), позволяя вам ввести некоторые данные. Она сопоставляет порт 80 хоста с портом 80 внутри контейнера. Мы используем флаг -v для монтирования двух каталогов хоста в контейнер: /etc/letsencrypt/ и /var/lib/letsencrypt/. Флаг --standalone флаг указывает, что мы хотим запустить образ Certbot без использования Nginx. Наконец, у нас есть --staging флаг, который заставит Certbot выполняться на тестовых серверах и проверять ваше доменное имя.
Введите свой адрес электронной почты и примите Условия использования при появлении запроса. Ниже приведен вывод успешной проверки:
|
1 2 3 4 5 6 7 |
Аккаунт зарегистрирован. Запрос a сертификата для example_domain.com Успешно получен сертификат. Сертификат is сохранен в: /etc/letsencrypt/live/example_domain.com/fullchain.pem Ключ is сохранен в: /etc/letsencrypt/live/example_domain.com/privkey.pem Этот сертификат истекает on 2022-04-29. Эти файлы будут beобновлены когда theсертификат обновится. |
СЛЕДУЮЩИЕ ШАГИ:
Сертификат необходимо будет обновить до истечения срока его действия. Certbot может автоматически обновлять сертификат в фоновом режиме, но вам может потребоваться предпринять шаги для включения этой функции. Перейдите по этой ссылке за инструкциями.
Вы можете просмотреть сертификат с помощью cat команды:
|
1 |
sudo cat /etc/letsencrypt/live/example_domain.com/fullchain.pem |
Вышеупомянутая команда должна отобразить ваш сертификат в терминале. Убедившись, что Certbot предоставил ваш сертификат, вы можете протестировать конфигурацию Nginx, созданную на Шаге 3. Выполните команду Docker ниже, чтобы запустить контейнер Nginx:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
В этой команде мы использовали флаг -v для монтирования директорий с SSL/TLS-сертификатами Let’s Encrypt.
Когда контейнер запустится и заработает, откройте веб-страницу в браузере: http://example_domain.com. Скорее всего, вы увидите предупреждение о том, что сайт небезопасен:
Это связано с тем, что мы предоставили только тестовые (staging) сертификаты, а не рабочие (production) сертификаты от Let’s Encrypt. Давайте получим рабочие сертификаты, выполнив следующую команду Certbot без флага --staging флага:
|
1 2 3 4 |
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 -d example_domain.com |
В запросе подтвердите, что вы хотите обновить и заменить существующий сертификат, введя 2 и нажав ENTER. Это должно предоставить готовый к работе сертификат. Теперь вы можете запустить контейнер Nginx, и все должно работать нормально:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Как только контейнер запустится и заработает, снова откройте веб-страницу в браузере: http://example_domain.com снова. Обратите внимание, что ваш браузер перенаправляется на HTTPS, даже если вы ввели HTTP. Это означает, что наш сервер в конфигурации Nginx, а также предоставленные SSL/TLS-сертификаты работают нормально. Перейдите по маршруту polls route http://example_domain.com/polls, так как у нас не определен маршрут для домашнего пути /. Вы должны увидеть интерфейс опросов:
На данный момент вы успешно настроили готовую к работе архитектуру. Вы развернули два сервера бэкенда, которые будут обрабатывать входящие запросы, перенаправляемые с прокси-сервера. Прокси-сервер будет отвечать за балансировку нагрузки и защиту трафика с помощью предоставленных TLS-сертификатов.
Однако следует помнить, что срок действия сертификатов Let’s Encrypt истекает через 90 дней. Поэтому вам необходимо обновить их до истечения этого срока. Поскольку контейнер Nginx будет запущен, вам следует использовать режим webroot вместо режима standalone при выполнении команды certbot для обновления сертификата. Помните, что вы указали директорию /var/www/html/.well-known/acme-challenge/ в конфигурационном файле Nginx на Шаге 3. Certbot будет использовать этот путь для хранения файлов проверки. Кроме того, клиент Let’s Encrypt будет обращаться по этому пути с запросами проверки при попытке обновить сертификаты. Как только команда обновления завершит выполнение, вы сможете перезапустить Nginx, чтобы изменения вступили в силу.
Остановите контейнер, нажав CTRL+C в терминале, и давайте снова запустим его в фоновом режиме с флагом -d:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -d -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Это оставит контейнер Nginx работать в фоновом режиме. Давайте протестируем процедуру обновления сертификата с флагом --dry-run, выполнив команду ниже:
|
1 2 3 4 5 |
docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ -v "/var/www/html:/var/www/html" \ certbot/certbot renew --webroot -w /var/www/html --dry-run |
В этой команде мы указали плагин --webroot, а также путь для запросов проверки с помощью флага -w. Мы также указываем флаг --dry-run для проверки процедуры автоматического обновления без фактического получения сертификата.
При успешной симуляции вы должны увидеть похожий вывод:
|
1 2 3 4 5 6 7 8 |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/example_domain.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Simulating renewal of an existing certificate for example_domain.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/hackinroms.com/fullchain.pem (success) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
Каждый раз, когда вы обновляете сертификат для работающего приложения, необходимо перезапускать Nginx, чтобы контейнер начал использовать новый сертификат. Следующая команда Docker перезапускает контейнер nginx (помните, мы назвали контейнер nginx):
|
1 |
docker kill -s HUP nginx |
Эта команда отправляет сигнал HUP Unix процессу Nginx, работающему внутри Docker-контейнера nginx. Это заставляет Nginx перезагрузить свои конфигурации и начать использовать обновленные сертификаты.
Поскольку на нашем прокси-сервере установлен TLS/SSL, а наш сайт работает по протоколу HTTPS, теперь нам нужно защитить наши серверы бэкенд-приложений, чтобы они разрешали запросы только от прокси-сервера.
Шаг 5. Защита бэкенд-серверов Django от внешнего доступа
Прокси-сервер, который вы настроили в этом руководстве, выполняет терминацию SSL, расшифровывая SSL-соединение и пересылая незашифрованные пакеты на бэкенд-серверы приложений. Поскольку мы будем защищать бэкенд-серверы от любого внешнего доступа, такого уровня безопасности должно быть достаточно для большинства случаев. Однако, если вы развертываете приложения, передающие конфиденциальные данные, такие как банковская информация или медицинские данные, вам следует внедрить сквозное шифрование.
В этом руководстве серверы Gunicorn на бэкенде защищены с помощью Nginx, так как они не предназначены для прямого доступа извне. Прокси-сервер Nginx работает как шлюз к бэкенд-серверам, предотвращая прямой доступ внешних клиентов к серверам приложений бэкенда. Вам необходимо убедиться, что все запросы проходят через прокси-сервер. При этом у Docker есть проблема, из-за которой он обходит ufw правила брандмауэра и открывает порты для внешнего доступа, что может сделать вашу инфраструктуру уязвимой. Это подтверждается тем, что мы настроили наши серверы приложений на Шаге 1 и Шаге 2 без разрешения порта 80 в правилах ufw. Тем не менее, вы все равно можете получить доступ к веб-страницам, перейдя по любому из публичных IP-адресов серверов в браузере. Один из способов решить эту проблему — использовать iptables напрямую, минуя ufw. Вы можете прочитать официальные документы Docker и iptables, чтобы узнать больше. Другой рекомендуемый способ — использование облачных брандмауэров.
Давайте изменим конфигурацию UFW, чтобы заблокировать внешний доступ ко всем портам, которые могли быть открыты Docker. Когда мы сопоставили порт хоста 80 с портом контейнера Docker 8000 с флагом -p 80:8000 в команде Docker, мы также непреднамеренно открыли порт 80 на хост-машине. Вы можете отключить этот доступ, изменив конфигурацию UFW, как описано в репозитории ufw-docker в файле README.
Давайте внесем изменения для первого сервера приложений Django. Войдите на сервер и откройте файл по пути /etc/ufw/after.rules с помощью nano от имени суперпользователя (sudo):
|
1 |
sudo nano /etc/ufw/after.rules |
Файл содержит следующие правила ufw:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# # rules.input-after # # Правила, которые должны выполняться после правил, добавленных через командную строку ufw. Пользовательские # правила должны быть добавлены в одну из этих цепочек: # ufw-after-input # ufw-after-output # ufw-after-forward # # Не удаляйте эти обязательные строки, иначе возникнут ошибки *filter :ufw-after-input - [0:0] :ufw-after-output - [0:0] :ufw-after-forward - [0:0] # Конец обязательных строк # не записывать в лог шумные службы по умолчанию -A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input # не записывать в лог шумные широковещательные пакеты -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input # не удаляйте строку 'COMMIT', иначе эти правила не будут обработаны COMMIT |
Добавьте следующий блок строк конфигурации UFW в конец файла:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 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. Кроме того, они разрешают доступ из 10.0.0.0/8, 172.16.0.0/12, и 192.168.0.0/16 диапазонов частных IP-адресов. Вы можете узнать больше о правилах в README ufw-docker. Сохраните и закройте файлы после завершения редактирования. Эта настройка должна работать, если вы настроили виртуальную частную сеть (VPC), в которой находятся все три ваших сервера, а затем указали частные IP-адреса серверов Django в директиве upstream в Nginx конфигурационном файле.
Однако мы использовали публичные IP-адреса, и у нас может не быть VPC. Таким образом, вам нужно добавить правило в ufw, чтобы разрешить трафик от прокси-сервера Nginx через порт 80 обоих серверов приложений Django. Вы можете добавить разрешающее правило в ufw, указав исходный IP-адрес сервера для порта 80 с помощью следующей команды:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
После внесения изменений перезагрузите сервер приложений Django, чтобы изменения вступили в силу, так как выполнение sudo ufw reload, судя по всему, не применяет изменения:
|
1 |
sudo reboot |
После перезагрузки сервера запустите контейнер так же, как вы делали на шаге 1 или шаге 2:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Затем попробуйте перейти по IP-адресу первого сервера Django в браузере, чтобы проверить, отображается ли интерфейс Polls: http://FIRST_SERVER_IP/polls. Это не удастся. Теперь выйдите из первого сервера и повторите шаги, которые вы выполнили здесь, для второго сервера. Откройте /etc/ufw/after.rules с помощью nano от имени пользователя sudo:
|
1 |
sudo nano /etc/ufw/after.rules |
Как и ранее, прокрутите вниз и добавьте блок конфигурации UFW:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 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 |
Сохраните и закройте файл после добавления указанного выше блока.
Затем добавьте разрешающее правило в ufw с указанием исходного IP-адреса сервера для порта 80 с помощью следующей команды:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
Перезагрузите сервер, чтобы изменения вступили в силу:
|
1 |
sudo reboot |
После перезапуска сервера снова запустите контейнер с помощью команды:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Проверьте, можете ли вы просмотреть интерфейс опросов, перейдя непосредственно по IP-адресу второго сервера: http://SECOND_SERVER_IP/polls. Это также должно завершиться ошибкой.
Теперь эта архитектура готова к тестированию. Вы можете перейти по адресу https://example_domain_here/polls для просмотра интерфейса опросов по умолчанию в вашем браузере. Это означает, что прокси-сервер Nginx по-прежнему имеет доступ к бэкенд-серверам Django.
Заключение
В этом руководстве мы показали, как развернуть масштабируемую инфраструктуру с использованием контейнеров Docker. Инфраструктура включает в себя отдельный сервер базы данных PostgreSQL, два бэкенд-сервера приложений и прокси-сервер Nginx для балансировки нагрузки и распределения трафика между двумя серверами. Хотя мы основывали наше приложение на приложении Django Polls, вы можете адаптировать эту архитектуру для различных приложений, использующих другие фреймворки, такие как Node.js, Laravel, и т. д.
Это базовое руководство для начала работы. В качестве улучшений вы можете разместить свой образ в репозитории образов, например Docker Hub, что позволит легко распространять образ на несколько серверов. Вы также можете добавить конвейеры непрерывной интеграции и развертывания для автоматической сборки, тестирования и развертывания образов на серверах приложений при возникновении определенных событий. Например, событием может быть отправка нового кода в указанную ветку в репозитории git. Также вы можете автоматизировать действия при возникновении ошибки в контейнере. В официальной документации Docker есть хорошее руководство по теме Автоматический запуск контейнеров в случае ошибок или перезагрузки системы.
Приятной работы!








Комментарии
Комментариев пока нет. Будьте первым.