Перейти к основному содержимому

Пейджинг и асинхронные вызовы с API Kubernetes

· 5 мин. чтения

1. Введение

В этом руководстве мы продолжаем изучать Kubernetes API для Java . На этот раз мы сосредоточимся на двух его функциях: пейджинге и асинхронных вызовах .

2. Пейджинг

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

  • limit : максимальное количество элементов, возвращаемых в одном вызове API.
  • continue : токен продолжения , который сообщает серверу начальную точку для возвращаемого набора результатов.

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

Теперь давайте посмотрим, как использовать эти параметры для получения списка всех доступных модулей в кластере, используя этот метод:

ApiClient client = Config.defaultClient();
CoreV1Api api = new CoreV1Api(client);
String continuationToken = null;
do {
V1PodList items = api.listPodForAllNamespaces(
null,
continuationToken,
null,
null,
2,
null,
null,
null,
10,
false);
continuationToken = items.getMetadata().getContinue();
items.getItems()
.stream()
.forEach((node) -> System.out.println(node.getMetadata()));
} while (continuationToken != null);

Здесь второй параметр вызова API listPodForAllNamespaces() содержит токен продолжения, а пятый — параметр limit . Хотя предел обычно представляет собой просто фиксированное значение, для продолжения требуется немного дополнительных усилий.

Для первого вызова мы отправляем нулевое значение, сигнализируя серверу, что это первый вызов последовательности выгружаемых запросов . После получения ответа мы получаем новое значение для следующего значения для продолжения использования из соответствующего поля метаданных списка.

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

2.1. Ошибки при нумерации страниц

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

  • В настоящее время API не поддерживает сортировку на стороне сервера. Учитывая текущее отсутствие поддержки сортировки на уровне хранилища, это вряд ли изменится в ближайшее время .
  • Все параметры вызова, кроме continue , должны быть одинаковыми между вызовами.
  • Значение continue должно рассматриваться как непрозрачный дескриптор. Мы никогда не должны делать никаких предположений о его ценности.
  • Итерация односторонняя . Мы не можем вернуться к набору результатов, используя ранее полученный токен продолжения . ``
  • Несмотря на то, что метаданные возвращаемого списка содержат поле restItemCount , его значение не является надежным и не поддерживается всеми реализациями.

2.2. Согласованность данных списка

Поскольку кластер Kubernetes является очень динамичной средой, существует вероятность того, что набор результатов, связанный с последовательностью вызовов с разбивкой на страницы, будет изменен во время чтения клиентом . Как в этом случае ведет себя API Kubernetes?

Как объясняется в документации Kubernetes , API списков поддерживают параметр resourceVersion , который вместе с resourceVersionMatch определяет, как конкретная версия выбирается для включения. Однако для страничного набора результатов поведение всегда одинаковое: «Продолжить токен, точно».

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

3. Асинхронные вызовы

До сих пор мы использовали Kubernetes API в синхронном режиме, который подходит для простых программ, но не очень эффективен с точки зрения использования ресурсов, поскольку он блокирует вызывающий поток, пока мы не получим ответ от кластера и не обработаем его. Такое поведение сильно повредит отзывчивости приложения, если, например, мы начнем делать эти вызовы в потоке графического интерфейса.

К счастью, библиотека поддерживает асинхронный режим, основанный на обратных вызовах, который немедленно возвращает управление вызывающей стороне .

Изучая класс CoreV1Api , мы заметим, что для каждого синхронного метода xxx() также существует вариант xxxAsync() . Например, асинхронным методом для listPodForAllNamespaces() является listPodForAllNamespacesAsync() . Аргументы те же, с добавлением дополнительного параметра для реализации обратного вызова.

3.1. Детали обратного вызова

Объект параметра обратного вызова должен реализовывать универсальный интерфейс ApiCallback<T>, который содержит всего четыре метода:

  • onSuccess: вызывается тогда и только тогда, когда вызов был успешным. Тип первого аргумента тот же, что и в синхронной версии.
  • onFailure: Called произошла ошибка при вызове сервера или ответ содержит код ошибки
  • onUploadProgress : вызывается во время загрузки. Мы можем использовать этот обратный вызов для предоставления обратной связи пользователю во время длительной операции.
  • onDownloadProgress : то же, что и onUploadProgress , но для загрузок

Асинхронные вызовы также не возвращают обычный результат. Вместо этого они возвращают экземпляр вызова OkHttp (базовый REST-клиент, используемый Kubernetes API) , который работает как дескриптор текущего вызова. Мы можем использовать этот объект для опроса состояния завершения или, если мы хотим, отменить его до завершения. ``

3.2. Пример асинхронного вызова

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

// Start async call
CompletableFuture<V1NodeList> p = AsyncHelper.doAsync(api,(capi,cb) ->
capi.listNodeAsync(null, null, null, null, null, null, null, null, 10, false, cb)
);
p.thenAcceptAsync((nodeList) -> {
nodeList.getItems()
.stream()
.forEach((node) -> System.out.println(node.getMetadata()));
});
// ... do something useful while we wait for results

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

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

  • Основной поток , запускающий асинхронный вызов ``
  • Потоки OkHttp, используемые для фактического HTTP-вызова
  • Поток завершения, где обрабатываются результаты

4. Вывод

В этой статье мы увидели, как использовать пейджинг и асинхронные вызовы с Java API Kubernetes.

Как обычно, полный исходный код примеров можно найти на GitHub .