Целью большинства команд веб-разработки является переход к непрерывному развёртыванию, для которого одним из существенных факторов может послужить наличие у каждой ветви функции собственного URL развёртывания (например, my-feature.example.com).
Любой, кто знаком с Vercel, знает, насколько полезны эти области предпросмотра для команд разработчиков. Vercel, AWS Amplify Console и другие играют важную роль в большинстве проектов, хотя если у вас есть требования к построению вроде использования нескольких источников, разных режимов кэширования и т.д., то вам может потребоваться управлять инстансом CloudFront самостоятельно.
Для крупных проектов, в которых есть отдел контроля качества, наличие выделенного URL для каждой функции означает, что вы не ограничены малым количеством статических сред вроде “тестирования” и “обкатки”. Вы можете масштабировать вашу команду разработки, не нарушая строгих требований контроля качества.
Использование Terraform, который я вам покажу, на мой взгляд является отличным способом предоставления предпросмотра URL при помощи AWS без задействования дополнительных ресурсов. Т.е. вам не потребуется разворачивать новые дистрибуции CloudFront или заморачиваться созданием новых DNS-записей для каждой ветви функционала.
Вот что мы будем создавать — простые области предпросмотра посредством AWS.Для этого мы будем использовать следующие ресурсы:
Я буду использовать подстановочные домены. Сначала мы создадим SSL-сертификат при помощи ACM, а затем прикрепим подстановочный домен к CloudFront и создадим псевдоним записи в Route 53, после чего в завершении добавим функции [email protected] для перенаправления наших динамических поддоменов по верному пути на S3.
Для простоты на всех этапах и во всех средах мы будем использовать один аккаунт AWS.
Самая суть этого проекта заключается в лямбда-функциях, поэтому можете спокойно пропустить первую половину статьи, если настройка CloudFront вам знакома.
Для его создания я буду использовать 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 — это идеальное место для хранения статических объектов вроде 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], то, по сути, это лямбда-функция, которая дублируется глобально автоматически и может быть прикреплена к четырём разным событиям 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 — это быстрая сеть доставки контента (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
.
В завершении всего этого процесса вам потребуется убедиться, что пользователи могут направляться в вашу 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
Перевод статьи Carlos Palacin Rubio: How to maximize Android’s UI reusability — 5 common mistakes
Комментарии