Удалённые подключения: Как работать с продакшен-ресурсами прямо с локальной машины

Удаленные привязки — это привязки, которые подключаются к развернутому ресурсу в вашей учетной записи Cloudflare вместо локально симулированного ресурса — и недавно мы объявили, что удаленные привязки теперь общедоступны.

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

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

Разработка на платформе Workers

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

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

Эти и другие проблемы мотивировали значительные инвестиции в полностью локальную среду разработки для Workers, которая была выпущена в середине 2023 года и стала стандартным опытом для wrangler dev. С тех пор мы вложили огромную работу в опыт локальной разработки с Wrangler, плагином Cloudflare Vite (наряду с @cloudflare/vitest-pool-workers) & Miniflare.

Тем не менее, исходный удаленный режим оставался доступным через флаг: wrangler dev --remote. При использовании удаленного режима все преимущества DX полностью локального опыта и улучшения, которые мы сделали за последние годы, обходятся. Так почему же люди все еще его используют? Он включает одну ключевую уникальную функцию: привязку к удаленным ресурсам во время локальной разработки. Когда вы используете локальный режим для разработки Worker локально, все ваши привязки симулируются локально с использованием локальных (изначально пустых) данных. Это фантастически для итерации над логикой вашего приложения с тестовыми данными — но иногда этого недостаточно, будь то желание делиться ресурсами в вашей команде, воспроизводить ошибки, связанные с реальными данными, или просто быть уверенным, что ваше приложение будет работать в продакшене с реальными ресурсами.

Учитывая это, мы увидели возможность: если бы мы могли перенести лучшие части удаленного режима (т.е. доступ к удаленным ресурсам) в wrangler dev, появился бы единый поток для разработки Workers, который бы включал множество случаев использования, при этом не лишая людей достижений, которые мы сделали в локальной разработке. И мы это сделали!

Начиная с Wrangler v4.37.0 вы можете выбирать для каждой привязки отдельно, должна ли привязка использовать удаленные или локальные ресурсы, просто указав опцию remote. Важно подчеркнуть это снова — вам нужно только добавить remote: true! Никакого сложного управления API-ключами и учетными данными не требуется, все просто работает с использованием существующего Oauth-подключения Wrangler к API Cloudflare.

{
  "name": "my-worker",
  "compatibility_date": "2025-01-01",
  "kv_namespaces": [{
    "binding": "KV",
    "id": "my-kv-id",
  },{
    "binding": "KV_2",
    "id": "other-kv-id",
    "remote": true
  }],
  "r2_buckets": [{
    "bucket_name": "my-r2-name",
    "binding": "R2"
  }]
}

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

Когда мы поняли, что разным продуктам внутри Workers нужно что-то похожее на удаленные привязки (например, Images и Hyperdrive), у нас получилось лоскутное одеяло из разных решений. Теперь мы объединились под единым решением удаленных привязок, которое работает для всех типов привязок.

Как мы это построили

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

const value = await env.KV.get("some-key")

Приведенный выше фрагмент кода показывает доступ к значению "some-key" в пространстве имен KV env.KV, которое недоступно локально и должно быть получено по сети.

Итак, если это было нашим требованием, как бы мы к этому пришли? Например, как бы мы перешли от вызова пользователем env.KV.put(“key”, “value”) в их Worker к фактическому сохранению этого в удаленном хранилище KV? Очевидным решением было, возможно, использовать Cloudflare API. Мы могли бы просто заменить весь env локально на заглушки, которые делают API-вызовы, преобразуя env.KV.put() в PUT http:///accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key_name}.

Это сработало бы отлично для KV, R2, D1 и других привязок со зрелыми HTTP API, но это было бы довольно сложным решением для реализации и поддержки. Нам пришлось бы воспроизводить всю поверхность API привязок и преобразовывать каждую возможную операцию над привязкой в эквивалентный API-вызов. Кроме того, некоторые операции привязок не имеют эквивалентного API-вызова и не могли бы быть поддержаны с помощью этой стратегии.

Вместо этого мы поняли, что у нас уже есть готовый API, ожидающий нас — тот, который мы используем в продакшене!

Как привязки работают под капотом в продакшене

Большинство привязок на платформе Workers сводятся по сути к сервисной привязке. Сервисная привязка — это связь между двумя Worker, которая позволяет им общаться по HTTP или JSRPC (мы вернемся к JSRPC позже).

Например, привязка KV реализована как сервисная привязка между вашим созданным Worker и платформенным Worker, общающимся по HTTP. JS API для привязки KV реализован в рантайме Workers и преобразует вызовы типа env.KV.get() в HTTP-вызовы к Worker, который реализует сервис KV.

Connecting to production: the architecture of remote bindings

Диаграмма, показывающая упрощенную модель работы привязки KV в продакшене

Вы можете заметить, что здесь есть естественная асинхронная сетевая граница — между рантаймом, преобразующим вызов env.KV.get(), и Worker, который реализует сервис KV. Мы поняли, что можем использовать эту естественную сетевую границу для реализации удаленных привязок. Вместо того чтобы продакшен-рантайм преобразовывал env.KV.get() в HTTP-вызов, мы могли бы заставить локальный рантайм (workerd) преобразовать env.KV.get() в HTTP-вызов и затем отправить его напрямую в сервис KV, минуя продакшен-рантайм. И так мы и поступили!

Connecting to production: the architecture of remote bindings

Диаграмма, показывающая локально запущенный Worker с одной привязкой KV, с одним удаленным прокси-клиентом, который общается с удаленным прокси-сервером, который в свою очередь общается с удаленным KV

Приведенная выше диаграмма показывает локальный Worker, запущенный с удаленной привязкой KV. Вместо обработки локальной симуляцией KV, она теперь обрабатывается удаленным прокси-клиентом. Этот Worker затем общается с удаленным прокси-сервером, подключенным к реальному удаленному ресурсу KV, в конечном счете позволяя локальному Worker бесшовно общаться с удаленными данными KV.

Каждая привязка может независимо либо обрабатываться удаленным прокси-клиентом (все подключены к одному и тому же удаленному прокси-серверу), либо локальной симуляцией, позволяя очень динамичные рабочие процессы, где некоторые привязки симулируются локально, а другие подключаются к реальному удаленному ресурсу, как проиллюстрировано в примере ниже:

Connecting to production: the architecture of remote bindings

Приведённая выше схема и конфигурация показывают Воркер (запущенный на вашем компьютере), связанный с 3 различными ресурсами — двумя локальными (KV & R2) и одним удалённым (KV_2)

Как JSRPC вписывается в картину

Вышеупомянутый раздел касается привязок, поддерживаемых HTTP-соединениями (такими как KV и R2), но современные привязки используют JSRPC. Это означает, что нам нужен был способ для локально запущенного workerd общаться по JSRPC с экземпляром продакшен-рантайма.

По счастливой случайности параллельно велась работа над проектом, делающим это возможным, как подробно описано в блоге Cap'n Web. Мы интегрировали это, заставив соединение между локальным экземпляром workerd и удалённым экземпляром рантайма общаться через веб-сокеты с использованием Cap'n Web, что позволило работать привязкам на основе JSRPC. Это включает новые привязки, такие как Images, а также привязки служб JSRPC к вашим собственным Воркерам.

Удалённые привязки с Vite, Vitest и экосистемой JavaScript

Мы не хотели ограничивать эту захватывающую новую функцию только wrangler dev. Мы хотели поддерживать её в наших пакетах Cloudflare Vite Plugin и vitest-pool-workers, а также позволить любым другим потенциальным инструментам и случаям использования из экосистемы JavaScript также извлечь из этого выгоду.

Чтобы достичь этого, пакет wrangler теперь экспортирует утилиты, такие как startRemoteProxySession, которые позволяют инструментам, не использующим wrangler dev, также поддерживать удалённые привязки. Более подробную информацию вы можете найти в официальной документации по удалённым привязкам.

Как это опробовать?