Альтернатива Cloudflare Workers: когда нужны контейнеры, а не изоляты

Cloudflare Workers исполняет изоляты V8 на границе сети; Inquir — полные контейнеры Node, Python и Go. Где начинается потолок изолятов, что меняют контейнеры и как выбрать.

Альтернатива Cloudflare Workers: когда нужны контейнеры, а не изоляты

Изоляты и контейнеры: две честные ставки на слово «serverless»

За словом «serverless» прячется развилка в архитектуре. Cloudflare Workers исполняет ваш код как изолят V8 — тот же самый лёгкий механизм песочницы, что разделяет вкладки браузера, — реплицированный по глобальной edge-сети Cloudflare. Inquir Compute исполняет ваш код как контейнер: полноценный процесс Node.js 22, Python 3.12 или Go 1.22 с настоящим рантаймом под ним. Оба тарифицируются по факту использования, оба прячут серверы, оба позволяют задеплоить функцию без выделения машины. Внутри они почти не имеют ничего общего.

Именно эта разница — весь сюжет данного сравнения. Если вы ищете альтернативу Cloudflare Workers (cloudflare workers alternative), вопрос почти никогда не звучит как «какой serverless лучше». Он звучит как «помещается ли моя нагрузка внутрь изолята — или ей нужен контейнер?». Ответьте на него правильно, и всё остальное — задержка, холодные старты, нативные модули, форма ценообразования — приложится.

Это честное сравнение, а не разоблачение. Workers действительно превосходен в том, для чего создавался, и для большого класса edge-задач это правильный выбор. Но у модели изолятов есть потолок, и когда вы в него упираетесь, это ощущается как стена, а не как пологий склон. Задача статьи — точно назвать этот потолок, показать, что меняет контейнерный serverless, и дать честный гид по выбору — включая случаи, когда стоит закрыть эту вкладку и остаться на Workers.

Что даёт модель изолятов V8 — и где её потолок

Начнём с того, почему Workers хорош, потому что сильные стороны и ограничения — это одно и то же проектное решение, рассмотренное с двух сторон.

Изолят V8 — это не контейнер и не виртуальная машина. Это песочница внутри общего процесса-рантайма. Тысячи изолятов делят один процесс, и Cloudflare поднимает нужный перед запросом за доли миллисекунды. Отсюда два следствия:

  • Холодного старта практически нет. Изоляту почти нечего загружать — нет ОС, нет образа контейнера, который надо стянуть, нет языкового рантайма, который надо инициализировать на каждый запрос. Старт близок к мгновенному.
  • Код работает везде. Поскольку изоляты дёшевы, Cloudflare запускает ваш код в сотнях локаций рядом с пользователями. Запрос обрабатывается в точке присутствия рядом с клиентом, а не в одном регионе-источнике.

Эти два свойства крайне трудно превзойти, и ни одна контейнерная платформа их не повторяет. Это честная хорошая новость про Workers.

Плата за такую модель — то, что вам разрешено делать внутри изолята. Чтобы изоляты оставались дёшевыми и безопасными для соседства тысячами, рантайм намеренно лишает вас большей части операционной системы:

  • Нет нативных модулей. Изолят не может загрузить нативный аддон — бинарник .node, разделяемую библиотеку, что-либо скомпилированное против libc. Чистый JavaScript, TypeScript и WebAssembly — можно; нативный код — нельзя.
  • Нет произвольных бинарников и подпроцессов. Нельзя вызвать ffmpeg, породить дочерний процесс или запустить CLI. Порождать процессы просто некуда.
  • Нет настоящей файловой системы. Нет надёжного локального диска, куда можно записать и потом прочитать.
  • Урезанная поверхность API. Вам доступны веб-стандартные API — fetch, Web Crypto, потоки — плюс опциональный слой совместимости с Node.js, покрывающий растущее подмножество встроенных модулей Node. Этого действительно стало заметно больше, чем раньше, но это подмножество, и оно не включает возможность исполнять нативный код.
  • Ограниченные CPU и память. Каждый запрос получает ограниченную долю процессорного времени и небольшой фиксированный бюджет памяти на изолят. Отлично для коротких резких обработчиков; беспощадно для чего-либо тяжёлого.

Ничто из этого не баг. Это ограничения изолятов V8 (v8 isolates limits) by design — те же ограничения, что делают платформу быстрой и глобально дешёвой. Проблемы начинаются только тогда, когда вашему коду нужно что-то из этого списка запретов.

Когда потолок реально мешает: нативные зависимости, тяжёлые библиотеки, бинарники, ML

Вот здесь команды и отправляются искать альтернативу Workers (workers alternative). Паттерн почти всегда один и тот же: зависимость, на которую ваш бизнес уже опирается, рассчитывает на настоящую операционную систему, а изолят отказывается её загружать.

Конкретные «нарушители», все — обычное дело в продакшене на Node и Python:

  • Обработка изображений и медиа. sharp оборачивает libvips; ffmpeg — бинарник, который вызывают как подпроцесс. Оба нативные. Ни один не поместится в изолят.
  • Хеширование паролей и криптография с нативными сборками. bcrypt, argon2 и ряд крипто-библиотек поставляют нативные аддоны ради скорости.
  • Headless-браузеры и рендеринг. Puppeteer или Playwright, управляющие Chromium, — это подпроцесс, дирижирующий большим нативным бинарником; до территории изолятов отсюда очень далеко.
  • Драйверы БД с нативными биндингами. Множество клиентов компилируются против C-библиотек.
  • ML-инференс. Запуск ONNX-модели, пайплайна scikit-learn или чего-либо через нативный рантайм — это работа для контейнера. У edge-платформ есть собственные управляемые продукты для инференса, но принести свой нативный ML-рантайм — не то, что делает изолят.
  • Большие деревья зависимостей и языковые рантаймы. Всё, что ожидает Python 3.12 или полноценный тулчейн Go, а не JavaScript-песочницу с прослойкой совместимости.

На границе сети есть обходные пути — перекомпилировать библиотеку в WebAssembly, заменить sharp на WASM-библиотеку для изображений, вызвать внешний сервис для нативной работы. Иногда это нормально, и справедливо сказать, что поддержка WASM в Workers реальна и полезна. Но WASM-порты отстают от нативных оригиналов, несут собственные издержки по размеру и производительности и существуют не для каждой библиотеки. Когда ваш обработчик по сути своей — про тяжёлую, нативную или бинарную работу, вы воюете с платформой. Это и есть сигнал вынести этот путь с изолятов.

Что меняет контейнерный serverless

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

В Inquir каждая функция — это настоящий контейнер с одним из трёх управляемых рантаймов: Node.js 22, Python 3.12 или Go 1.22. Поскольку это полноценный процесс ОС, список запретов из мира изолятов просто не действует:

  • Нативные модули загружаются как обычно. sharp, bcrypt, нативные драйверы БД — если оно ставится на обычном сервере, оно работает и здесь. Тяжёлые или разделяемые зависимости можно монтировать как слои (layers) — общие каталоги зависимостей для Node, Python и Go, — чтобы не пересобирать толстый образ на каждый деплой.
  • Полноценный рантайм и настоящее окружение. Файловая система (записываемый /tmp), стандартная библиотека, дружелюбные к подпроцессам паттерны — то, наличие чего предполагают ваши библиотеки.
  • Смешанные языки за одним шлюзом. Python-инструмент для ML, горячий путь на Go и обработчик вебхуков на Node могут жить в одном пространстве, каждый в своём контейнере, и все доступны через один API-шлюз с авторизацией на уровне маршрута.
  • Работа с источником и приватной сетью. Сетевой доступ по умолчанию выключен как политика безопасности и включается для каждой функции, так что контейнеру можно выдать доступ к приватным сервисам и базам — тот самый origin-IO, от которого edge-изолят намеренно держат далеко.

Смысл всей затеи — миниатюра через sharp, хеш bcrypt, ONNX-инференс (загрузить модель один раз на холодном старте, только CPU, через подключённый слой) — исполняется без борьбы.

Две проверки на честность, потому что контейнеры — не волшебство:

  • Холодный старт реален, просто спрятан. Контейнеру нужно стартовать — подготовить образ, поднять рантайм, — и это стоимость запуска контейнера, а не почти-ноль изолята. Inquir использует горячие/тёплые пулы (по умолчанию как минимум один и до восьми тёплых контейнеров на функцию, периодически перерабатываемых), чтобы держать готовый экземпляр перед трафиком. Это прячет холодные старты на тёплых путях; но не обнуляет их. Первый вызов — или первый после простоя — платит реальный старт. Кто обещает «нулевые холодные старты» на контейнерах, тот вам что-то продаёт.
  • Это не распределено по edge. Ваш контейнер работает в регионе, у источника, — а не реплицирован по сотням точек присутствия рядом с каждым пользователем. Если ваше определяющее требование — «обработать запрос в 20 миллисекундах от пользователя одновременно в Сиднее и Сан-Паулу», региональный контейнер здесь неправильный инструмент, а edge-изолят — правильный.

Есть и бюджет времени: одна функция или шаг выполняется 5 секунд по умолчанию и максимум 15 минут. Работа, которая длиннее одного запроса, выражается как пайплайн из сцепленных шагов, а не как одна безграничная функция, — с ретраями и backoff на каждом шаге и с dead-letter при исчерпании попыток. Гарантии exactly-once нет, строгого порядка нет — вы держите обработчики идемпотентными, той же дисциплины требует любая честная очередь.

До и после: миниатюра, которая не влезет в изолят

Самую частую стену изолята и показать проще всего. Эндпоинт для миниатюр на sharp — это несколько строк кода, которые изолят V8 выполнить не может, потому что за sharp стоит libvips — нативная библиотека.

На Workers проблемой оказывается сам импорт:

// Cloudflare Worker (V8 isolate)
import sharp from 'sharp'; // native addon — cannot load in an isolate

export default {
  async fetch(req) {
    const input = await req.arrayBuffer();
    // sharp needs libvips (a native binary); the isolate has no way to load it
    const thumb = await sharp(input).resize(320, 240).jpeg().toBuffer();
    return new Response(thumb, { headers: { 'content-type': 'image/jpeg' } });
  },
};

Ваши варианты на границе сети — найти WebAssembly-библиотеку для изображений с другим API или вызвать внешний сервис, чтобы сделать ресайз. Оба реальны; оба — обход того факта, что изолят не может выполнить библиотеку, которую вы уже используете.

В контейнере та же зависимость просто работает. Вот эквивалентная функция Inquir — шлюз вызывает её событием в стиле API Gateway (httpMethod, path, headers, queryStringParameters, body строкой):

// Inquir function (Node 22 container)
import sharp from 'sharp'; // native addon loads normally

export async function handler(event) {
  const input = Buffer.from(event.body ?? '', 'base64');

  const thumb = await sharp(input)
    .resize(320, 240, { fit: 'cover' })
    .jpeg({ quality: 82 })
    .toBuffer();

  return {
    statusCode: 200,
    body: JSON.stringify({ bytes: thumb.length, format: 'jpeg' }),
  };
}

Ни WASM-порта, ни внешнего сервиса изображений, ни переписывания логики обработки. Разница не в форме обработчика — оба это маленькие функции, принимающие запрос и возвращающие ответ. Разница в том, что один работает на полноценном рантайме, способном загрузить нативный код, а другой — нет.

Честный размен: задержка на edge и мгновенный холодный старт против полного рантайма и нативных модулей

Уберите маркетинг — и решение edge-функции против контейнеров (edge functions vs containers) сводится к одному размену, от которого не откупиться.

Workers даёт близость и мгновенный старт, а просит жить внутри песочницы изолята:

  • Код работает в нескольких миллисекундах от пользователя, в сотнях локаций.
  • Холодного старта практически нет.
  • Массивная дешёвая конкурентность для коротких обработчиков, оплата за запрос.
  • Взамен: только JavaScript / TypeScript / WASM, никаких нативных модулей, урезанные API, ограниченные CPU и память.

Контейнеры дают полный рантайм, а просят принять адрес у источника и реальную стоимость старта:

  • Любой нативный модуль, любая тяжёлая зависимость, три языка, настоящие подпроцессы и файловая система.
  • Доступ в приватные сети и к базам у источника.
  • Взамен: код работает в регионе, а не на границе; и холодный старт — это запуск контейнера, смягчённый тёплыми пулами, но никогда не нулевой.

Ни одна колонка не «строго лучше». Это ответы на разные вопросы. «Как исполнить тривиальную логику максимально близко к пользователю?» — указывает на изоляты. «Как запустить настоящий бэкенд с тяжёлыми зависимостями, не управляя серверами?» — указывает на serverless-контейнеры (serverless containers). Большая часть путаницы вокруг «альтернативы Workers» — это на самом деле нагрузка, которая тихо задавала второй вопрос, но исполнялась против первого инструмента.

Когда Cloudflare Workers всё ещё правильный выбор

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

Оставайтесь на Workers, когда:

  • Логика маленькая, а задержка на edge — это и есть суть. Редиректы, переписывание заголовков, гео-маршрутизация, A/B и маршрутизация по фиче-флагам, нормализация запросов, проверка подписанных URL. Крошечные обработчики, где выигрыш — в близости к пользователю.
  • Вы кешируете, экранируете (shielding) или делаете fan-out на edge. Стоять перед источником, отдавать попадания в кеш и схлопывать нагрузку до того, как она дойдёт до вас, — это ровно родная территория модели изолятов.
  • Ваш код уже isolate-native. Чистый JavaScript, TypeScript или WebAssembly, без нативных аддонов, без подпроцессов, без вызовов в приватную сеть у источника. Если оно помещается в песочницу — песочница быстра и дёшева.
  • Доминирует глобальная конкурентность коротких запросов. Оплата за запрос и сотни POP — действительно отличная посадка для рваного, высокообъёмного, низко-CPU трафика.

И, что важно, ответ часто — и то, и другое, а не «либо-либо». Workers на границе для кеширования, экранирования и лёгкой маршрутизации; контейнерная платформа у источника для тяжёлых по зависимостям API, обработчиков вебхуков, задач по расписанию и фоновой работы за ними. Два уровня, дополняющих друг друга: edge делает то, что может только edge, источник делает то, что требует настоящего рантайма. Потянуться за альтернативой Workers редко означает вырвать Workers с корнем; обычно это значит дать тяжёлым путям дом, в который они действительно помещаются.

Гид по выбору и вывод

Короткий полевой гид, без церемоний:

  • Нативный модуль, бинарник, подпроцесс или тяжёлая библиотека? Контейнер. Это самый однозначный сигнал: исполнять нативные модули на serverless (native modules serverless) — ровно то, чего изоляты не умеют, а контейнеры делают не задумываясь.
  • Python или Go — или смесь языков? Контейнер. Изоляты — это мир JavaScript / WASM.
  • IO в приватную сеть или к базе у источника? Контейнер.
  • Долгая или многошаговая работа за пределами одного запроса? Контейнер, выраженный как сцепленные шаги пайплайна в рамках бюджета 15 минут на шаг — а не одна безграничная функция.
  • Крошечная, критичная к задержке, глобальная, isolate-native логика? Workers. Оставайтесь.
  • Кеширование, экранирование, edge-маршрутизация? Workers. Оставайтесь — и поставьте тяжёлый уровень за ними.

Честная формулировка: Cloudflare Workers — превосходная платформа edge-функций, и именно модель изолятов делает её быстрой, глобальной и дешёвой для маленьких обработчиков. Та же модель — это жёсткий потолок для нативных модулей, тяжёлых библиотек, бинарников и настоящей бэкенд-работы. Контейнерный serverless вроде Inquir Compute меняет близость к edge и почти-нулевой холодный старт на полный рантайм Node.js 22 / Python 3.12 / Go 1.22, который загружает нативный код, дотягивается до приватных сервисов и крутит бэкенды на смеси языков, — с холодными стартами, спрятанными за тёплыми пулами, а не устранёнными, и с долгой работой, сцепленной в шаги пайплайна, а не обещанной как безграничная.

Выбирайте по нагрузке, а не по ярлыку. Когда вам нужны изоляты, Workers трудно превзойти. Когда вам нужны контейнеры — это ровно та стена, которую edge-изолят по замыслу не создан преодолевать, и самое время потянуться за контейнерной альтернативой.