Постепенно всё больше пользователей начинают использовать ресурсы нашего внутреннего облака, но не всем командам подходят стандартные серверы. Недавно появилась задача: создавать виртуальные машины по запросу из vRA с подключением дополнительных PCI-устройств. Сделать это можно двумя способами (ручную настройку VM как возможное решение рассматривать не будем):
- Подготовить отдельный шаблон VM с уже проброшенным устройством.
Минусы: 1. это ещё один шаблон, которому нужна поддержка и обновление; 2. если PCI-устройств несколько, то автоматически выбрать свободное при развертывании VM не получится.
Плюсы: быстро и просто. - Программно изменить конфигурацию обычной виртуальной машины.
Минусы: нужно написать скрипт реконфигурации VM.
Плюсы: вы получите универсальное решение, работающее со всеми шаблонами VM и различными устройствами.
В нашем случае задача состоит в пробросе одного из четырёх GPU NVIDIA Tesla M6, устройства разные (разные ID), поэтому подходит второй вариант. Будем разрабатывать процесс изменения конфигурации VM в vRO.
Современный графический ускоритель можно пробросить в виртуальную машину как полностью, так и частично (vGPU), распределив ресурсы одного GPU между несколькими клиентами. Разберем оба варианта.
Предоставление VM части ресурсов GPU (режим vGPU)
Настройка vGPU в vCenter (HTML Client): Host -> Configure -> Grafics
var vmSpec = new VcVirtualMachineConfigSpec(); vmSpec.deviceChange = [new VcVirtualDeviceConfigSpec()]; vmSpec.deviceChange[0].operation = VcVirtualDeviceConfigSpecOperation.add; vmSpec.deviceChange[0].device = new VcVirtualPCIPassthrough(); vmSpec.deviceChange[0].device.deviceInfo = new VcDescription(); vmSpec.deviceChange[0].device.deviceInfo.summary = ''; vmSpec.deviceChange[0].device.deviceInfo.label = 'Tesla M6 vGPU'; vmSpec.deviceChange[0].device.backing = new VcVirtualPCIPassthroughVmiopBackingInfo(); vmSpec.deviceChange[0].device.backing.vgpu = vgpuType; vmSpec.memoryReservationLockedToMax = true; vm.reconfigVM_Task(vmSpec);
В первой строке создаются новые параметры конфигурации vmSpec (VcVirtualMachineConfigSpec) для добавления устройства (VcVirtualDeviceConfigSpec) в виртуальную машину. Затем Вы описываете все изменения устройств в массиве deviceChange (VcVirtualDeviceConfigSpec). Для каждого устройства определяете тип операции (add/edit/remove), тип устройства и необходимые параметры. Подробно о доступных полях и их типах читайте в описании VcVirtualMachineConfigSpec.
В vmSpec.deviceChange[0].device.backing.vgpu передаётся тип vGPU (зависит от модели устройства, объёма выделяемой клиенту памяти GPU и вида лицензирования vGPU). Для просмотра всех доступных Вам типов vGPU можете выполнить следующее:
var vgpuTypes = host.config.sharedPassthruGpuTypes; System.log(vgpuTypes); // результат - массив строк с типами vGPU
Не забывайте про особенности работы VM с проброшенным устройством:
- необходимо полностью зарезервировать память VM, без этого она просто не включится;
- отсутствует поддержка некоторых технологий, таких как: vMotion, HA, FT, DRS (кроме Cisco UCS);
- отсутствует горячее добавление устройств, приостановление и возобновление работы VM, создание снимков VM (кроме Cisco UCS).
Если с прекращением работы некоторых технологий нужно просто смириться, то без резервирования памяти VM не будет работать. Настройку автоматического включения Memory Reservation выполняет строка vmSpec.memoryReservationLockedToMax = true.
Прямой проброс GPU в VM (VMDirectPath I/O)
Настройка DirectPath I/O PCI в vCenter (HTML Client): Host -> Configure -> PCI Devices
Следующий скрипт очень похож на предыдущий, только вместо типа устройства VcVirtualPCIPassthroughVmiopBackingInfo используется тип VcVirtualPCIPassthroughDeviceBackingInfo.
var freePciId = System.getModule("my.cloud").asGetFreeGpuId(vmhost); if (freePciId == undefined) { throw "Free PCI device not found" } else { System.log("Found free PCI device: " + freePciId); var vmSpec = new VcVirtualMachineConfigSpec(); vmSpec.deviceChange = [new VcVirtualDeviceConfigSpec()]; vmSpec.deviceChange[0].operation = VcVirtualDeviceConfigSpecOperation.add; vmSpec.deviceChange[0].device = new VcVirtualPCIPassthrough(); vmSpec.deviceChange[0].device.backing = new VcVirtualPCIPassthroughDeviceBackingInfo(); vmSpec.deviceChange[0].device.backing.deviceId = "13f3"; vmSpec.deviceChange[0].device.backing.systemId = "5a8292d1-d421-b231-ddc0-48df372226c1"; vmSpec.deviceChange[0].device.backing.deviceName = "PCI device 0"; vmSpec.deviceChange[0].device.backing.vendorId = 4318; vmSpec.deviceChange[0].device.backing.id = freePciId; vmSpec.memoryReservationLockedToMax = true; var task = vm.reconfigVM_Task(vmSpec); }
В скрипте используется дополнительный action, который возвращает ID первого свободного графического ускорителя:
// Action asGetFreeGpuId // Получаем в массив pciIds список всех доступных для проброса устройств в кластере var pciIds = []; var ptArray = cluster.EnvironmentBrowser.queryConfigTarget(host).pciPassthrough; for (var i = 0; i < ptArray.length; ++i) { pciIds.push(ptArray[i].pciDevice.id); } // Для отдельного хоста этот список можно сформировать так: /*var ptArray = host.config.pciPassthruInfo; for (var i = 0; i < ptArray.length; ++i) { if (ptArray[i].passthruEnabled == true) { pciIds.push(ptArray[i].id); } }*/ // После выполнения предыдущего блока мой массив содержал следующие значения: // var pciIds = ["0000:8a:00.0", "0000:8d:00.0", "0000:0f:00.0", "0000:0c:00.0"]; // Определяем, какие из устройств свободны, анализируя конфигурацию всех VM на хосте // Если знаете более изящный способ, то обязательно поделитесь var vms = host.vm; for each (vm in vms) { if (vm.config != undefined) { var device = vm.config.hardware.device; for (var i = 0; i < device.length; ++i) { if (device[i].deviceInfo.label == "PCI device 0") { // из массива ID устройств удаляем все уже задействованные delete pciIds[pciIds.indexOf(device[i].backing.id)]; } } } } // возвращаем ID первого из свободных устройств return first(pciIds); function first(p){ for (var i in p) return p[i]; }
Вызов cluster.EnvironmentBrowser.queryConfigTarget(host).pciPassthrough возвращает массив с описанием каждого из устройств:
[VcVirtualMachinePciPassthroughInfo]-[class com.vmware.o11n.plugin.vsphere_gen.PciPassthroughInfo_Wrapper] -- VALUE : (vim.vm.PciPassthroughInfo) { dynamicType = null, dynamicProperty = null, name = , configurationTag = null, pciDevice = (vim.host.PciDevice) { dynamicType = null, dynamicProperty = null, id = 0000:0c:00.0, classId = 768, bus = 12, slot = 0, function = 0, vendorId = 4318, subVendorId = 4318, vendorName = NVIDIA Corporation, deviceId = 5107, subDeviceId = 4419, parentBridge = 0000:0a:08.0, deviceName = NVIDIANVIDIA TESLA M6 }, systemId = 5a8292d1-d421-b231-ddc0-48df372226c1 }
Эти данные можно использовать при заполнении параметров устройства в скрипте конфигурации. Для однотипных устройств все параметры, кроме ID, будут одинаковые. Наш готовый процесс в vRealize Orchestrator включает следующие основные этапы:
- Подготовка входных параметров;
- Выключение VM;
- Проверка на необходимость миграции и миграция VM (если в кластере не все хосты имеют необходимое для проброса оборудование, то VM нужно переместить на подходящий хост);
- Реконфигурация VM;
- Включение VM.