Развертывание статического веб-сайта на AWS с помощью terraform


Введение

В предыдущей статье мы развернули API в виде контейнера docker на AWS EC2. В этой статье мы развернем простой веб-сайт на AWS без бэкенда (чтобы потом мы могли его добавить 😉). Мы будем распространять контент с помощью AWS S3 и AWS CloudFront (сервис сети доставки контента). В качестве фронтенда будет использован пример Start Bootstrap с открытым исходным кодом и лицензией MIT. Все, что мы развернем в статье, поддерживается AWS free tier. Весь код, относящийся к статье, можно найти в репозитории GitHub.

Важные замечания

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

  • Обслуживание типа содержимого было сделано как простая карта со значением по умолчанию text/htmp; charset=UTF-8.
  • Мы обслуживаем гораздо больше файлов, чем нужно, поэтому некоторые дополнительные файлы открыты.
  • Мы не шифруем ведра и не имеем версионности, чтобы упростить удаление ведра.
  • Использование сертификата CloudFront по умолчанию не рекомендуется, так как он использует старую версию SSL. В производственной системе следует использовать сертификат ACM.

Поскольку у нас нет бэкенда, мы не будем использовать AWS WAF, так как самое полезное, что он может сделать здесь — это защита от ботов. Для простого статического сайта это будет накладно. CloudFront уже обеспечивает достойный уровень защиты от DoS-атак 3 и 4 уровня.

Необходимые условия

В статье мы будем использовать terraform, и для этого необходимо иметь учетную запись AWS. Если у вас они не установлены и вы хотите попробовать развернуть все самостоятельно, вы можете найти инструкцию по установке в первой статье.

Код

Для фронтенда мы используем пример панели администратора, сделанный с помощью bootstrap. Это проект с открытым исходным кодом с лицензией MIT, который можно найти в репозитории GitHub.

Обзор

Базовая архитектура проекта описана на схеме.

Мы собираемся загрузить все файлы нашего сайта на AWS S3. Для обслуживания сайта мы будем использовать AWS CloudFront, чтобы уменьшить задержки и обеспечить кэширование статического контента. Для защиты доступа к объектам S3 мы будем использовать IAM, чтобы доступ к объекту могли получить только клиенты через CloudFront. Основные компоненты, которые мы рассмотрим, следующие:

  • CloudFront — сеть доставки контента AWS (CDN). Мы будем использовать ее для обслуживания нашего сайта и уменьшения задержек, чтобы обеспечить хороший пользовательский опыт.

  • AWS S3 — хранилище для всех статических данных. Ведро заблокировано для публичного доступа, и доступ к объектам возможен только из CloudFront.

Развертывание и распространение статического контента

S3

Как мы описывали ранее, есть 2 важные части, которые нам нужны. Первая — это наше ведро S3, которое нам нужно создать:

resource aws_s3_bucket static {
  // important to provide a global unique bucket name
  bucket = "boodyvo-go-example-static"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы сделать ведро приватным и удалить весь потенциальный доступ (кроме доступа к учетной записи AWS God), нам нужно обновить ведро:

resource aws_s3_bucket_acl static {
  bucket = aws_s3_bucket.static.id
  acl    = "private"
}

resource aws_s3_bucket_public_access_block website_bucket_public_access_block {
  bucket                  = aws_s3_bucket.static.id
  ignore_public_acls      = true
  block_public_acls       = true
  restrict_public_buckets = true
  block_public_policy     = true
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Еще одна вещь, которую рекомендуется сделать, — это принудительно установить право собственности на ведро для всех объектов, которые добавляются в ведро. Это отключит использование ACL, так что ведро всегда будет иметь полный контроль над объектами и управлять доступом к ним с помощью привилегий.

resource aws_s3_bucket_ownership_controls meta_static_resources {
  bucket = aws_s3_bucket.static.bucket

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы обслуживать наш сайт с S3, нам нужно сначала загрузить все имеющиеся у нас файлы. Исходя из структуры нашего проекта, мы собираемся загрузить все файлы в каталог frontend/ с помощью функции fileset terraform. Для обслуживания файлов через браузер важно обеспечить правильный content_type. Как уже упоминалось в упрощающей части в начале, мы собираемся предоставить content_type на основе простого отображения, где "text/html; charset=UTF-8" является значением по умолчанию. Карта представлена локальной переменной content_type_map.

locals {
  content_type_map = {
    css:  "text/css; charset=UTF-8"
    js:   "text/js; charset=UTF-8"
    svg:  "image/svg+xml"
  }
}

resource aws_s3_bucket_website_configuration static {
  bucket = aws_s3_bucket.static.id

  index_document {
    suffix = "index.html"
  }

  routing_rule {
    redirect {
      replace_key_with = "index.html"
    }
  }
}

resource aws_s3_object assets {
  for_each = fileset("${path.module}/../frontend", "**")

  bucket = aws_s3_bucket.static.id
  key    = each.value
  source = "${path.module}/../frontend/${each.value}"
  etag   = filemd5("${path.module}/../frontend/${each.value}")

  // simplification of the content type serving
  content_type = lookup(
    local.content_type_map,
    split(".", basename(each.value))[length(split(".", basename(each.value))) - 1],
    "text/html; charset=UTF-8",
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Примечание: Этого достаточно для примера проекта, но для производственного проекта карта может быть гораздо более детализированной или дист может быть более определенным на основе путей. Если мы не контролируем процесс загрузки объекта через Lambda@Edge или Lambda-функции, то во время загрузки файл на S3 зависит от содержимого. Более того, нам нужно отфильтровать dev-файлы, такие как .scss, лицензия, readme и т.д.

Еще одна вещь, которую нам нужно сделать, чтобы сделать возможным обслуживание нашего контента с помощью CloudFront, — это создать идентификатор доступа Origin Access Identity (OAI). Это политика, которая ограничивает доступ к ведру, так что только CloudFront может получить доступ к объектам. Таким образом, конечный пользователь может получить доступ к объектам только через CloudFront и не может обойти его.

// --- cloudfront.tf ---

locals {
  s3_origin_id = "s3-origin-example"
}

resource aws_cloudfront_origin_access_identity frontend {
  comment = "OAI for S3 frontend"
}

// --- s3.tf ---

data aws_iam_policy_document oai_access_policy {
  statement {
    actions   = ["s3:GetObject"]
    // as we use the bucket only for static content we provide an access for all objects in the bucket
    resources = ["${aws_s3_bucket.static.arn}/*"]

    principals {
      type        = "AWS"
            // the identity specifies in cloudfront.tf
      identifiers = [aws_cloudfront_origin_access_identity.frontend.iam_arn]
    }
  }
}

resource aws_s3_bucket_policy oai_access {
  bucket = aws_s3_bucket.static.id
  policy = data.aws_iam_policy_document.oai_access_policy.json
}
Вход в полноэкранный режим Выход из полноэкранного режима

Еще одна вещь, которую мы собираемся упростить, связана с CORS, поскольку мы предоставим возможность доступа к нашему сайту отовсюду с помощью allowed_origins = ["*"].


resource aws_s3_bucket_cors_configuration website {
  bucket = aws_s3_bucket.static.id

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET"]
    allowed_origins = ["*"]
    expose_headers  = []
    max_age_seconds = 3600
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

CloudFront

CloudFront предоставляет возможность создания дистрибутива, который настраивает, как контент должен распространяться и обслуживаться. Таким образом мы контролируем гео-ограничения, правила кэширования, правила перенаправления запросов, безопасность, доступ и т.д. Мы собираемся создать дистрибутив.

resource aws_cloudfront_distribution frontend {
  origin {
    domain_name = aws_s3_bucket.static.bucket_regional_domain_name
    origin_id   = local.s3_origin_id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "Distribution for example website"
  default_root_object = "index.html"

  aliases = []

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = local.s3_origin_id

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  price_class = "PriceClass_100"

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  wait_for_deployment = true
}
Вход в полноэкранный режим Выход из полноэкранного режима

Есть несколько мест, на которые мы можем обратить внимание в этом дистрибутивном ресурсе:

Кроме этих важных пунктов мы также указали поведение кэша, гео-ограничение и индексную страницу по умолчанию.

Мы хотим иметь URL дистрибутива в качестве выходных данных, чтобы использовать его для тестирования нашего сайта:

output service_ip {
  value = aws_cloudfront_distribution.frontend.domain_name
}
Войти в полноэкранный режим Выход из полноэкранного режима

Развертывание

Для развертывания нам нужно запустить все из каталога 2_static_website:

terraform -chdir=iaac init  # initialization of terraform modules
terraform -chdir=iaac apply # applying the resources
Войти в полноэкранный режим Выйти из полноэкранного режима

Создание дистрибутива может занять около 20 минут. После этого мы получим вывод со значением host_name, которое представляет собой URL сайта.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

host_name = "dqiv81ppt2ffj.cloudfront.net"
Вход в полноэкранный режим Выход из полноэкранного режима

Используя URL нашего дистрибутива dqiv81ppt2ffj.cloudfront.net из вывода, мы можем получить доступ к сайту. Он должен выглядеть следующим образом:

Чтобы уничтожить развертывание, выполните следующие действия:

terraform -chdir=iaac destroy
Войти в полноэкранный режим Выйти из полноэкранного режима

После этого

В статье мы развернули простое статическое веб-приложение и распространили его с помощью AWS CDN сервиса CloudFront. Мы сделали некоторые упрощения и собираемся улучшить развертывание в будущем.

Если у вас есть предложения по улучшению или темы, или какие-либо вопросы, не стесняйтесь обращаться ко мне в комментариях 👇😊.

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