Блог Amazon Web Services

Представляем CloudFront Functions: запускайте код в любых масштабах на периферии с низкой задержкой

Зарегистрируйтесь, чтобы получать приглашения на мероприятия AWS на русском языке.

Оригинал статьи: ссылка (Danilo Poccia, Chief Evangelist (EMEA))

С помощью Amazon CloudFront вы можете безопасно доставлять данные, видео, приложения и API клиентам по всему миру с низкой задержкой и на высокой скорости. Для индивидуальной настройки возвращаемого результата и обеспечения минимально возможной задержки многие современные приложения выполняют часть бизнес-логики на периферии. Такие сценарии можно сгруппировать в две основные категории:

  • Сложные операции, требующие большое количество вычислительных ресурсов, которые выполняются, если нужный объект не найден в кэше. В 2017 году мы запустили Lambda@Edge, которая позволяет создать полностью программируемую среду для бессерверных вычислений на периферии: с помощью неё можно выполнять широкий спектр задач по сложной обработке запросов. Функции Lambda@Edge запускаются на периферийных серверах кэширования в регионах (regional edge cache), которые обычно находятся в регионе AWS, ближайшем к используемой клиентом точке присутствия CloudFront. Например, при потоковой передаче видео или аудио вы можете использовать Lambda@Edge для создания и отправки нужных сегментов «на лету», что позволяет уменьшить требования к масштабируемости сервера-источника. Другой популярный пример – использование Lambda@Edge и базы данных Amazon DynamoDB для конвертации удобных коротких URL-адресов в полные адреса.
  • Простые преобразования запросов или ответов HTTP(s), которые могут быть выполнены функциями за очень короткое время. Для такого сценария вам нужна гибкая среда программирования с высокой производительностью, масштабируемостью и экономичностью, что позволит запускать функцию для каждого запроса.

Мы рады сообщить о выпуске CloudFront Functions: это новая платформа бессерверного выполнения скриптов, которая поможет вам со второй категорией сценариев, описанных выше. Она позволяет запускать легковесный JavaScript-код на более чем 218 периферийных местоположениях CloudFront по стоимости примерно равной 1/6 от цены Lambda@Edge.

Архитектурная диаграмма

CloudFront Functions идеально подходят для простой обработки веб-запросов, например:

  • Работа с ключом кэширования и нормализация: изменение атрибутов HTTP-запроса (URL, заголовки, файлы cookie, строка запроса) для создания ключа кэширования, который является уникальным идентификатором объектов в кэше. Он используется для проверки, содержится ли объект в кэше или нет. Например, вы можете сохранять данные в кэше на основании заголовка, который определяет тип устройства пользователя. Таким образом, вы создаёте две версии сайта: для мобильных и для настольных устройств. Кроме того, с помощью изменения атрибутов вы можете нормализовать разные запросы к одному ключу кэширования, тем самым повысив частоту попадания в кэш.
  • Изменения URL и перенаправления: вы можете вернуть ответ, который перенаправит запрос на другой URL-адрес. Например, перенаправить неавторизованного пользователя на страницу входа вместо страницы с закрытым доступом. Также изменение URL можно использовать для A/B-тестирования.
  • Изменение HTTP-заголовков: просматривайте, добавляйте, изменяйте или удаляйте любые заголовки запросов и ответов. Например, вы можете добавить заголовки HTTP Strict Transport Security (HSTS) к ответу или добавить новый заголовок запроса к серверу-источнику, в котором будет содержаться IP-адрес пользователя.
  • Авторизация доступа: внедрите контроль доступа к содержимому, предоставляемому через CloudFront, с помощью создания и проверки пользовательских токенов, таких как HMACили JSON web tokens (JWT), для разрешения или запрета запросов.

Чтобы обеспечить производительность и масштаб, которые требуются современным приложениям, CloudFront Functions используют новую модель изоляции на уровне процессов вместо изоляции на уровне виртуальных машин, которая используется в AWS Lambda и Lambda@Edge. Для этого нам пришлось ввести некоторые ограничения, такие как запрет на доступ к сети и файловой системе. Кроме того, функции должны выполняться менее 1мс (миллисекунды), что позволяет обработать миллионы запросов в секунду. Благодаря этому, функции практически не влияют на общую производительность сети доставки контента (CDN).

По аналогии с Lambda@Edge, CloudFront Functions выполняют ваш исходный код в ответ на триггеры (события) из CloudFront, а именно, они могут вызываться после получения запроса от пользователя (viewer request) или перед отправкой ответа пользователю (viewer response).

Функции Lambda@Edge, помимо этого, могут выполняться перед тем, как CloudFront перенаправит запрос к серверу-источнику (origin request), и после того, как CloudFront получит ответ от сервера-источника (origin response). Вы можете использовать CloudFront Functions и Lambda@Edge вместе в зависимости от того, когда вам необходимо изменить содержимое: до или после кэширования.

Триггеры для вызова CloudFront Functions и Lambda@Edge

Если вам необходимы возможности Lambda@Edge, которые недоступны в CloudFront Functions, например, сетевой доступ или более долгое время выполнения, вы всё так же можете использовать Lambda@Edge до и после кэширования содержимого в CloudFront.

Триггеры для вызова Lambda@Edge

Чтобы помочь вам лучше понять различия между CloudFront Functions и Lambda@Edge, мы создали следующее простое сравнение:

CloudFront Functions Lambda@Edge
Среды выполнения JavaScript
(совместимый с ECMAScript 5.1)
Node.js, Python
Место выполнения 218+ периферийных местоположений CloudFront 13 периферийных серверов кэширования CloudFront в регионах
Поддерживаемые триггеры CloudFront Запрос от пользователя (viewer request)
Ответ пользователю (viewer response)
Запрос от пользователя (viewer request)
Ответ пользователю (viewer response)
Запрос к источнику (origin request)Ответ от источника (origin response)
Максимальное время выполнения Менее 1 миллисекунды 5 секунд (для триггеров пользователя)
30 секунд (для триггеров источника)
Максимальный объём памяти 2 MB 128 MB (для триггеров пользователя)
10 GB (для триггеров источника)
Максимальный размер 10 KB 1 MB (для триггеров пользователя)
50 MB (для триггеров источника)
Доступ к сети Нет Да
Доступ к файловой системе Нет Да
Доступ к телу запроса Нет Да
Цена Доступен бесплатный уровень пользования;
оплата за запрос
Нет бесплатного уровня пользования; оплата за запрос и за время выполнения функции

Давайте теперь посмотрим, как это работает на практике.

Использование CloudFront Functions из консоли
Я хочу изменять содержимое моего сайта в зависимости от страны пользователя. Для этого я использую раздачу CloudFront, созданную для бакета S3 в качестве источника. Первым делом я создаю политику кэширования, которая добавляет заголовок CloudFront-Viewer-Country (он содержит двухбуквенное обозначение страны пользователя) в ключ кэширования. CloudFront Functions могут работать с заголовками, созданными CloudFront (такими как заголовки геолокации or заголовки с информацией об устройстве), только если они включены в политику источника или политику ключа кэширования.

В консоли CloudFront я перехожу в раздел Functions в меню слева и нажимаю Create function. Я ввожу название моей функции и нажимаю Continue.

Скриншот консоли: создание функции

Здесь мы пройдём по следующим шагам создания функции:

  1. Build: предоставление исходного кода функции.
  2. Test: проверка функции с помощью образца запроса.
  3. Publish: перенос функции из стадии разработки в производственную среду.
  4. Associate: настройка функции для одной или нескольких раздач CloudFront.

Скриншот консоли: вкладка Build

1. На вкладке Build я могу получить доступ к коду в двух средах: Development, используемый для тестирования, и Live, используемый одной или несколькими раздачами CloudFront. На вкладке Development я добавляю следующий исходный код и нажимаю Save:

function handler(event) {
  var request = event.request;
  var supported_countries = ['de', 'it', 'fr'];
  if (request.uri.substr(3,1) != '/') {
    var headers = request.headers;
    var newUri;
    if (headers['cloudfront-viewer-country']) {
      var countryCode = headers['cloudfront-viewer-country'].value.toLowerCase();
      if (supported_countries.includes(countryCode)) {
        newUri = '/' + countryCode + request.uri;
      }
    }
    if (newUri === undefined) {
      var defaultCountryCode = 'en';
      newUri = '/' + defaultCountryCode + request.uri;
    }
    var response = {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        "location": { "value": newUri }
      }
    }
    return response;
  }
  return request;
}

Эта функция проверяет значение заголовка CloudFront-Viewer-Country, добавленного CloudFront. Если в нём содержится одна из поддерживаемых стран, а URL-адрес ещё не содержит префикс, указывающий на страну, то функция добавляет такой префикс в начало пути. В противном случае функция не изменяет запрос.

2. На вкладке Test, я выбираю тип триггера (Viewer Request), среду (пока что Development) и образец события для тестирования.

Скриншот консоли: вкладка Test

Я могу изменить запрос для тестирования в разделе Input: выбрать метод HTTP, а также путь URL и опционально задать IP-адрес клиента. Кроме того, я могу добавить заголовки, файлы cookie и строку запроса. В данном случае я оставляю все значения по умолчанию и добавляю заголовок CloudFront-Viewer-Country со значением FR (Франция). Кроме того, вместо использования визуального редактора я могу поменять запрос для тестирования, предоставив его в формате JSON.

Скриншот консоли: раздел Input на вкладке Test

Я нажимаю кнопку Test, после чего получаю результат в разделе Output. Как и ожидалось, запрос был перенаправлен (код ответа HTTP 302). В заголовках ответа (вкладка Response headers) я могу увидеть, что перенаправленный запрос начинается с пути /fr/, чтобы пользователям из Франции была предоставлена локализованная версия сайта. Если что-то в моих тестах пойдёт не так, я могу посмотреть в раздел Function Logs. Я также могу использовать в коде console.log(), чтобы вывести дополнительную отладочную информацию.

Скриншот консоли: раздел Output вкладки Test

В разделе Output прямо над кодом ответа HTTP я могу увидеть поле Compute utilization, которое содержит число от 0 до 100: оно показывает время выполнения функции в процентах от максимально разрешённого. В моём случае это значение составило 21: это означает, что функция завершилась за 21% от максимально разрешённого времени.

3. Я запускаю другие тесты для проверки разных наборов URL и заголовков, а потом перехожу на вкладку Publish, чтобы скопировать функцию из среды разработки в производственную среду. После этого она будет готова для настройки в существующей раздаче CloudFront.

Скриншот консоли: вкладка Publish

4. На вкладке Associate я выбираю раздачу (Distribution), триггер (Event type: Viewer Request или Viewer Response) а также настройки кэша (Cache behavior: в моём случае на раздаче созданы только настройки по умолчанию Default (*)). Затем я нажимаю Add association и подтверждаю добавление в открывшемся диалоговом окне.

Скриншот консоли: вкладка Associate

После этого я могу увидеть настроенную раздачу для функции в нижней части вкладки Associate.

Скриншот консоли: раздачи CloudFront на вкладке Associate

Чтобы проверить работу функции из двух разных географических местоположений я создал два инстанса Amazon Elastic Compute Cloud (EC2): один в регионе US East (N. Virginia), а другой в регионе Europe (Paris). Я подключаюсь к ним по SSH и использую cURL, чтобы получить необходимый объект из раздачи CloudFront. Перед этим я загрузил два файла в бакет S3, который используется в качестве источника раздачи: один для клиентов из Франции с префиксом fr/, другой для клиентов из неподдерживаемых стран с префиксом en/.

Я проверяю, что объекты действительно созданы, с помощью интерфейса командной строки AWS (AWS CLI):

$ aws s3 ls --recursive s3://BUCKET
2021-04-01 13:54:20         13 en/doc.txt
2021-04-01 13:54:20          8 fr/doc.txt

Затем на инстансе в регионе US East (N. Virginia) я выполняю следующую команду, чтобы скачать файл:

[us-east-1]$ curl -L https://d2wj2l15gt32vo.cloudfront.net/doc.txt 
Good morning

Такую же команду я выполняю в регионе Europe (Paris):

[eu-west-3]$ curl -L https://d2wj2l15gt32vo.cloudfront.net/doc.txt
Bonjour

Как и ожидалось, хоть я и использовал одинаковый URL, я получил разные результаты. Для того чтобы cURL следовал по полученному перенаправлению, я использовал ключ -L. В этом случае каждая команда выполняла два HTTP-запроса: первый получал перенаправление от функции CloudFront, а второй – следовал по этому перенаправлению. При этом, во втором случае функция не изменяла запрос, так как в нём уже содержался путь со страной в URL (/en/ или /fr/).

Чтобы увидеть возвращённое значение перенаправления, а также все заголовки ответа HTTP, я могу использовать cURL с ключом -i. Ниже приведены заголовки ответа для инстанса, запущенного в США. Функция выполнялась на периферийном местоположении в Вирджинии:

[us-east-1]$ curl -i https://d2wj2l15gt32vo.cloudfront.net/doc.txt 
HTTP/2 302 
server: CloudFront
date: Thu, 01 Apr 2021 14:39:31 GMT
content-length: 0
location: /en/doc.txt
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 cb0868a0a661911b98247aaff77bc898.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD50-C2
x-amz-cf-id: TuaLKKg3YvLKN85fzd2qfcx9jOlfMQrWazpOVmN7NgfmmcXc1wzjmA==

Ниже приведены заголовки ответа для инстанса EC2, расположенного во Франции. В этот раз функция выполнялась на периферийном местоположении рядом с Парижем:

[eu-west-3]$ curl -i https://d2wj2l15gt32vo.cloudfront.net/doc.txt
HTTP/2 302 
server: CloudFront
date: Thu, 01 Apr 2021 14:39:26 GMT
content-length: 0
location: /fr/doc.txt
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 6fa25eadb94abd73b5efc56a89b2d829.cloudfront.net (CloudFront)
x-amz-cf-pop: CDG53-C1
x-amz-cf-id: jzWcbccJiTDRj22TGwsn_

Доступность и стоимость
CloudFront Functions доступны уже сегодня: вы можете использовать их как с новыми, так и с уже существующими раздачами (distributions). Вы можете создавать функции в Консоли управления AWS, с помощью Интерфейса командной строки AWS (AWS CLI), Инструментов для разработки AWS (AWS SDK), а также AWS CloudFormation. В случае CloudFront Functions вы платите за количество вызовов. Вы можете начать их использование без дополнительных затрат с Уровнем бесплатного пользования AWS. Более подробную информацию вы можете узнать на странице цен на Amazon CloudFront.

Используйте CloudFront Functions уже сегодня, чтобы добавить в ваши приложения логику на периферии.