Развертывание Django на AWS с помощью Docker и Let's Encrypt

В этом руководстве мы развернем приложение Django на AWS EC2 с помощью Docker. Приложение будет работать за HTTPS-прокси Nginx, использующим SSL-сертификаты Let's Encrypt. Мы будем использовать AWS RDS для обслуживания базы данных Postgres, а также AWS ECR для хранения и управления образами Docker.

Django на Docker Series:

  1. Докеризация Django с Postgres, Gunicorn и Nginx
  2. Защита контейнеризированного Django-приложения с помощью Let's Encrypt
  3. Развертывание Django на AWS с помощью Docker и Let's Encrypt (это руководство!)

Цели

К концу этого учебного курса вы сможете:

  1. Установите новый экземпляр EC2
  2. Установите Docker на экземпляр EC2
  3. Настройка и использование Elastic IP-адреса
  4. Настройка роли IAM
  5. Использование реестра образов Amazon Elastic Container Registry (ECR) для хранения построенных образов
  6. Настройте AWS RDS для сохранения данных
  7. Настройте группу безопасности AWS Security Group
  8. Развертывание Django на AWS EC2 с помощью Docker
  9. Запустите приложение Django за HTTPS-прокси Nginx с SSL-сертификатами Let's Encrypt

Пререквизиты

Этот пост развивает статьи Dockerizing Django with Postgres, Gunicorn, and Nginx и Securing a Containerized Django Application with Let's Encrypt.

Предполагается, что вы можете:

  1. Контейнеризация приложения Django вместе с Postgres, Nginx и Gunicorn.
  2. Защита контейнеризированного приложения Django, работающего за HTTPS-прокси Nginx, с помощью SSL-сертификатов Let's Encrypt.
  3. Используйте SSH для подключения к удаленному серверу и SCP для копирования файлов на сервер.

AWS EC2

Сначала создайте учетную запись AWS, если у вас ее еще нет.

Далее перейдите в консоль EC2 и нажмите Launch instance:

EC2 Home

Введите имя экземпляра и выберите Ubuntu Server 22.04 LTS (HVM) в качестве образа сервера (AMI):

Select AMI

Оставайтесь с экземпляром t2.micro:

EC2 instance type

Создайте новую пару ключей для доступа к вашему экземпляру по SSH, нажав на кнопку "Create new key pair". При настройке группы безопасности выберите "Создать новую группу безопасности" и выберите "Разрешить HTTPS-трафик из Интернета" и "Разрешить HTTP-трафик из Интернета":

EC2 configure security group and add new key pair

Эти правила необходимы для выдачи сертификатов и доступа к приложению.

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

Правила группы безопасности inbound используются для ограничения доступа к вашему экземпляру из Интернета. Если у вас нет дополнительных требований к безопасности, то для экземпляров, на которых размещены веб-приложения, скорее всего, нужно разрешить HTTP- и HTTPS-трафик из любой точки мира. Для подключения к экземпляру для настройки и развертывания необходимо разрешить SSH. В производственных условиях ограничьте количество IP-адресов, с которых разрешен SSH.

Нажмите Launch. На следующем экране нажмите View All Instances.

На раскрутку экземпляра уйдет несколько минут.

Настроить экземпляр EC2

В этом разделе мы установим Docker на экземпляр, добавим Elastic IP и настроим роль IAM.

Установите Docker

Вернитесь в консоль EC2, выберите только что созданный экземпляр и возьмите публичный IP-адрес:

EC2 Public IP

Подключитесь к своему экземпляру EC2, используя ключ .pem, который мы загрузили на шаге "AWS EC2".

$ ssh -i /path/to/your/djangoletsencrypt.pem ubuntu@public-ip-or-domain-of-ec2-instance

Ваш .pem, вероятно, был загружен по пути типа ~/Downloads/your-key-pair-name.pem. Если вы не уверены, где его хранить, переместите его в каталог "~/.ssh". Возможно, придется также изменить права доступа - например, chmod 400 -i /path/to/your/your-key-pair-name.pem.

Начните с установки последней версии Docker и версии 1.29.2 Docker Compose:

$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
$ sudo apt update
$ sudo apt install docker-ce
$ sudo usermod -aG docker ${USER}
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

$ docker -v
Docker version 20.10.8, build 3967b7d

$ docker-compose -v
docker-compose version 1.29.2, build 5becea4c

Install AWS CLI

Сначала установите unzip:

$ sudo apt install unzip

Скачать AWS CLI ZIP:

$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

Распакуйте его содержимое:

$ unzip awscliv2.zip

Установите AWS CLI:

$ sudo ./aws/install

Проверить установку:

$ aws --version

Elastic IP

По умолчанию экземпляры получают новый публичный IP-адрес при каждом запуске и перезапуске.

Elastic IP позволяет выделять статические IP-адреса для инстансов EC2, в результате чего IP-адрес остается неизменным и может быть переадресован между инстансами. Рекомендуется использовать его для производственных установок.

Перейдите в раздел Elastic IPs и нажмите Allocate Elastic IP address:

Elastic IP

Затем нажмите Allocate:

Elastic IP Allocate

Выделите только что созданный эластичный IP-адрес, нажмите кнопку Actions выпадающего списка -> Associate this Elastic IP address:

Elastic IP Associate

Выделите свой экземпляр и нажмите Associate:

Elastic IP Select Instance

IAM Role

В процессе развертывания мы будем извлекать образы Docker из AWS ECR на наш экземпляр EC2. Поскольку мы не будем предоставлять публичный доступ к Docker-образам на ECR, необходимо создать роль IAM с правами на извлечение Docker-образов из ECR и прикрепить ее к экземпляру EC2.

Перейдите в консоль IAM.

Нажмите Roles в левой боковой панели, а затем Create role:

IAM Roles

Выберите AWS Service и EC2, затем нажмите Next:

IAM Select Use Case

Введите container в поле поиска, выберите политику AmazonEC2ContainerRegistryPowerUser и нажмите Next:

IAM Role Policy

Нажмите Следующий: Review. Используйте django-ec2 для имени и нажмите Create role:

IAM Role ReviewIAM Role Review

Теперь необходимо прикрепить новую роль к экземпляру EC2.

Войдя в консоль EC2, нажмите Instances, а затем выберите свой экземпляр. Нажмите на выпадающий список Actions -> Instance settings -> Attach/Replace IAM Role:

EC2 Attach IAM Role

Выберите роль django-ec2, а затем нажмите Update IAM role.

EC2 Select IAM Role

Добавить DNS-запись

Добавьте в DNS запись A для используемого домена, указывающую на публичный IP-адрес экземпляра EC2.

Это Elastic IP, который вы привязали к своему экземпляру.

AWS ECR

Amazon Elastic Container Registry (ECR) - это полностью управляемый реестр образов Docker, который упрощает разработчикам хранение и управление образами. Доступ к частным образам осуществляется с помощью пользователей и ролей IAM.

Перейдите на консоль ECR. Нажмите Repositories в боковой панели и затем Create repository:

ECR Repositories

Задайте имя django-ec2 и нажмите Create repository:

ECR Create repositoryECR Create repository

AWS RDS

Теперь мы можем настроить базу данных RDS Postgres.

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

Перейдите в консоль RDS. Щелкните на Создать базу данных:

RDS Home

Выберите последнюю версию Postgres с шаблоном Free tier:

RDS Create database

В разделе Настройки установите:

  • Идентификатор экземпляра БД: djangoec2
  • Имя пользователя мастера: webapp
  • Выберите Автогенерация пароля

Оставайтесь с настройками по умолчанию для:

  • Размер экземпляра БД
  • Хранилище
  • Доступность и долговечность

Перейдите к разделу Connectivity и установите следующее:

  • Виртуальное частное облако (VPC): Default
  • Группа подсетей: по умолчанию
  • Публичный доступ: Нет
  • Группа безопасности VPC: django-ec2
  • Порт базы данных: 5432

RDS Create database connectivityRDS Create database connectivity

В разделе Мониторинг отключить Мониторинг производительности:

RDS Create database monitoring

Оставить аутентификацию базы данных в прежнем виде.

Откройте Дополнительная конфигурация и измените Имя начальной базы данных на djangoec2:

RDS Create database initial DB name

Оставить остальные настройки без изменений.

Наконец, нажмите Создать базу данных.

Нажмите на View credentials details, чтобы увидеть сгенерированный пароль для пользователя webapp:

RDS View credentials

Храните этот пароль в надежном месте. Вскоре его нужно будет сообщить приложению Django.

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

RDS DB details

AWS Security Group

В консоли EC2 нажмите Security Groups в боковой панели. Найдите и щелкните на идентификаторе группы django-ec2, чтобы отредактировать ее данные.

Нажмите на Редактировать правила входящих сообщений:

EC2 Security Group Details

Добавьте входящее правило, которое будет разрешать соединения Postgres с экземплярами, входящими в данную группу безопасности. Для этого необходимо:

  • нажмите на Add rule
  • выберите PostgreSQL для типа правила
  • выберите Custom для источника правила
  • щелкните в поле поиска и выберите django-ec2 Security Group
  • нажмите на Сохранить правила

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

EC2 Edit inbound rules

Project Config

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

Сначала клонируйте содержимое репозитория проекта на GitHub:

$ git clone https://github.com/testdrivenio/django-on-docker-letsencrypt django-on-docker-letsencrypt-aws
$ cd django-on-docker-letsencrypt-aws

Этот репозиторий содержит все необходимое для развертывания Dockerized Django с сертификатами Let's Encrypt HTTPS.

Docker Compose

При первом развертывании приложения необходимо выполнить следующие два шага, чтобы избежать проблем с сертификатами:

  1. Начните с выпуска сертификатов в среде Let's Encrypt staging
  2. Затем, когда все будет работать как надо, переключитесь на производственную среду Let's Encrypt

Более подробно об ограничениях Let's Encrypt в производственных средах можно прочитать в разделе Let's Encrypt из предыдущей статьи Securing a Containerized Django Application with Let's Encrypt.

Для тестирования обновите файл docker-compose.staging.yml. следующим образом:

version: '3.8'

services:
  web:
    build:
      context: ./app
      dockerfile: Dockerfile.prod
    image: <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:web
    command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
    expose:
      - 8000
    env_file:
      - ./.env.staging
  nginx-proxy:
    container_name: nginx-proxy
    build: nginx
    image: <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:nginx-proxy
    restart: always
    ports:
      - 443:443
      - 80:80
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
      - certs:/etc/nginx/certs
      - html:/usr/share/nginx/html
      - vhost:/etc/nginx/vhost.d
      - /var/run/docker.sock:/tmp/docker.sock:ro
    depends_on:
      - web
  acme-companion:
    image: nginxproxy/acme-companion
    env_file:
      - ./.env.staging.proxy-companion
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs:/etc/nginx/certs
      - html:/usr/share/nginx/html
      - vhost:/etc/nginx/vhost.d
      - acme:/etc/acme.sh
    depends_on:
      - nginx-proxy

volumes:
  static_volume:
  media_volume:
  certs:
  html:
  vhost:
  acme:

Для сервисов web и nginx-proxy обновите свойства image, чтобы использовать изображения из ECR (которые мы добавим в ближайшее время).

Примеры:

image: 123456789.dkr.ecr.us-east-1.amazonaws.com/django-ec2:web

image: 123456789.dkr.ecr.us-east-1.amazonaws.com/django-ec2:nginx-proxy

Значения состоят из URL хранилища (123456789.dkr.ecr.us-east-1.amazonaws.com), а также имени изображения (django-ec2) и тегов (web и nginx-proxy).

Для простоты мы используем один реестр для хранения обоих образов. Мы использовали символы web и nginx-proxy, чтобы различать их. В идеале следует использовать два реестра: один для web и один для nginx-proxy. Обновите это самостоятельно, если хотите.

Кроме свойств image, мы также удалили службу db (и связанный с ней том), поскольку мы используем RDS, а не управляем Postgres в контейнере.

Окружающая среда

Пришло время настроить файлы окружения для контейнеров web и acme-companion.

Сначала добавьте файл .env.staging для контейнера web:

DEBUG=0
SECRET_KEY=change_me
DJANGO_ALLOWED_HOSTS=<YOUR_DOMAIN.COM>
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=djangoec2
SQL_USER=webapp
SQL_PASSWORD=<PASSWORD-FROM-AWS-RDS>
SQL_HOST=<DATABASE-ENDPOINT-FROM-AWS-RDS>
SQL_PORT=5432
DATABASE=postgres
VIRTUAL_HOST=<YOUR_DOMAIN.COM>
VIRTUAL_PORT=8000
LETSENCRYPT_HOST=<YOUR_DOMAIN.COM>
CSRF_TRUSTED_ORIGINS=https://<YOUR_DOMAIN.COM>

Примечания:

  1. Измените <YOUR_DOMAIN.COM> на ваш реальный домен.
  2. Измените SQL_PASSWORD и SQL_HOST так, чтобы они соответствовали созданным в разделе RDS.
  3. Измените SECRET_KEY на некоторую длинную случайную строку.
  4. Контейнер VIRTUAL_HOST и VIRTUAL_PORT необходимы nginx-proxy для автоматического создания конфигурации обратного прокси.
  5. LETSENCRYPT_HOST нужен для того, чтобы nginx-proxy-companion мог выпустить сертификат Let's Encrypt для вашего домена.

Для целей тестирования/отладки вы можете использовать * для DJANGO_ALLOWED_HOSTS при первом развертывании, чтобы упростить работу. Только не забудьте ограничить количество разрешенных хостов после завершения тестирования.

Во-вторых, добавьте файл .env.staging.proxy-companion, обязательно обновив значение DEFAULT_EMAIL:

DEFAULT_EMAIL=youremail@yourdomain.com
ACME_CA_URI=https://acme-staging-v02.api.letsencrypt.org/directory
NGINX_PROXY_CONTAINER=nginx-proxy

Просмотрите раздел ACME companion service из статьи Securing a Containerized Django Application with Let's Encrypt, чтобы узнать больше об упомянутых выше переменных окружения.

Build and Push Docker Images

Теперь мы готовы к сборке образов Docker:

$ docker-compose -f docker-compose.staging.yml build

На сборку может потребоваться несколько минут. После этого мы готовы вывести изображения на ECR.

Сначала, предполагая, что у вас установлен awscli и заданы учетные данные AWS, войдите в Docker-репозиторий ECR:

$ aws ecr get-login-password --region <aws-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com
# aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com

Вы должны увидеть:

Login Succeeded

Затем переведите изображения в режим ECR:

$ docker-compose -f docker-compose.staging.yml push

Откройте свой репозиторий django-ec2 ECR, чтобы увидеть выложенные изображения:

ECR Images

Запуск контейнеров

Все готово к развертыванию.

Пришло время перейти к вашему экземпляру EC2.

Предполагая, что на вашем экземпляре создан каталог проекта, например /home/ubuntu/django-on-docker, скопируйте в него файлы и папки с помощью SCP:

$ scp -i /path/to/your/djangoletsencrypt.pem \
      -r $(pwd)/{app,nginx,.env.staging,.env.staging.proxy-companion,docker-compose.staging.yml} \
      ubuntu@public-ip-or-domain-of-ec2-instance:/path/to/django-on-docker

Далее подключитесь к своему экземпляру по SSH и перейдите в каталог проекта:

$ ssh -i /path/to/your/djangoletsencrypt.pem ubuntu@public-ip-or-domain-of-ec2-instance
$ cd /path/to/django-on-docker

Вход в Docker-репозиторий ECR.

$ aws ecr get-login-password --region <aws-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com

Вытащить изображения:

$ docker pull <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:web
$ docker pull <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:nginx-proxy

После этого можно приступать к раскрутке контейнеров:

$ docker-compose -f docker-compose.staging.yml up -d

После того как контейнеры будут запущены, перейдите в браузере на свой домен. Вы должны увидеть что-то вроде:

HTTPS certificate not secure

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

Как узнать, все ли работает?

Нажмите на кнопку "Дополнительно" и затем на кнопку "Продолжить". Теперь вы должны увидеть свое приложение. Загрузите изображение, а затем убедитесь, что его можно просмотреть по адресу https://yourdomain.com/media/IMAGE_FILE_NAME.

Выдача сертификата на продукцию

Теперь, когда все работает как надо, мы можем переключиться на производственную среду Let's Encrypt.

Свернуть существующие контейнеры и выйти из экземпляра:

$ docker-compose -f docker-compose.staging.yml down -v
$ exit

На локальной машине откройте docker-compose.prod.yml и внесите те же изменения, что и для staging-версии:

  1. Обновите свойства ìmage, чтобы они соответствовали вашим AWS ECR URL для сервисов ẁeb и nginx-proxy
  2. Удалите службу db вместе с соответствующим томом
version: '3.8'

services:
  web:
    build:
      context: ./app
      dockerfile: Dockerfile.prod
    image: 046505967931.dkr.ecr.us-east-1.amazonaws.com/django-ec2:web
    command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
    expose:
      - 8000
    env_file:
      - ./.env.prod
  nginx-proxy:
    container_name: nginx-proxy
    build: nginx
    image: 046505967931.dkr.ecr.us-east-1.amazonaws.com/django-ec2:nginx-proxy
    restart: always
    ports:
      - 443:443
      - 80:80
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
      - certs:/etc/nginx/certs
      - html:/usr/share/nginx/html
      - vhost:/etc/nginx/vhost.d
      - /var/run/docker.sock:/tmp/docker.sock:ro
    depends_on:
      - web
  acme-companion:
    image: nginxproxy/acme-companion
    env_file:
      - ./.env.prod.proxy-companion
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs:/etc/nginx/certs
      - html:/usr/share/nginx/html
      - vhost:/etc/nginx/vhost.d
      - acme:/etc/acme.sh
    depends_on:
      - nginx-proxy

volumes:
  static_volume:
  media_volume:
  certs:
  html:
  vhost:
  acme:

Далее создайте файл .env.prod, продублировав в нем файл .env.staging. Вносить в него какие-либо изменения не нужно.

Наконец, добавляем файл .env.prod.proxy-companion:

DEFAULT_EMAIL=youremail@yourdomain.com
NGINX_PROXY_CONTAINER=nginx-proxy

Снова постройте и выведите изображения на экран:

$ docker-compose -f docker-compose.prod.yml build
$ aws ecr get-login-password --region <aws-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com
$ docker-compose -f docker-compose.prod.yml push

Скопируйте новые файлы и папки на свой экземпляр с помощью SCP:

$ scp -i /path/to/your/djangoletsencrypt.pem \
      $(pwd)/{.env.prod,.env.prod.proxy-companion,docker-compose.prod.yml} \
      ubuntu@public-ip-or-domain-of-ec2-instance:/path/to/django-on-docker

Как и прежде, подключитесь к своему экземпляру по SSH и перейдите в каталог проекта:

$ ssh -i /path/to/your/djangoletsencrypt.pem ubuntu@public-ip-or-domain-of-ec2-instance
$ cd /path/to/django-on-docker

Снова войдите в свой Docker-репозиторий ECR:

$ aws ecr get-login-password --region <aws-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com

Вытащить изображения:

$ docker pull <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:web
$ docker pull <aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com/django-ec2:nginx-proxy

И, наконец, раскручиваем контейнеры:

$ docker-compose -f docker-compose.prod.yml up -d

Снова перейдите к своему домену. Предупреждение больше не должно появляться.

Поздравляем! Теперь вы используете производственный сертификат Let's Encrypt для своего Django-приложения, работающего на AWS EC2.

Хотите увидеть процесс создания сертификата в действии, посмотрите журналы:

$ docker-compose -f docker-compose.prod.yml logs acme-companion

Заключение

В этом руководстве вы развернули контейнерное приложение Django на EC2. Приложение работает за HTTPS-прокси Nginx с SSL-сертификатами Let's Encrypt. Также использовался экземпляр RDS Postgres, а образы Docker хранились на ECR.

Что дальше?

Настройте S3 и настройте Django на хранение статических и мультимедийных файлов в ведре, вне тома Docker

Вернуться на верх