Ця сторінка описує необовʼязкові функції DRA для розширених випадків використання. Деякі з цих функцій вимагають підтримки з боку драйвера DRA. Для кожної функції зазначено її зрілість та функціональну можливість, яка її вмикає.
Пристрої, представлені в DRA, не обовʼязково мають бути єдиним блоком, підключеним до однієї машини, але також можуть бути логічним пристроєм, що складається з кількох пристроїв, підключених до кількох машин. Ці пристрої можуть використовувати ресурси базових фізичних пристроїв, що перекриваються, тобто коли один логічний пристрій виділяється, інші пристрої стають недоступними.
В API ResourceSlice це представлено як список іменованих CounterSets, кожен з яких містить набір іменованих лічильників. Лічильники представляють ресурси, доступні на фізичному пристрої, які використовуються логічними пристроями, опублікованими через DRA.
Логічні пристрої можуть вказувати список ConsumesCounters. Кожен запис містить посилання на CounterSet та набір іменованих лічильників із кількостями, які вони споживатимуть. Отже, щоб пристрій можна було виділити, згадані набори лічильників повинні мати достатню кількість для лічильників, на які посилається пристрій.
CounterSets мають бути вказані в окремих ResourceSlices від пристроїв. Пристрої можуть споживати лічильники з будь-якого CounterSet, визначеного в тому самому пулі ресурсів, що й пристрій.
Ось приклад двох пристроїв, кожен з яких споживає 6Gi памʼяті зі спільного лічильника з 8Gi памʼяті. Таким чином, лише один із пристроїв може бути виділений у будь-який момент часу. Планувальник обробляє це, і це прозоро для споживача, оскільки API ResourceClaim не змінюється.
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: resourceslice-with-countersets
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 2
driver: dra.example.com
sharedCounters:
- name: gpu-1-counters
counters:
memory:
value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: resourceslice-with-devices
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 2
driver: dra.example.com
devices:
- name: device-1
consumesCounters:
- counterSet: gpu-1-counters
counters:
memory:
value: 6Gi
- name: device-2
consumesCounters:
- counterSet: gpu-1-counters
counters:
memory:
value: 6Gi
Пристрої, що розділяються на розділи, контролюються функціональною можливістю DRAPartitionableDevices у kube-apiserver та kube-scheduler.
Щоб скористатися цією функцією, вам (або адміністратору кластера) потрібно увімкнути функціональну можливість DRADeviceCompatibilityGroups для всіх відповідних компонентів у вашому кластері.
Перегляньте Увімкнення або вимкнення функціональних можливостей для отримання додаткової інформації.
Групи сумісності пристроїв дозволяють драйверу DRA оголошувати, які розділені пристрої можуть бути спільно виділені на тому самому фізичному обладнанні. Без цієї функції несумісні комбінації пристроїв виявляються лише тоді, коли kubelet готує Pod на вузлі — що призводить до невдалої підготовки. З групами сумісності планувальник відхиляє несумісні комбінації на етапі планування, до початку будь-якої роботи на стороні вузла.
Це найбільш корисно для обладнання, яке підтримує взаємовиключні режими роботи. Наприклад, GPU, який може працювати в режимі MIG або в режимі vGPU: пристрій у режимі MIG та пристрій у режимі vGPU не можуть бути спільно виділені, оскільки вони споживають ті самі фізичні ресурси несумісним чином. Оголошуючи compatibilityGroups, драйвер робить це обмеження видимим для планувальника.
Ця функція ґрунтується на пристроях, що розділяються на розділи: поле compatibilityGroups знаходиться в записах device.consumesCounters[], які існують лише для пристроїв, що розділяються на розділи. Обидві функціональні можливості DRADeviceCompatibilityGroups та DRAPartitionableDevices мають бути увімкнені в kube-apiserver та kube-scheduler.
Драйвер визначає список compatibilityGroups для кожного запису device.consumesCounters[] у ResourceSlice. Список містить щонайбільше 2 непрозорі рядкові назви, які представляють режим роботи або тип розділу цього пристрою на цьому конкретному наборі лічильників.
Коли планувальник виділяє кілька пристроїв, які використовують той самий набір лічильників, він обчислює перетин їхніх compatibilityGroups. Виділення успішне лише тоді, коли цей перетин не порожній — тобто кожен спільно виділений пристрій має принаймні одну спільну назву групи. Пристрої, які використовують різні набори лічильників, ніколи не порівнюються між собою.
Пристрій, який не оголошує жодних груп (невстановлений, nil або порожній список), розглядається як особливий випадок: він може бути спільно виділений лише з іншими пристроями без груп на тому самому наборі лічильників. Він ніколи не може бути спільно виділений з пристроєм, який оголошує одну або більше груп.
Обмеження застосовується до всіх заявок, що виділяються в одному циклі планування: якщо дві заявки кожна виділяє пристрій з того самого набору лічильників, перетин груп між заявками також забезпечується.
Розглянемо GPU, який може працювати в режимі MIG або в режимі vGPU. Драйвер публікує два пристрої, кожен з яких споживає 4 GiB з того самого спільного лічильника памʼяті обсягом 8 GiB. Лише на основі ємності лічильника обидва пристрої могли б бути виділені разом. Кожен пристрій оголошує свій режим роботи як групу сумісності, роблячи два режими взаємовиключними:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: gpu-counters
spec:
nodeName: worker-1
pool:
name: gpu-pool
generation: 1
resourceSliceCount: 2
driver: gpu.example.com
sharedCounters:
- name: gpu-0-memory
counters:
memory:
value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: gpu-devices
spec:
nodeName: worker-1
pool:
name: gpu-pool
generation: 1
resourceSliceCount: 2
driver: gpu.example.com
devices:
- name: gpu-0-mig
consumesCounters:
- counterSet: gpu-0-memory
counters:
memory:
value: 4Gi
compatibilityGroups:
- mig
- name: gpu-0-vgpu
consumesCounters:
- counterSet: gpu-0-memory
counters:
memory:
value: 4Gi
compatibilityGroups:
- vgpu
У цьому прикладі:
gpu-0-mig належить до групи mig.gpu-0-vgpu належить до групи vgpu.Якщо Pod або PodGroup запитує два пристрої з цього пулу, планувальник перевіряє, чи мають два вибрані пристрої спільну групу сумісності на наборі лічильників gpu-0-memory. Оскільки {"mig"} ∩ {"vgpu"} = ∅, пара відхиляється — навіть якщо набір лічильників має достатньо памʼяті для обох. Обидва запити можуть бути задоволені лише двома пристроями MIG (або двома пристроями vGPU) з пулу, де такі пари існують.
consumesCounters[] може оголошувати щонайбільше 2 назви груп.Коли функціональну можливість DRADeviceCompatibilityGroups вимкнено (стандартно для альфа), kube-apiserver видаляє поле compatibilityGroups з будь-якого нового або оновленого ResourceSlice — якщо старий обʼєкт уже не мав цього поля встановленим. Тоді планувальник розглядає пристрої в будь-якому пулі, який раніше мав згруповані пристрої, як такі, що належать до неповного пулу, і повністю пропускає їх.
Лише непорожній список вважається встановленим полем: compatibilityGroups: null та compatibilityGroups: [] обробляються так само, як і пропуск поля. Пристрої з ними поводяться точно так само, як пристрої без груп — вони не змушують планувальник вважати пул неповним.
Групи сумісності пристроїв контролюються функціональною можливістю DRADeviceCompatibilityGroups у kube-apiserver та kube-scheduler. Функціональна можливість DRAPartitionableDevices також має бути увімкнена.
Функція споживчої ємності дозволяє одним і тим самим пристроям споживатися кількома незалежними заявками на ресурси, при цьому планувальник Kubernetes керує тим, скільки ємності пристрою використовує кожна заявка. Це аналогічно тому, як Podʼи можуть спільно використовувати ресурси на вузлі; заявки на ресурси можуть спільно використовувати ресурси на пристрої.
Драйвер пристрою може встановити поле allowMultipleAllocations, додане в .spec.devices обʼєкта ResourceSlice, щоб дозволити виділення цього пристрою кільком незалежним заявкам на ресурси або кільком запитам у межах однієї заявки на ресурс.
Користувачі можуть встановити поле capacity, додане в spec.devices.requests обʼєкта ResourceClaim, щоб вказати вимоги до ресурсів пристрою для кожного виділення.
Для пристрою, який дозволяє кілька виділень, запитана ємність береться з, або споживається з, його загальної ємності, концепція, відома як споживча ємність. Тоді планувальник гарантує, що сукупна спожита ємність у всіх заявках не перевищує загальну ємність пристрою. Крім того, автори драйверів можуть використовувати обмеження requestPolicy для окремих ємностей пристроїв, щоб контролювати, як ці ємності споживаються. Наприклад, автор драйвера може вказати, що певна ємність споживається лише приростами по 1Gi.
Ось приклад мережевого пристрою, який дозволяє кілька виділень і містить споживчу ємність пропускної здатності.
kind: ResourceSlice
apiVersion: resource.k8s.io/v1
metadata:
name: resourceslice
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 1
driver: dra.example.com
devices:
- name: eth1
allowMultipleAllocations: true
attributes:
name:
string: "eth1"
capacity:
bandwidth:
requestPolicy:
default: "1M"
validRange:
min: "1M"
step: "8"
value: "10G"
Споживчу ємність можна запитати, як показано в прикладі нижче.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: bandwidth-claim-template
spec:
spec:
devices:
requests:
- name: req-0
exactly:
deviceClassName: resource.example.com
capacity:
requests:
bandwidth: 1G
Результат виділення міститиме спожиту ємність та ідентифікатор частки.
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
allocation:
devices:
results:
- consumedCapacity:
bandwidth: 1G
device: eth1
shareID: "a671734a-e8e5-11e4-8fde-42010af09327"
У цьому прикладі було вибрано пристрій, який допускає кілька виділень. Однак будь-який пристрій resource.example.com із принаймні запитаною пропускною здатністю 1G міг би задовольнити вимогу. Якби було вибрано пристрій, який не допускає кілька виділень, виділення призвело б до використання всього пристрою. Щоб примусово використовувати лише пристрої, які допускають кілька виділень, ви можете використати критерій CEL device.allowMultipleAllocations == true.
Коли ви запитуєте кілька пристроїв у заявці на ресурс, ви можете використати обмеження DistinctAttribute, щоб гарантувати, що кожен виділений пристрій має різне значення для вказаного атрибута. Це обмеження було введено разом із функцією споживчої ємності.
Обмеження DistinctAttribute особливо корисне під час роботи з пристроями, які допускають кілька виділень. Воно запобігає виділенню одного й того самого пристрою кілька разів у межах однієї заявки на ресурс, навіть якщо цей пристрій дозволяє кілька виділень.
Окрім запобігання дублюванню виділень, це обмеження допомагає оптимізувати продуктивність, гарантуючи розподіл пристроїв на основі їхніх атрибутів. Наприклад, ви можете використати його для розподілу пристроїв між різними NUMA-вузлами, щоб оптимізувати пропускну здатність памʼяті та зменшити конкуренцію.
Починаючи з Kubernetes v1.36, DRA застосовує детальні перевірки авторизації для оновлень статусу ResourceClaim, використовуючи синтетичні субресурси та дієслова, що враховують вузли.
Інструкції з посилення безпеки, включаючи приклади RBAC для планувальника та драйверів DRA, див. у Посібнику із зміцнення безпеки — динамічний розподіл ресурсів.
Покрокову процедуру для адміністратора кластера див. у Посиленні безпеки динамічного розподілу ресурсів у вашому кластері.
Щоб скористатися цією функцією, вам (або адміністратору кластера) потрібно увімкнути функціональну можливість DRAOptionalNodeOperations для всіх відповідних компонентів у вашому кластері.
Перегляньте Увімкнення або вимкнення функціональних можливостей для отримання додаткової інформації.
У динамічному виділенні ресурсів (DRA) kubelet координується з локальним для вузла драйвером через gRPC, щоб підготувати виділені пристрої перед запуском контейнера (NodePrepareResources) та скасувати їх підготовку після завершення роботи Podʼа (NodeUnprepareResources). Хоча така схема є критичною для локального для вузла обладнання, такого як GPU або FPGA, деякі ресурси керуються повністю в панелі управління і не потребують жодної локальної настройки на вузлі.
Функція необовʼязкових операцій з вузлами дозволяє драйверам ресурсів оголошувати, що певні локальні для вузла операції gRPC можуть бути пропущені. Після налаштування kubelet оминає пошук драйвера та виклики gRPC для цих пристроїв, усуваючи потребу розгортати та підтримувати порожні локальні для вузла драйвери на кожному робочому вузлі.
Автори драйверів можуть вказати поле skipNodeOperations у
.spec.skipNodeOperations обʼєкта ResourceSlice. Це поле є списком унікальних рядків, які вказують локальні для вузла операції, що мають бути пропущені для всіх пристроїв у цьому розділі.
Допустимі значення:
"NodePrepareResources": пропускає виклики gRPC NodePrepareResources. Це значення не можна вказати, якщо "NodeUnprepareResources" також не вказано (або не вказано "*"). Це обмеження запобігає застряганню Podʼів у стані Terminating, якщо локальний для вузла втулок відсутній, оскільки втулок не перевіряється під час запуску Podʼа, коли підготовку пропущено."NodeUnprepareResources": пропускає виклики gRPC NodeUnprepareResources."*": пропускає всі локальні для вузла операції з ресурсами.Ось приклад ResourceSlice для ресурсу панелі управління, який пропускає всі локальні для вузла операції:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: control-plane-resources
spec:
nodeName: worker-1
pool:
name: central-pool
generation: 1
resourceSliceCount: 1
driver: control-plane.example.com
skipNodeOperations:
- "*"
devices:
- name: virtual-device-1
Коли планувальник Kubernetes виділяє пристрій заявці на ресурс, він копіює список skipNodeOperations з ResourceSlice у результат виділення:
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
allocation:
devices:
results:
- device: virtual-device-1
driver: control-plane.example.com
pool: central-pool
skipNodeOperations:
- "*"
Коли Pod працює на вузлі, kubelet читає результати виділення. Якщо всі виділені пристрої для певного драйвера в межах заявки на ресурс пропускають конкретну операцію, kubelet повністю оминає виклик цього хука gRPC для цього драйвера.
Оскільки налаштування skipNodeOperations копіюється з ResourceSlice у заявку на ресурс під час виділення, запущені Podʼи та активні виділення зберігають те налаштування, яке діяло на момент їх планування.
Якщо вимоги драйвера до операцій з вузлами оновлюються на місці (наприклад, змінюються з вимоги виконання операцій на їх пропуск), наявні заявки все ще використовуватимуть попередню конфігурацію. Щоб уникнути проблем, наприклад, зависання завершуваних Podʼів в очікуванні виведеного з експлуатації втулка вузла, адміністратори кластерів мають переконатися, що для драйвера не існує активних заявок, перш ніж змінювати його вимоги до операцій з вузлами або видаляти локальні для вузла DaemonSets драйвера.
Щоб запобігти плануванню Podʼів на вузлах, де kubelet не підтримує пропуск операцій DRA (що призвело б до збою kubelet в очікуванні відсутнього втулка вузла), ця функція інтегрується з Оголошеними функціями вузла. Коли Pod використовує заявку на ресурс із налаштованим skipNodeOperations, планувальник Kubernetes перевіряє, що цільовий вузол оголошує підтримку функції DRAOptionalNodeOperations у своєму .status.declaredFeatures, перш ніж планувати Pod.
Необовʼязкові операції з вузлами контролюються функціональною можливістю DRAOptionalNodeOperations у kube-apiserver, kube-scheduler та kubelet.
Драйвери DRA можуть надавати метадані пристроїв, такі як атрибути пристроїв (адреси шини PCI або mdevUUID для пристроїв з опосередкованим керуванням) або мережеву конфігурацію, безпосередньо контейнерам у вигляді JSON-файлів. Це дозволяє застосункам усередині контейнера дізнаватися інформацію про виділені пристрої без запитів до API Kubernetes або створення власних контролерів.
KEP-5304 визначає протокол метаданих пристроїв, якого драйвери мають дотримуватися, щоб застосунки всередині контейнера бачили узгоджену структуру в різних драйверах і кластерах. Бібліотека втулка kubelet DRA реалізує цей протокол за вас; решта цього розділу описує, як ним користуватися.
Метадані пристроїв підпорядковуються тим самим правилам, що й доступ до пристроїв: вони доступні всередині контейнера лише тоді, коли цей контейнер запитує пристрій у своїй специфікації контейнера, і не інакше. Інструкції щодо запиту пристроїв DRA в Podʼах і контейнерах див. у Запиті пристроїв у робочих навантаженнях за допомогою DRA.
Протокол складається з чотирьох правил:
Шляхи до файлів. Файли метаданих розташовані всередині контейнерів у /var/run/kubernetes.io/dra-device-attributes. Для безпосередньо згаданої заявки на ресурс шлях має вигляд resourceclaims/<claimName>/<requestName>/<driverName>-metadata.json; для заявки, створеної з шаблону заявки на ресурс, шлях має вигляд resourceclaimtemplates/<podClaimName>/<requestName>/<driverName>-metadata.json (де podClaimName — це pod.spec.resourceClaims[].name).
У випадках, коли запит заявки на ресурс використовує функцію списку пріоритетів, для сегмента <requestName> у шляху до файлу використовується лише назва запиту верхнього рівня (тобто частина /<subrequest> відкидається). Усередині JSON-файлу поле requests[].name містить повне посилання <request>/<subrequest> (наприклад, gpu/high-memory), щоб споживачі могли визначити, яку альтернативу було виділено.
Константи шляхів визначені в k8s.io/dynamic-resource-allocation/api/metadata.
JSON API. Кожен файл є потоком одного або кількох обʼєктів DeviceMetadata, серіалізованих як версіонований JSON з apiVersion та kind, відповідно до конвенцій API Kubernetes. Ті самі метадані кодуються один раз для кожної підтримуваної версії API (спочатку найновіша). Усі обʼєкти в потоці семантично еквівалентні; споживачі мають використовувати перший обʼєкт, який вони можуть декодувати.
Покоління. Коли драйвер оновлює файл метаданих, вбудоване поле metadata.generation має збільшуватися, щоб споживачі могли виявляти зміни.
Надання контейнеру. Файли зазвичай надаються через CDI bind-mount, але інші механізми дозволені, якщо файл зʼявляється за правильним шляхом і є доступним лише для читання всередині контейнера.
Метадані пристроїв — це функція на стороні драйвера, яка не потребує жодних змін API Kubernetes або функціональних можливостей. Використання бібліотеки втулка kubelet DRA є поширеним способом реалізації драйвера, але драйвери можуть бути створені й іншими способами. Драйвери, які використовують втулок kubelet, вмикають цю функцію, передаючи опції EnableDeviceMetadata та MetadataVersions під час запуску втулка. MetadataVersions визначає, які версії API серіалізуються у файл метаданих, і має бути явно встановлена драйвером. Перегляньте документацію вашого драйвера DRA, щоб дізнатися, чи підтримуються метадані пристроїв і як їх увімкнути.
Коли метадані пристроїв увімкнено, драйвер генерує файли метаданих та специфікації CDI bind-mount під час підготовки виділених пристроїв для podʼа, до запуску контейнерів, що їх споживають. Метадані зʼявляються всередині контейнерів за добре відомими шляхами, як визначено вище.
Коли один запит виділяє пристрої від кількох драйверів DRA, кожен драйвер записує власний файл метаданих. Контейнери перелічують файли *-metadata.json у теці запиту, щоб виявити всі пристрої.
Пакунок Go k8s.io/dynamic-resource-allocation/devicemetadata надає утиліти для читання та декодування цих файлів метаданих застосунками всередині контейнера.
Кожен файл метаданих відповідає API DeviceMetadata (metadata.resource.k8s.io/v1alpha1). Наведений нижче приклад показує файл метаданих для пристрою GPU, виділеного через шаблон заявки на ресурс:
{
"kind": "DeviceMetadata",
"apiVersion": "metadata.resource.k8s.io/v1alpha1",
"metadata": {
"name": "pod0-gpu-2kqrd",
"namespace": "gpu-test1",
"uid": "c7e7b22e-239b-4498-b27c-7f1344481e14",
"generation": 1
},
"podClaimName": "gpu",
"requests": [
{
"name": "gpu",
"devices": [
{
"driver": "gpu.example.com",
"pool": "worker-0",
"name": "gpu-0",
"attributes": {
"driverVersion": {
"version": "1.0.0"
},
"index": {
"int": 0
},
"model": {
"string": "LATEST-GPU-MODEL"
},
"uuid": {
"string": "gpu-18db0e85-99e9-c746-8531-ffeb86328b39"
}
}
}
]
}
]
}
Драйвери надають метадані одним із двох способів:
metadata.generation, щоб споживачі могли виявляти зміни. API MetadataUpdater у бібліотеці втулка kubelet DRA автоматично обробляє облік поколінь для авторів драйверів.В обох випадках метадані залишаються доступними для кожного контейнера, що їх споживає, протягом усього життєвого циклу цього контейнера. Файли метаданих видаляються після завершення роботи всіх контейнерів у Podʼі.
Щоб дізнатися, як використовувати метадані пристроїв у ваших робочих навантаженнях, див. Доступ до метаданих пристроїв DRA.
Власні, створені вручну драйвери, які не використовують бібліотеку втулка kubelet DRA, мають самостійно реалізувати протокол метаданих пристроїв. Це означає запис JSON DeviceMetadata за правильними шляхами до файлів, збільшення metadata.generation при кожному оновленні та надання файлів лише для читання всередині контейнера через CDI або еквівалентний механізм.