Много узлов, одна распределенная система


Целью большинства команд веб-разработки является переход к непрерывному развёртыванию, для которого одним из существенных факторов может послужить наличие у каждой ветви функции собственного URL развёртывания (например, my-feature.example.com).

Любой, кто знаком с Vercel, знает, насколько полезны эти области предпросмотра для команд разработчиков. Vercel, AWS Amplify Console и другие играют важную роль в большинстве проектов, хотя если у вас есть требования к построению вроде использования нескольких источников, разных режимов кэширования и т.д., то вам может потребоваться управлять инстансом CloudFront самостоятельно. 

Для крупных проектов, в которых есть отдел контроля качества, наличие выделенного URL для каждой функции означает, что вы не ограничены малым количеством статических сред вроде “тестирования” и “обкатки”. Вы можете масштабировать вашу команду разработки, не нарушая строгих требований контроля качества. 

Использование Terraform, который я вам покажу, на мой взгляд является отличным способом предоставления предпросмотра URL при помощи AWS без задействования дополнительных ресурсов. Т.е. вам не потребуется разворачивать новые дистрибуции CloudFront или заморачиваться созданием новых DNS-записей для каждой ветви функционала. 

Вот что мы будем создавать — простые области предпросмотра посредством AWS.

Для этого мы будем использовать следующие ресурсы:

  • Amazon CloudFront для нашей CDN.
  • Amazon S3 для хранения объектов.
  • [email protected] для переписывания URL.
  • Route 53 для наших DNS-записей.

Подход

Я буду использовать подстановочные домены. Сначала мы создадим SSL-сертификат при помощи ACM, а затем прикрепим подстановочный домен к CloudFront и создадим псевдоним записи в Route 53, после чего в завершении добавим функции [email protected] для перенаправления наших динамических поддоменов по верному пути на S3.

Для простоты на всех этапах и во всех средах мы будем использовать один аккаунт AWS.

Самая суть этого проекта заключается в лямбда-функциях, поэтому можете спокойно пропустить первую половину статьи, если настройка CloudFront вам знакома.

Подстановочный SSL-сертификат

Для его создания я буду использовать Terraform.

resource "aws_acm_certificate" "main" { domain_name = local.cdn_domain_name subject_alternative_names = [local.wildcard_domain] validation_method = "DNS" lifecycle { create_before_destroy = true } }

Этот код создаёт сертификат, тем не менее Amazon нужно проверить, что мы действительно владеем этим доменным именем. Мы можем сделать эту проверку автоматической, добавив DNS-запись при помощи Route 53.

resource "aws_route53_record" "validation" { name = aws_acm_certificate.main.domain_validation_options[0].resource_record_name type = aws_acm_certificate.main.domain_validation_options[0].resource_record_type records = [aws_acm_certificate.main.domain_validation_options[0].resource_record_value] zone_id = data.aws_route53_zone.external.zone_id ttl = 60 allow_overwrite = true }

И в завершении добавляем шаг проверки.

resource "aws_acm_certificate_validation" "main" { certificate_arn = aws_acm_certificate.main.arn validation_record_fqdns = [ aws_route53_record.validation.fqdn ] timeouts { create = "10m" } }

Amazon S3

Amazon S3 — это идеальное место для хранения статических объектов вроде HTML/CSS/JS. Нам нужна лишь корзина с включенным хостингом веб-сайта. Вы можете предпочесть использовать идентификаторы доступа к источнику для дальнейшей защиты файлов, хотя в большинстве случаев хостинга веб-сайта на S3 вполне достаточно. 

resource "aws_s3_bucket" "bucket" { bucket = local.bucket_name acl = "public-read" policy = data.aws_iam_policy_document.bucket.json website { index_document = "index.html" error_document = "error.html" } } data "aws_iam_policy_document" "bucket" { statement { actions = ["s3:GetObject"] resources = [ "arn:aws:s3:::${local.bucket_name}", "arn:aws:s3:::${local.bucket_name}/*" ] principals { identifiers = ["*"] type = "*" } } }

Конвейер развёртывания

Что здесь не рассмотрено, так это конвейер непрерывной интеграции (CI). Ваш процесс сборки должен выполняться как обычно, однако при развёртывании в качестве префикса-ключа должен использоваться транслитерированный url (slug) ветки Git.

Пример развёртывания с помощью GitLab может выглядеть следующим образом:

deploy: stage: deploy script: - S3="s3://example-bucket/$CI_COMMIT_REF_SLUG" - aws s3 rm $S3 --recursive - aws s3 cp $S3 --recursive environment: name: $CI_COMMIT_REF_SLUG url: https://$CI_COMMIT_REF_SLUG.example.com

Обратите внимание, как мы динамически установили url среды. Это отличная возможность для пул-реквестов, поскольку вы можете просмотреть историю развёртывания и использовать удобную вложенную ссылку. 

В этом примере я определил среду как число PR

[email protected]

А вот и важнейшая часть. Если вы не знакомы с [email protected], то, по сути, это лямбда-функция, которая дублируется глобально автоматически и может быть прикреплена к четырём разным событиям CloudFront.

Для того, чтобы перенаправить запрос верной функциональности, нам нужно его проинспектировать и определить, в какой каталог S3 отослать.

Предположим, нам нужно, чтобы все области функциональностей имели префикс preview. Тогда для нашей директории develop адрес будет выглядеть так: preview-develop.example.com. При этом запросы для всех поддоменов, не начинающихся с preview (например, www.example.com), нам следует направлять в корневую директорию.

Чтобы это осуществить, нам нужно создать лямбду и прикрепить её к событию запроса источника (origin request) CloudFront. В моём случае для этого я переписал путь к источнику, добавив к нему имя ветки в виде префикса.

exports.handler = (event, context, callback) => { const { request } = event.Records[0].cf; try { const host = request.headers['x-forwarded-host'][0].value; const branch = host.match(/^preview-([^\.]+)/)[1]; request.origin.custom.path = `/${branch}`; } catch (e) { request.origin.custom.path = `/master`; } return callback(null, request); };

Пока что это не сработает, поскольку объект запроса ожидает заголовок x-forwarded-host. По умолчанию он не будет доступен в запросе к источнику, но можно переписать этот заголовок в лямбде, обрабатывающей событие запроса зрителя (viewer request). Следовательно, на данном этапе нам нужно создать эту лямбду.

exports.handler = (event, context, callback) => { const { request } = event.Records[0].cf; request.headers['x-forwarded-host'] = [ { key: 'X-Forwarded-Host', value: request.headers.host[0].value } ]; return callback(null, request); };

Для краткости я не стал включать сюда код Terraform, но вы можете посетить созданный мной репозиторий Git и самостоятельно в нём покопаться. Ознакомьтесь с моим лямбда-модулем, где вы сможете увидеть, какие разрешения ему требуются, чтобы CloudFront запускал лямбды.eknowles/aws-cloudfront-preview-domainsThis repo contains the Terraform code to build a CDN with feature branch subdomains. Article =>…github.com

Amazon CloudFront

Amazon CloudFront — это быстрая сеть доставки контента (CDN). Она идеально подходит для кэширования веб-приложений в точках присутствия. В нашем текущем примере мы создадим дистрибуцию CloudFront с одним источником S3.

resource "aws_cloudfront_distribution" "cdn" { enabled = true default_root_object = "index.html" price_class = "PriceClass_100" aliases = [local.cdn_domain_name, local.wildcard_domain] viewer_certificate { acm_certificate_arn = aws_acm_certificate.main.arn ssl_support_method = "sni-only" minimum_protocol_version = "TLSv1.1_2016" } origin { domain_name = aws_s3_bucket.bucket.website_endpoint origin_id = "app" custom_origin_config { http_port = 80 https_port = 443 origin_protocol_policy = "http-only" origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] } } default_cache_behavior { target_origin_id = "app" allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] cached_methods = ["GET", "HEAD"] min_ttl = 0 default_ttl = 0 # 3600 max_ttl = 0 # 86400 viewer_protocol_policy = "redirect-to-https" lambda_function_association { event_type = "origin-request" lambda_arn = module.origin_request_lambda.qualified_arn include_body = false } lambda_function_association { event_type = "viewer-request" lambda_arn = module.viewer_request_lambda.qualified_arn include_body = false } forwarded_values { query_string = false headers = ["x-forwarded-host"] cookies { forward = "none" } } } restrictions { geo_restriction { restriction_type = "none" } } }

Сюда нужно поместить очень многое, поскольку CloudFront — это огромный ресурс, который может требовать вплоть до 10 минут на своё обновление. Я же включил лишь необходимые компоненты. В зависимости от приложения, вы можете настроить вашу CDN по-своему, определив разные режимы кэширования для разных маршрутов, но стоит обратить внимание на раздел forwarded_values. Важно убедиться, что вы внесли в белый список заголовок x-forwarded-host, чтобы лямбда, обрабатывающая запросы, могла видеть значения. 

Что касается псевдонимов, то я включил “голый” домен example.com, а также подстановочный *.example.com. Это важно для того, чтобы ваша CDN разрешала эти запросы. 

Я предположил, что вы будете выполнять это не в продакшен аккаунте AWS, поэтому установил TTL как 0.

Добавление DNS-записей при помощи Route53

В завершении всего этого процесса вам потребуется убедиться, что пользователи могут направляться в вашу CDN. Здесь мы просто добавим две A-записи, выступающие псевдонимами вашей дистрибуции CloudFront: одну для “голого” домена и вторую для подстановочного.  

resource "aws_route53_record" "wildcard_cdn" { zone_id = data.aws_route53_zone.external.zone_id name = local.wildcard_domain type = "A" alias { name = aws_cloudfront_distribution.cdn.domain_name zone_id = aws_cloudfront_distribution.cdn.hosted_zone_id evaluate_target_health = false } } resource "aws_route53_record" "naked_cdn" { zone_id = data.aws_route53_zone.external.zone_id name = local.cdn_domain_name type = "A" alias { name = aws_cloudfront_distribution.cdn.domain_name zone_id = aws_cloudfront_distribution.cdn.hosted_zone_id evaluate_target_health = false } }

Итог

Мы это сделали! Теперь у вас должно сложиться хорошее понимание того, как можно использовать подстановочные домены, чтобы создавать бесконечные области предпросмотра для ваших веб-приложений. Что же дальше? Вы можете использовать лямбду для запросов зрителя, чтобы обезопасить эти области предпросмотра проверкой присутствия IP запроса в белом списке. Подробнее о [email protected] вы можете узнать в блоге AWS.

Надеюсь, что для вас эта тема оказалась полезной. Есть много способов организации продакшен-сред, и это лишь один из них.

 Я же ещё раз напоминаю, что исходный код Terraform и тестовые данные доступны в репозитории на GitHub:eknowles/aws-cloudfront-preview-domainsThis repo contains the Terraform code to build a CDN with feature branch subdomains. Article =>…github.com


Перевод статьи Vaidehi Joshi: Many Nodes, One Distributed System


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.