Развертывание приложения Django на AWS с помощью Terraform. Домен Namecheap + SSL

В предыдущих шагах мы развернули Django на AWS ECS, подключили его к PostgreSQL RDS и настроили GitLab CI/CD.

В этом шаге мы должны:

  • Подключить домен Namecheap к DNS-зоне Route53.
  • Создать SSL-сертификат с помощью Certificate Manager.
  • Перенаправить HTTP-трафик на HTTPS и отключить ALB-хост для приложения Django.
  • Добавьте маршрут /health/ для проверки здоровья.

Настройка API Namecheap

У меня уже есть доменное имя на Namecheap. Поэтому я решил подключить домен Namecheap к зоне AWS Route53. Но вы можете зарегистрировать домен в AWS Route53.

Сначала давайте включим доступ к API для Namecheap. Просмотрите это руководство, чтобы получить APIKey и добавьте свой IP в белый список.

Во-вторых, добавьте провайдер Namecheap в проект Terraform. Добавьте в файл provider.tf следующий код:

terraform {
  required_providers {
    namecheap = {
      source  = "namecheap/namecheap"
      version = ">= 2.0.0"
    }
  }
}

provider "namecheap" {
  user_name   = var.namecheap_api_username
  api_user    = var.namecheap_api_username
  api_key     = var.namecheap_api_key
  use_sandbox = false
}
Вход в полноэкранный режим Выйти из полноэкранного режима

В variables.tf добавьте:

# Namecheap
variable "namecheap_api_username" {
  description = "Namecheap APIUsername"
}
variable "namecheap_api_key" {
  description = "Namecheap APIKey"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Также добавьте переменные TF_VAR_namecheap_api_username и TF_VAR_namecheap_api_key в .env, чтобы обеспечить значения соответствующих переменных Terraform.

TF_VAR_namecheap_api_username=YOUR_API_USERNAME
TF_VAR_namecheap_api_key=YOUR_API_KEY
Вход в полноэкранный режим Выход из полноэкранного режима

Импортируйте переменные .env с помощью export $(cat .env | xargs) и запустите terraform init для добавления провайдера Namecheap в проект.

Подключение домена к AWS

Теперь создадим зону Route53 для домена Namecheap и настроим серверы имен AWS. Таким образом, все DNS-запросы будут направляться на серверы имен AWS Route53, и мы сможем управлять DNS-записями из зоны AWS Route53.

Добавьте в variables.tf следующий код:

# Domains
variable "prod_base_domain" {
  description = "Base domain for production"
  default = "example53.xyz"
}
variable "prod_backend_domain" {
  description = "Backend web domain for production"
  default = "api.example53.xyz"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавьте файл route53.tf:

resource "aws_route53_zone" "prod" {
  name = var.prod_base_domain
}

resource "namecheap_domain_records" "prod" {
  domain = var.prod_base_domain
  mode   = "OVERWRITE"

  nameservers = [
    aws_route53_zone.prod.name_servers[0],
    aws_route53_zone.prod.name_servers[1],
    aws_route53_zone.prod.name_servers[2],
    aws_route53_zone.prod.name_servers[3],
  ]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Запустите terraform apply. Проверьте серверы имен на Namecheap:

Создание SSL-сертификата

Теперь давайте создадим SSL сертификат и настроим DNS A запись для домена api.example53.xyz.

Добавьте в route53.tf следующий код:

...

resource "aws_acm_certificate" "prod_backend" {
  domain_name       = var.prod_backend_domain
  validation_method = "DNS"
}

resource "aws_route53_record" "prod_backend_certificate_validation" {
  for_each = {
    for dvo in aws_acm_certificate.prod_backend.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.prod.zone_id
}

resource "aws_acm_certificate_validation" "prod_backend" {
  certificate_arn         = aws_acm_certificate.prod_backend.arn
  validation_record_fqdns = [for record in aws_route53_record.prod_backend_certificate_validation : record.fqdn]
}

resource "aws_route53_record" "prod_backend_a" {
  zone_id = aws_route53_zone.prod.zone_id
  name    = var.prod_backend_domain
  type    = "A"

  alias {
    name                   = aws_lb.prod.dns_name
    zone_id                = aws_lb.prod.zone_id
    evaluate_target_health = true
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь мы собираемся создать новый SSL сертификат для api.example53.xyz, подтвердить SSL сертификат через DNS CNAME запись, и добавить DNS A запись в Load Balancer.

Примените изменения с помощью terraform apply и дождитесь проверки сертификата. Обычно это занимает до нескольких минут. Но в некоторых случаях это может занять несколько часов. Более подробную информацию вы можете найти здесь.

Перенаправление HTTP на HTTPS

Теперь давайте используем выданный SSL-сертификат для включения HTTPS. Замените блок resource "aws_lb_listener" "prod_http" в файле load_balancer.tf следующим кодом:

# Target listener for http:80
resource "aws_lb_listener" "prod_http" {
  load_balancer_arn = aws_lb.prod.id
  port              = "80"
  protocol          = "HTTP"
  depends_on        = [aws_lb_target_group.prod_backend]

  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# Target listener for https:443
resource "aws_alb_listener" "prod_https" {
  load_balancer_arn = aws_lb.prod.id
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  depends_on        = [aws_lb_target_group.prod_backend]

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.prod_backend.arn
  }

  certificate_arn = aws_acm_certificate_validation.prod_backend.certificate_arn
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь мы перенаправляем незащищенный HTTP-трафик на HTTPS и добавляем слушателя для HTTPS-порта. Примените изменения и проверьте URL https://api.example53.xyz. Вы должны увидеть стартовую страницу Django.

Настройка переменной ALLOWED_HOSTS

Теперь давайте предоставим приложению Django параметр ALLOWED_HOSTS. Это важно для предотвращения атак на заголовки HTTP Host. Таким образом, приложение Django должно принимать только наш домен api.example53.xyz в заголовке host.

Сейчас Django принимает любой домен, например, домен балансировщика нагрузки. Посетите https://prod-1222631842.us-east-2.elb.amazonaws.com, чтобы проверить этот факт. Вы можете проигнорировать предупреждение о недействительном SSL-сертификате и увидеть, что Django отвечает на этот хост.

Также, давайте отключим режим Debug и удалим значение SECRET_KEY из кода для повышения безопасности. Добавьте переменную TF_VAR_prod_backend_secret_key со случайно сгенерированным значением в .env, запустите export $(cat .env | xargs), и укажите этот var в variables.tf:

variable "prod_backend_secret_key" {
  description = "production Django's SECRET_KEY"
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Далее передайте имя домена и SECRET_KEY в ecs.tf, настройте переменные SECRET_KEY, DEBUG и ALLOWED_HOSTS в backend_container.json.tpl и примените изменения:

locals {
  container_vars = {
    ...

    domain = var.prod_backend_domain
    secret_key = var.prod_backend_secret_key
  }
}
Войдите в полноэкранный режим Выйдите из полноэкранного режима
"environment": [
  ...
  {
    "name": "SECRET_KEY",
    "value": "${secret_key}"
  },
  {
    "name": "DEBUG",
    "value": "off"
  },
  {
    "name": "ALLOWED_HOSTS",
    "value": "${domain}"
  }
],
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь у нас есть все необходимые переменные окружения на ECS. Перейдите в приложение Django и измените settings.py:

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env("SECRET_KEY", default="ewfi83f2ofee3398fh2ofno24f")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env("DEBUG", cast=bool, default=True)

ALLOWED_HOSTS = env("ALLOWED_HOSTS", cast=list, default=["*"])
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь мы получаем переменные SECRET_KEY, DEBUG, и ALLOWED_HOSTS из переменных окружения. Мы предоставляем SECRET_KEY по умолчанию, чтобы позволить запустить приложение локально без указания SECRET_KEY в файле .env.

Проверка здоровья

Все запросы пользователей будут иметь заголовок Host api.example53.xyz. Но у нас также есть запросы на проверку работоспособности от балансировщика нагрузки.

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

Для HTTP или HTTPS запросов на проверку здоровья заголовок хоста содержит IP-адрес узла балансировщика нагрузки и порт слушателя, а не IP-адрес цели и порт проверки здоровья.

Мы не знаем IP-адрес балансировщика нагрузки. Кроме того, этот IP может быть изменен через некоторое время. Поэтому мы не можем добавить хост балансировщика нагрузки в ALLOWED_HOSTS.

Решением является написание пользовательского промежуточного ПО, которое возвращает успешный ответ до проверки хоста в SecurityMiddleware.

Сначала перейдите в инфраструктуру, измените URL проверки здоровья в load_balancer.tf на /health/ и примените изменения:

resource "aws_lb_target_group" "prod_backend" {
  ...
  health_check {
    path = "/health/"
    ...
  }
}
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Вернитесь в проект Django и создайте django_aws/middleware.py:

from django.http import HttpResponse
from django.db import connection


def health_check_middleware(get_response):
    def middleware(request):
        # Health-check request
        if request.path == "/health/":
            # Check DB connection is healthy
            with connection.cursor() as cursor:
                cursor.execute("SELECT 1")

            return HttpResponse("Healthy!")

        # Regular requests
        return get_response(request)

    return middleware
Войдите в полноэкранный режим Выход из полноэкранного режима

Добавьте это промежуточное ПО в settings.py перед SecurityMiddleware:

MIDDLEWARE = [
    'django_aws.middleware.health_check_middleware',
    'django.middleware.security.SecurityMiddleware',
    ...
]
Вход в полноэкранный режим Выйти из полноэкранного режима

Запустите python manage.py runserver и проверьте 127.0.0.1:8000/health/ URL в браузере. Вы должны увидеть текстовый ответ Healthy!.

Зафиксируйте изменения, дождитесь конвейера и снова проверьте домен балансировщика нагрузки https://prod-57218461274.us-east-2.elb.amazonaws.com/. Теперь мы получаем ошибку Bad Request. Также мы не увидели трассировки или другой отладочной информации, поэтому мы можем быть уверены, что режим отладки отключен.

Также перейдите по адресу https://prod-57218461274.us-east-2.elb.amazonaws.com/health/ в браузере, чтобы проверить health_check_middleware. Мы получаем ответ Healthy!. Таким образом, балансировщик нагрузки сможет проверить здоровье контейнеров без предоставления корректного заголовка Host.

Поздравляем! Мы успешно настроили доменное имя, создали проверки здоровья, отключили режим отладки и удалили значение SECRET_KEY из исходного кода. Не забудьте выложить код инфраструктуры на GitLab.

Вы можете найти исходный код бэкенда и инфраструктурных проектов здесь и здесь.

Если вам нужен технический консалтинг по вашему проекту, загляните на наш сайт или свяжитесь со мной напрямую на LinkedIn.

Оцените статью
devanswers.ru
Добавить комментарий