Циклы: питание непрерывных запросов с наблюдаемостью FaaS

Мы все знакомы с этим небольшим фрагментом кода, который добавляет разумную ценность вашему бизнес-подразделению. Он может материализоваться как сценарий, программа, строка кода… и он будет создавать отчет, новые метрики, KPI или создавать новые составные данные. Этот код предназначен для периодического запуска, чтобы удовлетворить требования к актуальной информации.

В команде Observability мы встречаем эти фрагменты как запросы в базе данных временных рядов (TSDB), чтобы выразить непрерывные запросы, которые отвечают за автоматизацию различных вариантов использования, таких как: удаление, свертка или любая бизнес-логика, которая должна манипулировать данными временных рядов.

Мы уже представляли TSL в предыдущем сообщении в блоге, в котором демонстрировалось, как наши клиенты используют доступные протоколы OVH Metrics, такие как Graphite, OpenTSDB, PromQL и WarpScript ™, но когда дело доходит до манипулирования или даже создания новых данных, у вас нет много вариантов, хотя вы можете использовать WarpScript ™ или TSL в качестве языка сценариев вместо запроса.

В большинстве случаев эта бизнес-логика требует создания приложения, которое требует больше времени, чем выражение логики в виде запроса, ориентированного на TSDB. Создание базового кода приложения — это первый шаг, за которым следует CI / CD (или любой процесс доставки) и настройка его мониторинга. Однако управление сотнями небольших приложений, подобных этим, увеличит органические затраты из-за необходимости поддерживать их вместе с базовой инфраструктурой.

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

Мы хотели найти решение, которое сфокусировалось бы на бизнес-логике, без необходимости запуска всего приложения. Таким образом, кому-то, желающему сгенерировать файл JSON с ежедневным отчетом о данных (например), нужно будет только выразить соответствующий запрос.



Вы не должны FaaS!
Планирование работы — старая, привычная рутина. Будь то bash cron, runner или специализированные планировщики, когда дело доходит до упаковки фрагмента кода и его периодической работы, существует имя для него: FaaS.

FaaS родился с простой целью: сократить время разработки. Мы могли бы найти реализацию с открытым исходным кодом для оценки (например, OpenFaas), но большинство из них полагались на управляемый стек контейнеров. Наличие одного контейнера на запрос было бы очень дорогостоящим, плюс разогрев контейнера для выполнения функции и последующее его замораживание было бы очень контрпродуктивным.

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

def Loops
Именно тогда мы решили создать «Loops»: платформу приложений, в которой вы можете запустить код, который хотите запустить. Это все. Цель состоит в том, чтобы выдвинуть функцию (буквально!), А не модуль, как это делают все текущие решения FaaS:
function dailyReport (event) {
    return Promise.resolve («Сегодня все хорошо!»)
}

Затем вы можете выполнить его вручную с помощью HTTP-вызова или Cron-подобного планировщика.
Оба эти аспекта необходимы, поскольку вы можете (например) иметь ежемесячный отчет, но для одного дня потребуется дополнительный, через 15 дней после последнего отчета. Циклы упростят создание вашего нового отчета вручную, в дополнение к ежемесячному.

Были некоторые необходимые ограничения, когда мы начали строить Loops:
  • Эта платформа должна легко масштабироваться для поддержки производственной нагрузки OVH
  • Это должно быть очень доступно
  • Он должен быть независимым от языка, потому что некоторые из нас предпочитают Python, а другие JavaScript
  • Это должно быть надежно
  • Часть планирования не должна коррелировать с частью исполнения (культура µService)
  • Он должен быть безопасным и изолированным, чтобы любой мог вставить неизвестный код на платформу.

Реализация циклов
Мы решили построить нашу первую версию на V8. Мы выбрали JavaScript в качестве первого языка, потому что он прост в изучении, а асинхронные потоки данных легко управляются с помощью Promises. Кроме того, он очень хорошо подходит для FaaS, так как функции Javascript очень выразительны. Мы создали его вокруг нового модуля VM NodeJS, который позволяет выполнять код в выделенном контексте V8.

Контекст V8 подобен объекту (JSON), изолированному от вашего исполнения. В контексте вы можете найти собственные функции и объекты. Однако, если вы создадите новый контекст V8, вы увидите, что некоторые переменные или функции не доступны изначально (например, setTimeout (), setInterval () или Buffer). Если вы хотите использовать их, вам придется добавить их в ваш новый контекст. Последнее, что нужно помнить, это то, что когда у вас есть новый контекст, вы можете легко выполнить сценарий JavaScript в строковой форме.

Контексты выполняют самую важную часть нашего первоначального списка требований: изоляция. Каждый контекст V8 изолирован, поэтому он не может общаться с другим контекстом. Это означает, что глобальная переменная, определенная в одном контексте, недоступна в другом. Вам нужно будет построить мост между ними, если вы хотите, чтобы это было так.

Мы не хотели выполнять сценарии с помощью eval (), поскольку вызов этой функции позволяет вам выполнять код JS в основном общем контексте с кодом, вызывающим его. После этого вы можете получить доступ к тем же объектам, константам, переменным и т. Д. Эта проблема безопасности стала нарушением условий для новой платформы.

Теперь мы знаем, как выполнять наши скрипты, давайте реализуем некоторое управление для них. Чтобы быть без сохранения состояния, каждый рабочий экземпляр Loops (то есть механизм JavaScript, способный выполнять код в контексте виртуальной машины) должен иметь последнюю версию каждого цикла (цикл — это скрипт для выполнения). Это означает, что когда пользователь запускает новый цикл, мы должны синхронизировать его с каждым рабочим цикла. Эта модель хорошо согласуется с парадигмой pub / sub, и, поскольку мы уже используем Kafka в качестве инфраструктуры pub / sub, нужно было просто создать отдельную тему и использовать ее у рабочих. В этом случае публикация включает API, где пользователь отправляет свои циклы, которые генерируют событие Kafka, содержащее тело функции. Поскольку у каждого работника есть своя собственная группа потребителей Kafka, они все получают одинаковые сообщения.

Рабочие подписываются на обновления Loops как потребители Kafka и поддерживают хранилище Loop, которое является встроенным ключом (хэш Loop) / Value (текущая версия функции). В части API хеши циклов используются в качестве параметров URL для определения того, какой цикл следует выполнить. После вызова цикл извлекается из карты, затем вводится в контексте V8, выполняется и удаляется. Этот механизм перезагрузки горячего кода гарантирует, что каждый цикл может выполняться на каждом работнике. Мы также можем использовать возможности наших балансировщиков нагрузки для распределения нагрузки на работников. Эта простая модель распределения позволяет избежать сложного планирования и облегчает обслуживание всей инфраструктуры.

Чтобы обеспечить защиту от перезагрузки, мы используем очень удобную функцию сжатия журналов Kafka. Сжатие журнала позволяет Kafka сохранять последнюю версию каждого ключевого сообщения. Когда пользователь создает новый цикл, ему дается уникальный идентификатор, который используется в качестве ключа сообщения Kafka. Когда пользователь обновляет цикл, это новое сообщение будет переслано всем потребителям, но, поскольку ключ уже существует, Kafka сохранит только последнюю ревизию. Когда работник перезапускается, он использует все сообщения для восстановления своего внутреннего KV, поэтому предыдущее состояние будет восстановлено. Кафка используется здесь как постоянный магазин.



Циклы выполнения
Даже если базовый движок способен запускать собственный Javascript, как указано выше, мы хотели, чтобы он выполнял более идиоматические запросы временных рядов, такие как TSL или WarpScript. Чтобы добиться этого, мы создали абстракцию Loops Runtime, которая оборачивает не только Javascript, но также запросы TSL и WarpScript в код Javascript. Пользователи должны объявлять цикл с его временем выполнения, после чего это просто вопрос работы упаковщиков. Например, выполнение цикла WarpScript подразумевает использование простого WarpScript ™ и его отправку через HTTP-вызов по запросу узла.


Петли обратной связи
Безопасное выполнение кода — это начало, но когда дело доходит до выполнения произвольного кода, также полезно получить некоторую обратную связь о состоянии выполнения. Это было успешно или нет? Есть ли ошибка в функции? Если цикл находится в состоянии сбоя, пользователь должен быть немедленно уведомлен.

Это приводит нас к одному особому условию: пользовательские скрипты должны быть в состоянии сказать, все ли в порядке или нет. Есть два способа сделать это в базовом движке JavaScript: обратные вызовы и обещания.
Мы решили использовать Promises, который предлагает лучшее асинхронное управление. Каждый цикл возвращает обещание в конце скрипта. Отклоненное обещание вызовет состояние ошибки HTTP 500, а разрешенное — HTTP 200.

Планирование циклов
При публикации Loops вы можете объявить несколько триггеров, аналогично Cron. Каждый триггер будет выполнять HTTP-вызов вашего цикла с необязательными параметрами.

Исходя из этой семантики, для генерации нескольких отчетов мы можем зарегистрировать одну функцию, которая будет планироваться в разных контекстах, определенных различными параметрами (регион, скорость). Смотрите пример ниже:
functions:
  warp_apps_by_cells:
    handler: apps-by-cells.mc2
    runtime: ws
    timeout: 30
    environment:
    events:
      - agra:
          rate: R/2018-01-01T00:00:00Z/PT5M/ET1M
          params:
            cell: ovh-a-gra
      - abhs:
          rate: R/2018-01-01T00:00:00Z/PT5M/ET1M
          params:
            cell: ovh-a-bhs


Планирование основано на Metronome, который является планировщиком событий с открытым исходным кодом, с особым акцентом на планирование, а не на выполнение. Он идеально подходит для Loops, поскольку Loops управляет выполнением, полагаясь на Metronome для управления вызовами выполнения.

Петли трубопроводов
Проект Loops может иметь несколько циклов. Одним из распространенных случаев использования наших клиентов было использование Loops в качестве платформы данных в режиме потока данных. Поток данных — это способ описать последовательность этапов выполнения. В контексте Loops существует глобальный объект `Loop`, который позволяет сценарию выполнить другой цикл с этим именем. Затем вы можете связать выполнение циклов, которые будут действовать как пошаговые функции.

Болевые точки: масштабирование приложения NodeJS
Рабочие циклов являются приложениями NodeJS. Большинство разработчиков NodeJS знают, что NodeJS использует однопоточный цикл обработки событий. Если вы не позаботитесь о поточной модели вашего приложения nodeJS, вы, скорее всего, пострадаете из-за нехватки производительности, поскольку будет использоваться только один хост-поток.

NodeJS также имеет модуль кластера, который позволяет приложению использовать несколько потоков. Вот почему в Loops-работнике мы начинаем с потока N-1 для обработки вызовов API, где N — общее количество доступных потоков, и один из них остается выделенным для основного потока.

Главный поток отвечает за использование тем Kafka и поддержку хранилища Loops, в то время как рабочий поток запускает сервер API. Для каждого запрошенного выполнения цикла он запрашивает у мастера содержимое скрипта и выполняет его в выделенном потоке.

При такой настройке на каждом сервере запускается одно приложение NodeJS с одним потребителем Kafka, что упрощает масштабирование инфраструктуры, просто добавляя дополнительные серверы или облачные экземпляры.

Заключение
В этой статье мы предварительно ознакомились с Loops, масштабируемым, ориентированным на метрики FaaS с собственной поддержкой JavaScript и расширенной поддержкой WarpScript и TSL.

У нас еще есть несколько вещей, которые нужно улучшить, например, импорт зависимостей в стиле ES5 и предварительный просмотр метрик для проектов Loops наших клиентов. Мы также планируем добавить дополнительные среды выполнения, особенно WASM, которые позволят многим другим языкам, на которые он может ориентироваться, таким как Go, Rust или Python, удовлетворять большинству предпочтений разработчика.

Платформа Loops была частью требования для создания высокоуровневых функций вокруг продуктов OVH Observability. Это первый шаг к предложению более автоматизированных услуг, таких как сведение показателей, конвейеры агрегации или средства извлечения логов в метрики.

Этот инструмент был создан из набора продуктов Observability с учетом более высокого уровня абстракции, но вам также может потребоваться прямой доступ к API, чтобы реализовать собственную автоматизированную логику для ваших метрик. Вы были бы заинтересованы в такой функции? Посетите наш канал Gitter, чтобы обсудить это с нами!

www.ovh.com/fr/blog/v8-hot-code-injection-for-continuous-queries/