В статье — варианты использования расширенного планирования подов в Kubernetes, а также передовые методы его применения на практике. Информация пригодится разработчикам приложений и администраторам K8s, чтобы реализовать расширенные шаблоны развёртывания приложений, в которых есть локальность данных, совместное размещение модулей, высокая доступность и эффективное использование ресурсов кластеров K8s.
В Kubernetes задача планирования подов для определённых узлов в кластере выполняется с помощью компонента kube-scheduler. По умолчанию он фильтрует узлы на основе запросов ресурсов и ограничений каждого контейнера в созданном модуле. Затем возможные узлы оцениваются для поиска лучшей возможности для размещения пода.
Во многих сценариях планирование подов на основе ограничений ресурсов — лучший из возможных вариантов. Однако иногда администраторы Kubernetes планируют поды для опредёленных узлов в соответствии с другими вариантами ограничений. В таких случаях стоит рассматривать возможность расширенного планирования.
Сценарии использования для ручного планирования доставки от пода к узлу
В производственном применении Kubernetes всегда необходима настройка распределения подов по узлам. Перечислим некоторые распространённые сценарии, при которых расширенное планирование — оптимальный вариант.
Запуск подов на узлах с выделенным оборудованием. У некоторых приложений Kubernetes особые требования к оборудованию. Поды, выполняющие задания по машинному обучению, требуют высокопроизводительных графических процессоров вместо ЦП, в то время как модули Elasticsearch более эффективны на твердотельных накопителях, чем на жёстких дисках. Лучшая практика для любого управления кластером K8s с учётом ресурсов — это назначать поды узлам с правильным оборудованием.
Совместное размещение подов и кодовая зависимость. В настройке микросервисов или тесно связанном стеке приложений определённые поды должны быть размещены на одном компьютере, чтобы повысить производительность, избежать проблем с задержкой в сети и сбоев подключения. Рекомендуется запускать веб-сервер на том же компьютере, что и службу кэширования в памяти или базу данных.
Локальность данных. Требования к локальности данных для приложений, интенсивно использующих данные, аналогичны предыдущему варианту использования. Для более быстрого чтения и лучшей пропускной способности записи этим приложениям требуется развёртывание баз данных на одном компьютере с клиентским приложением.
Ресурсы Kubernetes для расширенного планирования подов
Kubernetes располагает множеством ресурсов и стратегий API. Рассмотрим некоторые из них: концепции nodeSelector, сродства узлов и сродства между модулями — и покажем наглядно, как реализовать их в вашем кластере K8s.
Ручное планирование подов с помощью nodeSelector
В более ранних версиях K8s пользователи могли планировать поды вручную, используя поле nodeSelector в файле PodSpec. NodeSelector — метод планирования от пода к узлу на основе меток, при котором пользователи назначают определённые метки узлам и следят за тем, чтобы поле nodeSelector соответствовало этим меткам.
Допустим, что одна из меток узла — «storage = ssd», указывающая тип хранилища на узле.
kubectl describe node “host01”
Name: host01
Roles: node
Labels: storage=ssd,
Для планирования подов на узле с этой меткой указываем поле nodeSelector с ней в манифесте для пода.
apiVersion: v1
kind: Pod
metadata:
name: pod
labels:
env: dev
spec:
containers:
- name: your-container
image: your-container-image
imagePullPolicy: IfNorPresent
nodeSelector:
storage: ssd
Селекторы узлов — наиболее доступный метод расширенного планирования модулей. Однако они неэффективны в случаях, когда при планировании подов приходится учитывать дополнительные правила и условия.
Сродство узлов
Функция привязки узлов более эффективна по сравнению с ручным размещением подов. Она включает в себя функциональный язык сродства с использованием логических операторов и ограничений, обеспечивающий точный контроль над размещением подов. Язык поддерживает мягкие и жёсткие правила планирования, позволяющие контролировать строгость ограничений привязки узлов в зависимости от требований пользователя.
Рассмотрим привязку узлов для размещения подов на узлах в определённых зонах доступности.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
affinity:
nodeAffinity
requiredDuringSchedulingIgnoreDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/cp-az-name
operator: In
values:
- cp-1a
- cp-1b
preferredDuringSchedulingIgnoreDuringExecution:
- weight: 7
preference:
matchExpressions:
- key: custom-key
operator: In
values:
- custom-value
containers:
- name: node-affinity
image: your-container-image
Жёсткие правила сродства указываются в поле “required during scheduling ignored during execution”(«Требуется во время планирования, игнорируется во время выполнения») раздела nodeAffinity в манифесте для пода. В примере планировщику было дано задание разместить модуль только на узлах с меткой, имеющей ключ kubernetes.io/cp-az-name и значения cp-1a или cp-1b.
Для этого использован логический оператор In, фильтрующий массив существующих значений меток. Другие возможные в подобных случаях операторы: NotIn, Exists, DoesNotExist, Gt и Lt.
Мягкое правило указывается в поле спецификации «Предпочитается во время планирования, игнорируется во время выполнения» (“preferred during scheduling ignored during execution”). В примере среди узлов, отвечающих жёстким критериям, используются узлы с меткой, имеющей ключ с именем custom-key и значением custom-value. Если таких узлов нет, возможно добавление подов другим кандидатам, если они соответствуют жёстким критериям.
Эффективная практика — создание правил привязки узлов таким образом, чтобы они включали как жёсткие, так и мягкие правила. Следование этому подходу максимальных усилий (использовать по возможности ту или иную опцию, но не отклонять планирование, если опция недоступна) помогает гибко и предсказуемо планировать развёртывание.
Межподовое сродство
Сродство в Kubernetes — это функция, служащая для планирования подов на основе их отношения к другим подам. Сродство позволяет использовать различные интересные варианты, такие как совместное размещение подов в рамках созависимой службы (служб) или реализация локальности данных, при которой поды данных работают на том же компьютере, что и основной под службы.
Сродство между пакетами определяется аналогично сродству узлов. В данном случае мы будем использовать поле podAffinity внутри спецификации.
apiVersion: v1
kind: Pod
metadata:
name: example-pod-affinity
spec:
affinity:
nodeAffinity
requiredDuringSchedulingIgnoreDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: failure-domain.beta.kubernetes.io/zone
containers:
- name: node-affinity
image: your-container-image
Как и сродство узла, сродство пода определяется с помощью выражений соответствия и логических операторов. Но в этом случае они применяются к селектору меток подов, работающих на определённом узле. Если указанное выражение совпадает с меткой целевого пода, новый под размещается вместе с целевым на том же компьютере.
Анти-сродство
Иногда предпочтительнее использовать метод чёрного списка для планирования пакетов. Такой подход препятствует планированию подов на определённых узлах при невыполнении опредёленных условий. Такая концепция называется анти-сродством Kubernetes.
В основном анти-сродство от пода к узлу применяется для выделенных узлов. Для управления использованием ресурсов в кластере администраторы K8s могут выделить определённые узлы для опредёленных типов подов и приложений.
Есть и другие интересные варианты использования анти-сродства между подами. Во-первых, это предотвращение единой точки отказа. Это достигается путём распределения подов в рамках одного и того же сервиса по разным машинам, что требует предотвращения совмещения подов с другими подами того же типа. Во-вторых, это предотвращение конкуренции между службами за ресурсы. Для повышения производительности некоторых сервисов их не стоит размещать вместе с другими сервисами, которые потребляют много ресурсов.
Анти-сродство от пода к узлу в Kubernetes может быть достигнуто с помощью порчи и толерантности.
Порча и терпимость
Ошибки, условия и допуски помогают пользователю контролировать планирование подов для определённых узлов без изменения уже существующих.
По умолчанию все поды, не имеющие допусков к порче узла, будут отклонены или исключены из него. Такое поведение обеспечивает гибкость шаблонов развёртывания кластеров и приложений. Не нужно изменять спецификацию пода, если вы не хотите, чтобы он работал на определённых узлах.
Реализовать порчи и терпимости довольно просто. Для начала — добавить к узлу искажение, для которого необходимо применить нестандартное поведение при планировании:
kubectl taint nodes host2 storage=ssd:NoSchedule
node “host1” tainted
Формат порчи выглядит так: <taintKey> = <taintValue>: <taintEffect>. Эффект порчи в примере предотвращает включение любого модуля без соответствующих допусков в расписание для этого узла.
Другие поддерживаемые эффекты порчи — NoExecute и PreferNoSchedule (мягкая версия NoSchedule). При использовании эффекта PreferNoSchedule kube-scheduler попытается не размещать под модуль без требуемого допуска на испорченный узел.
Эффект NoExecute вызывает мгновенное удаление всех подов без определённого разрешения узла. Он может быть полезен, если у вас уже есть поды, запущенные на узле, которые вам больше не нужны.
Создание порчи — лишь первая часть настройки. Чтобы поды могли быть запланированы на испорченном узле, нужно добавить терпимость:
apiVersion: v1
metadata:
name: esearch
spec:
containers:
- name: esearch
image: your-es-container
resources:
requests:
cpu: 0.8
memory: 4Gi
limits:
cpu: 3.0
memory: 22Gi
tolerations:
- key: “storage”
operator: “Equal”
value: “ssd”
effect: “NoSchedule”
Терпимость для порчи добавлена с помощью оператора «Равно». Можно использовать оператор Exists, который применяет допуск к любому узлу, соответствующему ключу порчи. При этом точное значение указывать не нужно.
В этом случае используем taint storage = ssd: NoSchedule, чтобы запланировать под, определённый выше, для узла.
Анти-сродство подов
Также можно отделять поды друг от друга с помощью функции анти-сродства. Одна из лучших практик, доступных Kubernetes — избегание единой точки отказа с распределением подов по разным зонам доступности. Можно настроить аналогичное поведение в анти-сродстве для части спецификации пода. Для анти-сродства нам понадобятся два пода.
Первый под:
apiVersion: v1
kind: Pod
metadata:
name: s1
labels:
security: s1
spec:
containers:
- name: c1
image: first-image
Для первого пода использована метка “security: s1.”
Второй под:
apiVersion: v1
kind: Pod
metadata:
name: s2
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoreDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- s1
topologyKey: kubernetes.io/hostname
containers:
- name: pod-anti-affinity
image: second-image
Второй под относится к селектору меток security: s1 в spec.affinity.podAntiAffinity. Соответственно, он не будет назначен узлу, на котором уже размещены какие-либо поды с меткой security: s1.
Заключение
Расширенное планирование подов позволяет использовать множество интересных вариантов и продвинутых практик для развёртывания сложных приложений и микросервисов в Kubernetes. С помощью привязки подов реализуется их размещение и локализация данных для тесно связанных стеков приложений и микросервисов.
В таблице — ключевая информация для каждого типа ресурсов.
Обзор возможностей Kubernetes для расширенного планирования подов
Тип ресурса | Когда используется | Достоинства | Недостатки | Оптимальные сферы применения |
nodeSelector | Назначение подов узлам с определёнными метками | Простота использова-ния, незначитель-ные изменения в PodSpec | Отсутствие поддержки логических операторов, ограниченные возможности расширения с помощью сложных правил планирования | Только в ранних версиях K8s, не поддержива-ющих сродство узлов |
Сродство узлов | Реализация локальности данных, запуск модулей на узлах с помощью специального программного обеспечения. | Функциональ-ный синтаксис с поддержкой логических операторов, детальный контроль над правилами размещения подов, поддержка жёстких и мягких правил размещения подов | Необходи-мость модификации существующих подов для изменения поведения | Сочетание жёстких и мягких правил для охвата различных вариантов использова-ния и сценариев |
Сродство подов | Размещение модулей в зависимой службе, обеспечива-ющей локальность данных | Аналогично сродству узлов | Необходи-мость модификации существующих подов для изменения поведения | Правильное управление метками в подах и документи-рование используемых меток |
Анти-сродство подов | Обеспечение высокой доступности через распростране-ние пакетов, предотвраще-ние конкуренции между сервисами за ресурсы | Развёрнутый контроль над отторжением подов, поддержка жёстких и мягких правил анти-сродства подов | Необходи-мость модификации существующих подов для изменения поведения | Аналогично сродству узлов |
Порча и терпимость | Узлы с выделенным программным обеспечением, разделение ресурсов команды | Отсутствие необходи-мости модификации существующих подов, поддержка автоматического удаления подов без требуемой терпимости, поддержка различных эффектов порчи | Отсутствие поддержки синтаксиса с логическими операторами | Необходимо быть осторожными при нанесении нескольких порч на узел. Важно убедиться, что нужные поды имеют требуемые уровни терпимости |
Используя анти-сродство узлов и порчу, можно запускать узлы с оборудованием, выделенным для определённых приложений и сервисов, обеспечивая эффективное использование ресурсов в кластере. При помощи анти-сродства пода и анти-сродства узла возможна высокая доступность приложений с избежанием единой точки отказа: в таком случае разные компоненты будут работать на разных узлах.
Сродство и анти-сродство — две мощные концепции в Kubernetes, которые необходимо освоить каждому администратору кластера.