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

Что такое нода в кластере

  • автор:

Что такое Pods, Nodes, Containers и Clusters в Kubernetes

Что такое Pods, Nodes, Containers и Clusters в Kubernetes

Kubernetes (k8s) очень стремительно становится новым стандартом для деплоймента и менеджмента вашего кода в клауде. Вместе с тем, сколько фич предоставляет k8s, для новичка наступает высокий порог входа в новую технологии.

Документация по k8s достаточно обширна и довольно сложно пройти ее всю. Именно по этому эта статья служит неким обобщением для того, чтобы разобрать основные модули kubernetes.

Hardware

Nodes

node k8s

Node — это самая маленькая единица ‘computing hardware в k8s. Это представление одной машины в вашем кластере. В большинстве производственных систем нодой, корее всего, будет либо физическая машина в датацентре, либо виртуальная машина, размещенная на облачном провайдере, таком как Google Cloud Platform, Azure, AWS. Однако, вы можете сделать ноду практически из чего угодно (например Rasbery PI).

Если обсуждать про машину как «ноду», можем разбавить это слоем абстракции, мы можем представлять ее как некий набор CPU, RAM ресурсов которые можно использовать. Таким образом любая такая машина может заменить любую другую машину как k8s кластер.

Cluster

cluster

Хотя работа с отдельными нодами может быть полезной, это не путь kubernetes. В общем, вы должны думать о кластере в целом, а не беспокоиться о состоянии отдельных нодов.

В Kubernetes ноды объединяют свои ресурсы для формирования более мощной машины. Когда вы развертываете программы в кластере, он балансирует нагрузку по индивидуальным нодам для вас. Если какие-либо nodes добавляются или удаляются, кластер будет перемещаться по мере необходимости. Для программы или девелопера не должно быть важно, на каких машинах выполняется код в k8s. Можно сравнить такую систему с улием.

Persistent Volumes

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

Persistent Volumes

Для постоянного хранения данных Kubernetes использует Persistent Volumes. Хотя ресурсы ЦП и ОЗУ всех нодов эффективно объединяются и управляются кластером, постоянного хранение файлов — нет. Вместо этого локальные или облачные диски могут быть подключены к кластеру как постоянный том (Persistent Volumes). Это можно рассматривать как подключение внешнего жесткого диска к кластеру. Persistent Volumes предоставляют файловую систему, которая может быть подключена к кластеру без привязки к какому-либо конкретному ноду.

Software

Контейнеры

containers

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

Контейнеризация позволяет вам создавать self-contained environments. Любая программа и все ее зависимости могут быть объединены в один файл и затем опубликованы в Интернете. Любой может загрузить контейнер и развернуть его в своей инфраструктуре с минимальными настройками. Создание контейнера может быть сделано и скриптом, позволяя строить CI/CD пайплайны.

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

Pods

pods

В отличие от других систем, которые вы, возможно, использовали в прошлом, Kubernetes не запускает контейнеры напрямую; вместо этого он упаковывает один или несколько контейнеров в структуру более высокого уровня, называемую pod. Любые контейнеры в одном pod’e будут использовать одни и те же ресурсы и локальную сеть. Контейнеры могут легко связываться с другими контейнерами в том же pod’e, как если бы они находились на одной машине, сохраняя степень изоляции от других pod’ов.

Pod’ы используются как единица репликации в Kubernetes. Если ваше приложение становится слишком популярным, и один экземпляр модуля не может нести нагрузку, Kubernetes можно настроить для развертывания новых реплик вашего модуля в кластере по мере необходимости. Даже если не под большой нагрузкой, в продакшинев любое время можно запустить несколько копий модуля в любое время, чтобы обеспечить балансировку нагрузки и устойчивость к сбоям.

Pod’ы могут содержать несколько контейнеров, но вы должны ограничивать их количество, когда это возможно. Поскольку контейнеры масштабируются как единое целое, все контейнеры в паке должны масштабироваться вместе, независимо от их индивидуальных потребностей. Это приводит к потраченным впустую ресурсам и дорогому счету. Чтобы решить эту проблему, Pod’ы должны оставаться меньше на сколько это возможно, обычно вмещая только основной процесс и его тесно связанные вспомогательные контейнеры (эти вспомогательные контейнеры обычно называют Side-cars).

Deployments

deployemnt

Хотя в Kubernetes pod являются базовой единицей вычислений, они, как правило, не запускаются напрямую в кластере. Вместо этого pod обычно менеджится еще одним уровнем абстракции — deployment.

Основная цель юзать подход с deployment состоит в том, чтобы настроить, сколько реплик pod’а должно работать одновременно. Когда развертывание добавляется в кластер, оно автоматически деплоит требуемое количество pod’ов и отслеживает их. Если pod умирает, deployment автоматически пересоздает его.

Используя deployment, вам не нужно иметь дело с подами вручную. Вы можете просто объявить желаемое состояние системы, и оно будет управляться автоматически.

Ingress

ingress

Используя описанные выше концепции, вы можете создать кластер нодов и запустить деплоймент подов в кластере. Однако есть еще одна проблема, которую необходимо решить: разрешить внешний трафик вашему приложению. По умолчанию Kubernetes обеспечивает изоляцию между модулями и внешним миром. Если вы хотите общаться с сервисом, работающим в pod, вам нужно открыть канал для связи. Это называется Ingress.

Есть несколько способов добавить ingress в ваш кластер. Наиболее распространенными способами являются добавление либо ingress controller, либо LoadBalancer. Описание различий и что лучше выбрать выходит за рамки этой статьи, но вы должны держать в голове что вам нужно разобратся с доступом к сервису, если вы хотите работать с k8s.

База знаний

Для обеспечения отказоустойчивости кластеров облачных баз данных проекта CLO применяется технология «мастер-слейв», обеспечивающая бесперебойное функционирование кластера.

Принцип работы технологии мастер-слейв

У кластера есть несколько нод (узлов), содержащих одинаковые базы данных:

  • Мастер-нода, на которую производится запись и осуществляется чтение информации
  • Слейв-ноды, представляющие собой автоматически созданные копии (реплики) мастер-ноды

Примечание. В настоящий момент кластер баз данных состоит из одной мастер-ноды и одной слейв-ноды. Пользователь не может изменить количество нод.

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

Примечание. При некоторых условиях (большое количество транзакций, высокая нагрузка на сеть) возможно отставание слейв-ноды от мастер-ноды. Эта ситуация почти никогда не сказывается на работе кластера. Но при восстановлении кластера часть самых последних транзакций на мастер-ноде может быть утрачена.

Работоспособность нужного числа нод кластера автоматически поддерживается сервисом. Если по каким-либо причинам мастер-нода становится недоступной, кластер автоматически выполнит следующие действия:

  • назначит слейв-ноду новой мастер-нодой
  • переключит IP-адреса кластера на новую мастер-ноду
  • создаст новую слейв-ноду

Если недоступной окажется слейв-нода, то сервис автоматически создаст новую слейв-ноду и выполнит на неё репликацию мастер-ноды.

Автоматическое создание новой слейв-ноды приводит к изменению её внутреннего IP-адреса. Чтобы избежать таких проблем, мы рекомендуем всегда обращаться к кластеру через IP-адрес мастер-ноды.

Внимание! Во время восстановления целостности кластера он становится частично недоступен для чтения и записи. При этом некоторые транзакции могут потеряться.

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

Примечание. Для повышения надёжности хранения данных ноды одного кластера располагаются на разных физических серверах.

Технология репликации нод

Репликация нод осуществляется автоматически, с использованием штатного механизма репликации конкретной СУБД:

  • Для MySQL — полусинхронно, с последовательным внесением изменений данных мастер-ноды на слейв-ноду. Консистентность данных на уровне кластера не гарантируется. Репликация поддерживает как InnoDB, так и MyISAM
  • Для PostgreSQL — синхронно, с подтверждением внесения изменений на всех нодах кластера

Примечание. Пользователь может отменить синхронизацию для каждой конкретной транзакции PostgreSQL, установив для неё synchronous_commit: off.

Репликация нод кластеров MySQL

Для репликации кластеров на базе MySQL используется Row-based технология, опирающаяся на идентификатор глобальной транзакции (GTID ). С помощью этого механизма каждая транзакция на кластере получает свой идентификатор, по которому можно идентифицировать и восстановить передаваемые данные. Транзакции на мастер-ноде с помощью GTID идентифицируются и передаются на слейв-ноду. Эта технология позволяет сохранить согласованность данных между нодами.

Репликация кластеров PostgreSQL

Для кластеров на базе СУБД PostgreSQL используется стандартный механизм потоковой репликации. Все изменения БД PostgreSQL сначала помещаются в специальный журнал изменений WAL (write-ahead log), откуда изменения уже передаются в базу данных. Такой подход обеспечивает целостность данных и отсутствие конфликтов между изменениями таблиц. В журнал WAL последовательно записываются только сделанные изменения. Когда все транзакции из журнала считаются успешно применёнными, журнал удаляется. При репликации слейв-нода получает копию журнала с мастер-ноды.

ELK — Что такое master node и cluster

В elastcsearch каждый экземпляр/сервер с установленным elascsearch называется нодой (node).

Объединение нескольких нод в одну группу, с одинаковым именем называется кластером (cluster). Для создания кластера используется атрибут cluster.name , в котором задаётся имя кластера. Когда ноды присоединяются к кластеру или отключаются от него, кластер автоматически реорганизуется для равномерного распределения данных по доступным узлам.

Минимальное количество нод в кластере равняется 3.

В elastcsearch присутствуют 2 основных механизма коммуникации на сетевом уровне:

  1. HTTP, который предоставляет REST API Elasticsearch
  2. Transport используется для связи между узлами в кластере

По умолчанию присваивается значение localhost , с помощью атрибута http.host . Порт по умолчанию является первым доступным между 9200-9299 и настраивается с помощью http.port (можно просто указать один).

Каждый запрос между узлами в кластере происходит именно по **transport **. Например, когда вы, используя get обращаясь к данным, которые лежат на другой ноде будет использоваться **transport ** для получения данных основной нодой.

Транспорт по умолчанию привязывается к localhost и настраивается с помощью transport.host . Порт по умолчанию является первым доступным между 9300-9399 и настраивается с помощью transport.tcp.port .

Специальные значения для network.host

Если вы привязываете и transport ** и **http к одному ip адресу можно воспользоваться одним атрибутом network.host .

Следующие специальные значения могут использоваться для network.host , например если вы не хотите жёстко привязываться к ip адресу .

Значение Описание
_local_ Любые адреса обратной связи в системе (например, 127.0.0.1)
_site_ Любой локальный адрес сайта в системе (например, 192.168.1.1)
_global_ Любые глобальные адреса в системе (статика)
_[networkInterface]_ Адрес сетевого интерфейса (например, eth0)

Модуль обнаружения (Discovery Module) отвечает за обнаружение нод в кластере. Процесс модуля обнаружения помогает нодам находить друг друга. Сами узлы он получает из переменной discovery.seed_hosts .

Когда вы прописали значение для discovery.seed_hosts модуль берёт оттуда каждый хост и проверяет его на доступность, посылая ping запросы . Происходит это на каждой ноде в кластере. Если нода не найдёт ни одной ноды с одинаковым именем кластера ( cluster.name ) она создаст свой кластер.

discovery.seed_hosts: ["host1", "host2", "host3"] 
discovery.seed_hosts: file.txt 
sudo cat file.txt host1 host2 host3 

Чтобы узнать, как себя чувствует кластер можно проверить состояние кластера (cluster state). С помощью API состояния кластера вы можете получить доступ к метаданным, представляющим состояние всего кластера. А именно набор узлов в кластере, индексы, сопоставления, настройки, выделение сегментов и т. Д.

Вы можете использовать конечную точку _cluster для просмотра состояния кластера:

GET _cluster/state 

Для укороченного вывода можно использовать:

GET _cluster/health 
 "cluster_name" : "elk-cluster", "status" : "green", "timed_out" : false, "number_of_nodes" : 4, "number_of_data_nodes" : 3, "active_primary_shards" : 43, "active_shards" : 93, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 0, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 100.0 > 

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

Мастер нода выбирается в кластере путем голосования всеми нодами. Для того чтобы какая-либо нода сала мастером за неё должно проголосовать большинство.

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

Обычно лучше иметь нечетное количество узлов, имеющих право быть мастером, чтобы был кворум во время выбора мастера. Если у вас есть четное количество узлов, Elasticsearch исключит один, чтобы избежать ничьих выборов.

Каждый кластер Elasticsearch имеет конфигурацию голосования, которая представляет собой набор узлов для голосования, ответы которых учитываются при принятии таких решений, как выбор нового мастера. Решения принимаются только после того, как большинство (более половины) узлов в конфигурации голосования выберут одного и того же кандидата.

Чтобы получить текущую конфигурацию голосования:

GET _cluster/state?filter_path=metadata.cluster_coordination.last_committed_config 
 "metadata" :  "cluster_coordination" :  "last_committed_config" : [ #host1 "eiX9un9MSZ-4KsmZyEhE9Q", #host2 "CkcXQe1jRQS9_gCJ3WGkVg", #host3 "MOJcfnA4T82Pmlb2Q6wPyA" ] > > > 

Для того чтобы задать кто именно имеет права голоса можно воспользоваться переменной cluster.initial_master_nodes.

cluster.initial_master_nodes: ["host1", "host2", "host3"] 
  • Elasticsearch использует два механизма сетевой связи: HTTP для клиентов REST ; и transport для меж узловой связи
  • Детали кластера сохраняются в состоянии кластера
  • В каждом кластере есть один узел, назначенный мастер нодой
  • У вас есть возможность указать какие ноды имеют право голосовать

Поделиться: Twitter Facebook
Пожалуйста подпишитесь: Telegram Youtube

Ynwasg

О Ynwasg

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

Масштабируем кластер Kubernetes до 7500 нод

image

Фото Carles Rabada, Unsplash.com

Мы заскейлили кластер Kubernetes до 7500 нод, создав масштабируемую архитектуру для крупных моделей, вроде GPT-3, CLIP и DALL·E, и для небольших итеративных исследований, например, законов масштабирования для нейронных моделей языка. Кластер Kubernetes такого размера — редкость, и действовать нужно осторожно, зато мы получили простую инфраструктуру, в которой специалисты по машинному обучению работают быстрее и могут масштабироваться без изменения кода.

image

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

Наша рабочая нагрузка

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

Большое задание машинного обучения выполняется на множестве нод. Эффективнее всего, когда у него есть доступ ко всем аппаратным ресурсам на каждой ноде. Таким образом GPU общаются напрямую друг с другом с помощью NVLink либо с сетевой картой при помощи GPUDirect. Так что для многих рабочих нагрузок один под занимает целую ноду. Состязание за ресурсы NUMA, CPU или PCIE не учитывается при планировании. Bin-packing или фрагментация — не частая проблема. В пределах кластера элементы могут общаться друг с другом на полной скорости (full bisection bandwidth), так что мы не учитываем такие факторы, как стойки и топология сети. В итоге, несмотря на большое количество нод, на планировщик ложится относительно небольшая нагрузка.

А вот нагрузка на kube-scheduler очень неравномерная, с резкими пиками. Новое задание может привести к созданию сотен подов одновременно, а потом все снова успокаивается.

image

Наши самые большие задания используют MPI, и все поды в задании участвуют в одном MPI-коммуникаторе. Если один из подов умирает, останавливается все задание, и его приходится перезапускать. Задание регулярно создает чекпойнты и возобновляется с последнего из них. Так что поды у нас наполовину stateful — остановленные поды можно заменить, и работа продолжится, но это очень мешает и лучше не злоупотреблять.

Мы не особо полагаемся на балансировку нагрузки в Kubernetes. У нас очень мало HTTPS-трафика, нам не нужно A/B-тестирование, blue/green и canary деплои. Поды общаются друг с другом напрямую по IP-адресу с MPI через SSH, а не через эндпоинты сервиса. Service «discovery» ограничено — мы просто разово смотрим, какие поды участвуют в MPI при запуске задания.

Большинство заданий взаимодействуют с blob-хранилищем. Как правило, они стримят шарды набора данных или чекпойнты напрямую из blob-хранилища или кэшируют их на быстрый локальный эфемерный диск. У нас есть несколько PersistentVolume под семантику POSIX, но blob-хранилище куда проще масштабировать, и не нужно долго ждать присоединения и отсоединения.

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

Сети

Когда на кластерах у нас стало больше нод и подов, мы узнали, что у Flannel есть проблемы с увеличением пропускной способности до нужного уровня. Мы перешли на нативные сетевые технологии подов для конфигураций IP для Azure VMSS и соответствующие плагины CNI. Так нам удалось добиться пропускной способности на подах на уровне хоста.

Еще одна причина перехода на IP-адреса на базе алиасов в том, что на самых больших кластерах у нас могло быть по 200 000 IP-адресов одновременно. Мы тестили соединения на основе маршрутов, но нашли серьезные ограничения по числу маршрутов.

Без инкапсуляции нагрузка на базовый движок маршрутизации или SDN возросла, но зато у нас все очень просто. Добавлять VPN или туннелирование можно без дополнительных адаптеров. Мы не беспокоимся о фрагментации пакетов, потому что у части сети низкий MTU. С сетевыми политиками и мониторингом трафика все просто — источник и место назначения пакетов всегда очевидны.

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

Iptables-правила mangle позволяют произвольно отмечать пакеты, которые соответствуют определенным критериям. Вот какие правила мы используем, чтобы понять, какой трафик идет внутри, а какой — связан с интернетом. Правила FORWARD распространяются на трафик с подов, а INPUT и OUTPUT — на трафик с хоста:

iptables -t mangle -A INPUT ! -s 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-in" iptables -t mangle -A FORWARD ! -s 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-in" iptables -t mangle -A OUTPUT ! -d 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-out" iptables -t mangle -A FORWARD ! -d 10.0.0.0/8 -m comment --comment "iptables-exporter openai traffic=internet-out"

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

% iptables -t mangle -L -v Chain FORWARD (policy ACCEPT 50M packets, 334G bytes) pkts bytes target prot opt in out source destination . 1253K 555M all -- any any anywhere !10.0.0.0/8 /* iptables-exporter openai traffic=internet-out */ 1161K 7937M all -- any any !10.0.0.0/8 anywhere /* iptables-exporter openai traffic=internet-in */

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

image

У нашей сетевой модели есть одна уникальная особенность — мы полностью раскрываем CIDR-диапазоны нод, подов и сервисной сети для наших исследователей. Мы используем топологию hub and spoke и нативные CIDR-диапазоны нод и подов для маршрутизации трафика. Исследователи подключаются к хабу, а оттуда получают доступ к отдельным кластерам (лучам звезды). При этом кластеры друг с другом общаться не могут — они изолированы и не имеют общих зависимостей, которые могут помешать локализации сбоя.

Мы используем NAT для преобразования CIDR-диапазона сервисной сети для трафика, поступающего из-за пределов кластера. Благодаря такой схеме исследователи вольны решать, как и какие сетевые конфигурации использовать для экспериментов.

Серверы API

Серверы Kubernetes API и etcd — это критические компоненты здорового кластера, так что мы обращаем особое внимание на нагрузку на эти системы. Мы используем дашборды Grafana от kube-prometheus, а еще собственные дашборды. Нам показалось полезным настроить алерты о количестве ошибок HTTP 429 (слишком много запросов) и 5xx (ошибка сервера) для серверов API.

image

Некоторые предпочитают запускать серверы API в kube, нам больше нравится делать это за пределами самого кластера. У etcd и серверов API есть собственные ноды. У самых крупных кластеров по пять нод для серверов API и etcd, чтобы распределить нагрузку и свести к минимуму проблемы, если одна из них отвалится. У нас не было особых проблем с etcd с момента переноса событий Kubernetes в отдельный кластер etcd, как мы рассказывали здесь. Серверы API — stateless и обычно их легко запускать в самовостанавливающейся группе экземпляров или масштабируемом наборе. Мы еще не пробовали автоматизировать самовосстановление кластеров etcd, потому что инциденты возникают совсем редко.

Серверы API требуют немало памяти, причем потребление линейно зависит от количества нод в кластере. Для кластера на 7500 нод у нас получается куча до 70 ГБ на каждый сервер API. К счастью, возможностей нашего оборудования хватит еще надолго.

image

Серьезной проблемой для серверов API были операции WATCH на конечных точках. Есть несколько сервисов, например kubelet и node-exporter, в которые входит каждая нода на кластере. Когда нода добавлялась в кластер или удалялась из него, срабатывал WATCH. А поскольку обычно каждая нода сама следила за сервисом kubelet через kube-proxy, число и трафик в этих ответах составляли N² и целый 1 ГБ/с или даже больше. Благодаря EndpointSlices в Kubernetes 1.17 эту нагрузку удалось сократить в 1000 раз.

image

Обычно мы следим за запросами сервера API, которые растут вместе с кластером. Мы не хотим, чтобы DaemonSet взаимодействовал с сервером API. Если вам действительно нужно, чтобы каждая нода следила за изменениями, можно использовать промежуточный сервис кэширования, например Datadog Cluster Agent, чтобы избежать узких мест в кластере.

Чем больше становились кластеры, тем реже мы использовали автомасштабирование. Если автомасштабирования было слишком много, у нас иногда возникали проблемы — при добавлении новой ноды в кластер создавалось слишком много запросов, а если таких нод сразу несколько сотен, на сервер API ложилась непосильная нагрузка. Ускорив этот процесс, пусть даже на несколько секунд, мы смогли избежать перебоев.

Метрики временных рядов в Prometheus и Grafana

Мы используем Prometheus для сбора метрик временных рядов и Grafana для графов, дашбордов и алертов. Мы начали с установки kube-prometheus, который собирает самые разные метрики и создает хорошие дашборды для визуализации. Со временем мы добавили много собственных дашбордов, метрик и алертов.

Когда нод стало очень много, возникли проблемы с объемом метрик, которые собирал Prometheus. kube-prometheus дает много полезных данных, но некоторые из них мы даже никогда не смотрели, а другие были слишком подробными, чтобы можно было эффективно собирать, хранить и запрашивать их. Мы используем правила Prometheus, чтобы пропускать некоторые метрики.

Какое-то время мы пытались решить проблему, при которой Prometheus захватывал все больше и больше памяти, пока контейнер не падал с ошибкой Out-Of-Memory (OOM). Это происходило даже после того, как мы выделили приложению огромное количество памяти. Более того, когда возникал сбой, нам требовались часы, чтобы проиграть файлы write-ahead-log (WAL) и вернуть контейнер в строй.

В итоге мы отследили источник OOM. Им оказалось взаимодействие между Grafana и Prometheus, при котором Grafana использовала API /api/v1/series для Prometheus с запросом (т. е. «дай мне все метрики гистограммы»). У реализации /api/v1/series не было ограничений по времени и пространству — если у запроса было много результатов, требовалось все больше памяти и времени. Причем это продолжалось даже после того, как инициатор запроса закрывал соединение. Памяти никогда не хватало, и Prometheus вылетал. Мы пропатчили Prometheus, чтобы ограничить этот API контекстом и применить таймаут. После этого проблем не возникало.

Хотя Prometheus стал ломаться гораздо реже, когда нам все же приходилось его перезапускать, проигрывание WAL доставляло много проблем. Иногда только через много часов Prometheus снова начинал собирать метрики и обслуживать запросы. Благодаря Robust Perception мы узнали, что здесь спасает GOMAXPROCS=24 . Prometheus пытается использовать все ядра при воспроизведении WAL, а если у серверов ядер много, из-за состязания производительность сильно падает.

Мы изучаем новые варианты расширения возможностей мониторинга (см. раздел Нерешенные проблемы).

Проверки работоспособности

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

Пассивные проверки работоспособности

Некоторые проверки работоспособности всегда пассивно выполняются на всех нодах. Они отслеживают базовые ресурсы системы, вроде доступности сети, неисправных или заполненных дисков и ошибок GPU. Проблемы с GPU проявляются по-разному, но одна из самых распространенных — неисправимая ошибка ECC. С инструментами Nvidia Data Center GPU Manager (DCGM) гораздо проще делать запросы по таким и другим ошибкам Xid. Для отслеживания таких ошибок мы используем в том числе dcgm-exporter, чтобы отправлять метрики в Prometheus, нашу систему мониторинга. Это метрика DCGM_FI_DEV_XID_ERRORS с самым частым кодом ошибки. Кроме того, NVML Device Query API дает более подробную информацию о работоспособности и работе GPU.

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

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

Пассивные проверки работоспособности непрерывно выполняются в фоновом режиме на всех нодах. Если проверка работоспособности сбоит, для ноды автоматически делается cordon, чтобы на ней нельзя было планировать новые поды. Если происходит более серьезный сбой проверки работоспособности, мы вытесняем поды, чтобы они сразу завершили работу. Под сам решает, разрешить это вытеснение или нет (настраивается через Pod Disruption Budget). В конце концов, после завершения всех подов или через 7 дней (как указано у нас в SLA) мы принудительно завершаем виртуальную машину.

Активные тесты GPU

К сожалению, не все проблемы с GPU отображаются как ошибки в DCGM. Мы создали собственную библиотеку тестов, чтобы отлавливать другие проблемы с GPU и гарантировать ожидаемое поведение оборудования и драйвера. Эти тесты нельзя выполнять в фоновом режиме — они занимают весь GPU на несколько секунд или минут.

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

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

Использование квот и ресурсов

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

Taint для команд

В каждом кластере у нас есть сервис team-resource-manager с несколькими функциями. Он берет данные из ConfigMap, где указан селектор нод, метка команды и объем выделенных ресурсов для всех команд, использующих этот кластер. Он сверяет эти значения с текущими нодами в кластере, помечая нужное число нод с помощью taint openai.com/team=teamname:NoSchedule.

team-resource-manager содержит сервис вебхука допуска (admission webhook service), так что при отправке каждого задания применяется toleration в зависимости от членства в команде. Использование taint позволяет гибко ограничивать планировщик подов Kubernetes, например toleration для подов с низким приоритетом разрешает командам одалживать друг у друга емкость без масштабной координации.

Baloon для CPU и GPU

Мы используем автомасштабирование кластера, чтобы не только динамически масштабировать кластеры на базе виртуальных машин, но и исправлять (удалять и снова добавлять) нездоровые члены кластера. Для этого мы задаем для минимального размера кластера 0, а для максимального — доступную емкость. Если сервис автомасштабирования видит простаивающие ноды, он пытается сократить масштаб до необходимой емкости. Это не идеальный вариант по многим причинам — задержка при запуске виртуальной машины, затраты на предварительное выделение ресурсов и влияние на сервер API, о котором мы говорили раньше.

В результате мы решили развернуть balloon-деплой для хостов только с CPU и с GPU, куда входит ReplicaSet с максимальным размером для подов с низким приоритетом. Эти поды занимают ресурсы на ноде, так что автомасштабирование не считает, что они простаивают. При этом они имеют низкий приоритет, так что планировщик может выселить их сразу, чтобы освободить место для настоящей работы. (Мы решили использовать Deployment вместо DaemonSet, чтобы DaemonSet не считался простаивающей рабочей нагрузкой на ноде.)

Кстати, мы используем для подов anti-affinity, чтобы они равномерно распределялись по нодам. В ранних версиях у планировщика Kubernetes возникала проблема производительности O(N²) из-за anti-affinity. Начиная с Kubernetes 1.18 проблема исправлена.

Gang scheduling — параллельное планирование

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

По умолчанию Kubernetes не обязательно назначает приоритет для запросов от того или иного StatefulSet. Например, если два эксперимента запросили 100% емкости кластера, Kubernetes не планирует один из экспериментов полностью, а, допустим, берет по половине подов каждого эксперимента, что приводит к дедлоку и остановке обоих экспериментов.

Мы поэкспериментировали с кастомным планировщиком, но столкнулись с пограничными случаями, которые приводили к конфликтам с планированием обычных подов. В Kubernetes 1.18 появилась архитектура плагинов для основного планировщика Kubernetes, благодаря чему нативно добавлять такие фичи стало куда проще. Недавно мы остановились на плагине Coscheduling.

Нерешенные проблемы

Мы решили еще не все проблемы с увеличением масштаба кластеров Kubernetes. Например:

Метрики

При нашем масштабе у нас возникает немало трудностей со встроенной СУБД для хранения временных рядов в Prometheus — сжатие происходит очень медленно, а для воспроизведения WAL при каждом перезапуске требуется очень много времени. Запросы часто приводят к ошибкам «query processing would load too many samples» — при обработке запроса будет загружено слишком много сэмплов. Сейчас мы переходим на другое хранилище и движок запросов, совместимые с Prometheus.

Шейпинг трафика для подов

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

Заключение

Мы считаем, что гибкая платформа Kubernetes отлично подходит для наших исследовательских задач. Ее масштаб можно увеличить для самых требовательных рабочих нагрузок. Пока, конечно, не все еще идеально, так что команда по супервычислениям в OpenAI продолжает изучать возможности масштабирования в Kubernetes.

  • Блог компании Слёрм
  • Системное администрирование
  • Серверное администрирование
  • DevOps
  • Kubernetes

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *