ЯдроКодаподготовка к экзаменам
Научная библиотека

Загружаем научный разбор

Подготавливаем текст, источники и редакционные примечания без изменения разметки страницы.

Каталог статейМатериал и источники

Borg: почему свободные ресурсы ещё не означают место для задачи

Автор: · Обновлено

Историческая архитектура Borg, размещение по нескольким ресурсам и собственный перебор назначений: плотность, фрагментация и разделение реплик.

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

Размещение — это проверка совместимости задачи с конкретным местом, а не только сложение ресурсов кластера. К CPU и памяти добавляются архитектура, доступность данных, допустимое соседство и устойчивость к отказам. Исследование Borg помогает увидеть, как эти требования превращаются в отдельные решения управляющей системы. Ниже мы разберём историческую работу и проведём маленький собственный эксперимент с точным перебором.

Что описано в исследовании Borg

В статье Verma и соавторов, EuroSys 2015 представлен промышленный менеджер кластеров Google. Он принимает декларативные задания, размещает задачи, наблюдает их состояние и восстанавливает выполнение после отказов. В описанной архитектуре центральный управляющий компонент взаимодействует с агентами на машинах. Решение о размещении разделено на поиск допустимых узлов и оценку подходящих кандидатов.

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

Статья является историческим источником идей. Текущие правила Kubernetes нужно смотреть отдельно: сходство терминов не означает одинаковых объектов API, алгоритмов и поведения при отказах. Официальная документация kube-scheduler также различает фильтрацию и оценку узлов, но конкретная настройка относится уже к Kubernetes.

Три вопроса, которые нельзя смешивать

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

Пусть задача заявляет два ядра и четыре гигабайта памяти. На первом узле свободно четыре ядра и ноль памяти, на втором — ноль ядер и восемь гигабайт. Сумма ресурсов достаточна, но задача не помещается никуда. Это фрагментация по нескольким измерениям. Сообщение «в кластере свободно четыре ядра» не показывает её причину.

Ограничения могут быть и качественными. Узел подходит по памяти, но имеет другую архитектуру CPU; рядом уже находится реплика того же сервиса; нужный диск доступен в другом месте. При диагностике удобно хранить причины отклонения каждого кандидата. Тогда ожидание в очереди превращается из общего симптома в конкретное противоречие между требованиями и доступными местами.

Проверяем размещение полным перебором

Создадим три одинаковых узла по четыре условных ядра и восемь единиц памяти. Есть две реплики веб-сервиса и один пакетный работник. Первый вариант разрешает размещать реплики вместе, второй требует разных узлов. Все числа и правила принадлежат этому примеру; код не реализует планировщик Borg. На Python 3 достаточно стандартного itertools.

from itertools import product

capacity = (4, 8)
jobs = [(1, 2), (1, 2), (2, 4)]  # web-a, web-b, batch
node_count = 3

def fits(assignment, separate_replicas):
    if separate_replicas and assignment[0] == assignment[1]:
        return False
    for node in range(node_count):
        used = [sum(job[d] for job, host in zip(jobs, assignment)
                    if host == node) for d in range(2)]
        if any(used[d] > capacity[d] for d in range(2)):
            return False
    return True

def best(separate_replicas):
    placements = (assignment for assignment
                  in product(range(node_count), repeat=len(jobs))
                  if fits(assignment, separate_replicas))
    return min(placements, key=lambda a: (len(set(a)), a))

packed = best(False)
spread = best(True)
assert len(set(packed)) == 1
assert len(set(spread)) == 2
remaining = [(4, 0), (0, 8)]
request = (2, 4)
assert all(sum(r[d] for r in remaining) >= request[d] for d in range(2))
assert not any(all(r[d] >= request[d] for d in range(2)) for r in remaining)
print('packed:', packed, 'spread:', spread)

Получатся назначения (0, 0, 0) и (0, 1, 0). Оба соблюдают ресурсные ограничения. Первое использует одну машину, второе — две. Если узел 0 потерян, в первом варианте исчезнут обе веб-реплики; во втором одна останется. Мы заплатили дополнительной занятой машиной за выбранное свойство размещения. Это ещё не доказательство доступности сервиса: обе машины могут зависеть от одного сетевого коммутатора или диска.

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

Почему точный ответ на маленьком наборе не решает большую задачу

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

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

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

Заявленные ресурсы и фактическое потребление

Представим, что все пользователи нашего кластера запрашивают восемь единиц памяти «на всякий случай», а обычно потребляют две. Планировщик, верящий заявкам, будет резервировать значительный объём. Если вместо этого ориентироваться только на вчерашнее потребление, одновременный пик может превысить вместимость. Между этими крайностями необходима политика: какие ресурсы можно временно предоставить соседям и кого остановить при дефиците.

CPU и память при этом не симметричны. Работе часто можно дать меньше процессорного времени ценой задержки; нехватка памяти способна завершить процесс. Поэтому одно правило «разрешить превышение на 20%» для всех ресурсов скрывает разные последствия. В собственной оценке фиксируйте, чем оплачивается плотность: ожиданием, вытеснением, повторным запуском или потерей прогресса.

Добавим ещё один инженерный вопрос: как приложение переживает повторный запуск? Если пакетная работа сохраняет контрольные точки, вытеснение может быть приемлемым. Если она отправляет внешний платёж без защиты от повторения, запуск второй копии требует других гарантий. Планировщик способен возобновить процесс, но семантика внешнего действия остаётся задачей приложения.

Продолжение эксперимента

Добавьте четвёртую задачу с запросом (3, 6) и сравните решения. Затем ограничьте доступные узлы двумя. Программа должна явно сообщать об отсутствии решения вместо неясной ошибки min на пустом наборе. После этого назначьте узлам зоны отказа и потребуйте, чтобы веб-реплики находились в разных зонах. Объясните, почему разные имена машин сами по себе не выполняют это требование.

Чтобы связать размещение с повседневной разработкой, пройдите [трек Docker](/lessons/without-university/docker-containerization) и [урок Linux о процессах и службах](/lessons/without-university/linux-developer-foundations/linux-developer-11). Контейнер задаёт удобную единицу запуска, но планировщику всё ещё нужны честные требования к ресурсам, сигнал готовности и понятное поведение при остановке. Именно эти свойства превращают упаковку программы в управляемую нагрузку.

Источники

Формат и права

Формат
Авторский разбор

Атрибуция

Самостоятельный русскоязычный разбор ЯдроКода по указанным первичным источникам. Учебные модели, примеры и выводы редакции отделены от результатов исследований. Материал не является переводом или перепечаткой.

Код, данные и иллюстрации

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