Відхід від VMware після Broadcom: перевірений план міграції
Коротко (перевірено 2026-11-03): після купівлі VMware компанія Broadcom припинила продаж безстрокових ліцензій, звела лінійку продуктів до двох пакетів за підпискою, закрила стару програму хмарних партнерів у Європі, а цієї осені прибрала з публічного доступу VDDK - бібліотеку для роботи з дисками, через яку більшість інструментів міграції читає диски VMware. Сьогодні The Register повідомляє про відкриту заміну цієї бібліотеки, що працює лише на читання. Втім, не кожен шлях виходу потребує VDDK: експорт в OVA плюс конвертація диска взагалі не використовують код VMware. Нижче: зміни з датами, реалістичні цільові платформи для команди в ЄС, покроковий переїзд і конвертер, який ми запустили на справжньому експорті у форматі VMware.
Джерело. "Architect creates open-source replacement for VM migration tool VMware has hidden" (Simon Sharwood, The Register, 2026-11-03). Інші факти мають посилання там, де згадуються. Ваш власний договір може відрізнятися від публічних умов, тож уточнюйте деталі ліцензування у свого реселера.
Що змінила Broadcom: хронологія
| Дата | Зміна | Джерело |
|---|---|---|
| 2023-12-11 | Кінець продажу безстрокових ліцензій і продовження підтримки; пропозицію скорочено до підписок VMware Cloud Foundation (VCF) і vSphere Foundation (VVF) | Новини Broadcom, Krish Prasad |
| 2025-03-28 | Дистриб'ютор Arrow повідомляє партнерам, що з 2025-04-10 мінімальне замовлення зростає з 16 до 72 ядер на продуктову лінійку | The Register |
| 2026-01-26 | Договори хмарних провайдерів (VCSP) в ЄЕЗ не продовжуються; відкриті угоди треба закрити до 2026-03-31; далі програма лише за запрошеннями | The Register, 2026-01-31 |
| Aug-Sep 2026 | VDDK прибрано з публічного завантаження: "The VDDK is not a customer entitlement" (VDDK не входить у права клієнта), доступ через окремих партнерів TAP | TechTarget, 2026-09-09 |
| 2026-11-03 | OpenVDDK від Andrija Panić: сумісна заміна libvixDiskLib.so.8.0.3 лише для читання під ліцензією Apache, яка "contains no VMware code" (не містить коду VMware) | The Register, 2026-11-03 |
Два застереження. Про мінімум у 72 ядра повідомив дистриб'ютор, а не сторінка Broadcom, яку нам вдалося знайти: стаття Broadcom про підрахунок ядер (KB 313548, перевірено 2026-11-03) каже "You must license a minimum of 16 physical cores for each CPU" (потрібно ліцензувати щонайменше 16 фізичних ядер на кожен CPU), а деякі реселери стверджують, що мінімальне замовлення у 72 ядра знову скасували. Просіть письмово вказати кількість ядер у комерційній пропозиції. До того ж європейська історія ще не завершена: у липні 2025 року галузеве об'єднання хмарних провайдерів CISPE звернулося до Загального суду ЄС з вимогою скасувати схвалення злиття Єврокомісією (справа T-503/25); рішення ми поки не знайшли.
Чому VDDK важливий і які шляхи без нього обходяться
Бібліотека VDDK, libvixDiskLib, дає інструменту змогу читати диски ВМ, які ще працюють у vSphere. The Register називає серед інструментів, що її завантажують, virt-v2v, nbdkit, імпортери Forklift / MTV, Apache CloudStack і власні скрипти компаній. Розширення Microsoft VM Conversion для Windows Admin Center (з vCenter у Hyper-V, з теплою синхронізацією через Change Block Tracking) досі позначене як preview у його документації (оновлено 2026-02-20), а матеріали про запуск називають VDDK серед передумов. OpenVDDK - новий варіант для всіх них, хоча автор сам попереджає, що звіти тестувальників "not independently audited customer endorsements" (не є незалежно перевіреними відгуками клієнтів).
Шляхи взагалі без VDDK, згідно з документацією virt-v2v щодо введення з VMware (перевірено 2026-11-03): експорт OVA (-i ova), файли ВМ на NFS або через SSH (-i vmx, -it ssh), NFC і звичайний шлях через vCenter HTTPS, який документація називає "the slowest method" (найповільніший спосіб). Майстер імпорту ESXi у Proxmox VE, анонсований 2024-03-27, читає диски через ESXi API за допомогою власної файлової системи користувацького простору на Rust і був протестований з ESXi від 6.5 до 8.0 (форум Proxmox).
Куди переїжджати: п'ять реалістичних варіантів
| Цільова платформа | Підходить, коли | На що зважати |
|---|---|---|
| Proxmox VE (KVM) на вашому обладнанні | 3+ хости, команда хоче GUI і кластеризацію | Оновлення, сховище (ZFS, Ceph) і підтримка на вас |
| OpenStack | Багато орендарів, усе через API | Важкий в експлуатації; окупається лише на масштабі |
| Hyper-V (Windows Server) | Переважно Windows, ліцензії Datacenter уже оплачені | Інструменту Microsoft спершу потрібні драйвери Hyper-V у гостьових Linux |
| Хмарні сервери (KVM) | Від кількох до кількох десятків ВМ, без бажання обслуговувати гіпервізор | Один імпорт на кожну ВМ, нові IP-адреси |
| Виділені сервери + власний гіпервізор | Стабільне навантаження, потрібна вся машина | Гіпервізор ви експлуатуєте й оновлюєте самі |
Перенесення ВМ за п'ять кроків
- Інвентаризація та прибирання. Зафіксуйте прошивку (BIOS чи EFI), контролер диска, тип мережевої карти, статичні IP і будь-яку ліцензію, прив'язану до MAC-адреси. Видаліть або консолідуйте знімки, щоб експортувати один актуальний диск.
- Підготуйте гостьову систему, поки вона ще працює на VMware. Windows: встановіть драйвери virtio (virtio-win) зараз; гостьова Windows, перенесена на диск virtio без них, не завантажиться. Linux: драйвери virtio є в ядрі, але initramfs у режимі host-only може їх не містити; перевірте через
lsinitramfsабоlsinitrdі за потреби перезберіть його. - Експорт або підключення. Вимкніть ВМ і експортуйте OVF / OVA (у vSphere Client "Export OVF Template" або через ovftool), або дозвольте virt-v2v чи майстру Proxmox забрати її самостійно.
- Конвертація. Лише диск - через qemu-img, усю гостьову систему - через virt-v2v (він також налаштовує драйвери), або напряму в Proxmox з OVF.
- Завантаження через консоль, потім прибирання. Перевірте ім'я мережевого інтерфейсу та IP, видаліть VMware Tools або open-vm-tools, встановіть qemu-guest-agent.
qemu-img convert -p -f vmdk -O qcow2 disk1.vmdk disk1.qcow2
virt-v2v -i ova VM.ova -o local -os /var/tmp -of qcow2
qm importovf 120 VM.ovf local-lvm
Це задокументовані форми з посібника кожного інструмента; ці три команди ми тут не запускали. Конвертацію, яку ми справді запустили, наведено нижче.
Перевірено: що насправді робить конвертація (ova2raw.js)
Щоб показати, з чого складається процес, ми написали конвертацію приблизно в 100 рядках на Bun без залежностей. Скрипт відкриває OVA (звичайний tar), перевіряє маніфест SHA-256, виводить обладнання, яке вимагає OVF, розпаковує streamOptimized VMDK зерно за зерном у розріджений raw-образ і перевіряє GPT. Ми подали йому офіційний OVA Ubuntu 24.04, справжній експорт у форматі VMware: noble-server-cloudimg-amd64.ova, збірка від 2026-10-17, SHA-256 якої збігся з SHA256SUMS від Ubuntu. Збережіть як ova2raw.js:
// ova2raw.js - check a VMware OVA export and turn its disk into a raw image for KVM
// usage: bun ova2raw.js <export.ova> <out.raw>
import { openSync, readSync, writeSync, ftruncateSync, closeSync, fstatSync } from 'node:fs'
import { inflateSync } from 'node:zlib'
import { createHash } from 'node:crypto'
const [ova, out] = process.argv.slice(2)
const fd = openSync(ova, 'r')
const read = (pos, len) => {
const b = Buffer.alloc(len)
readSync(fd, b, 0, len, pos)
return b
}
// 1. an OVA is a plain tar: list its members
const files = {}
for (let o = 0; o + 512 <= fstatSync(fd).size; ) {
const h = read(o, 512)
if (h.every(x => x === 0)) break
const name = h.toString('utf8', 0, 100).replace(/\0.*$/s, '')
const size = parseInt(h.toString('ascii', 124, 136).replace(/\0.*$/s, '').trim(), 8)
files[name] = { pos: o + 512, size }
o += 512 + Math.ceil(size / 512) * 512
}
const find = ext => Object.keys(files).find(n => n.endsWith(ext))
const text = n => read(files[n].pos, files[n].size).toString('utf8')
console.log('OVA members:', Object.entries(files).map(([n, f]) => `${n} (${f.size} B)`).join(', '))
// 2. the .mf manifest: every member's SHA-256 must match
const mf = find('.mf')
for (const line of mf ? text(mf).split('\n') : []) {
const m = line.match(/^SHA(1|256)\((.+)\)\s*=\s*([0-9a-f]+)/i)
if (!m || !files[m[2]]) continue
const h = createHash('sha' + m[1])
const f = files[m[2]]
for (let p = 0; p < f.size; p += 1 << 24) h.update(read(f.pos + p, Math.min(1 << 24, f.size - p)))
console.log(` manifest ${m[2]}: ${h.digest('hex') === m[3].toLowerCase() ? 'OK' : 'MISMATCH'}`)
}
// 3. the .ovf descriptor: what the VM expects from its hardware
const ovf = text(find('.ovf'))
const items = [...ovf.matchAll(/<(?:ovf:)?Item\b[\s\S]*?<\/(?:ovf:)?Item>/g)].map(x => x[0])
const tag = (s, t) => s.match(new RegExp(`<(?:rasd:)?${t}>([^<]*)<`))?.[1]
const kind = { 3: 'CPU', 4: 'RAM', 5: 'IDE controller', 6: 'SCSI controller', 10: 'NIC', 17: 'disk', 20: 'SATA controller' }
console.log('OVF:', ovf.match(/vmw:osType="([^"]+)"/)?.[1] ?? '?', '| firmware',
ovf.match(/key="firmware" ovf:value="([^"]+)"/)?.[1] ?? 'bios (default)')
for (const it of items) {
const k = kind[tag(it, 'ResourceType')]
if (k) console.log(` ${k.padEnd(16)} ${tag(it, 'ElementName') ?? ''} ${tag(it, 'ResourceSubType') ?? ''} ${tag(it, 'VirtualQuantity') ?? ''}`.trimEnd())
}
// 4. the streamOptimized VMDK: a header, then compressed grains and markers
const vmdk = files[find('.vmdk')]
const hd = read(vmdk.pos, 512)
if (hd.readUInt32LE(0) !== 0x564d444b) throw new Error('not a sparse VMDK (no KDMV magic)')
const capacity = Number(hd.readBigUInt64LE(12)) * 512
const desc = read(vmdk.pos + Number(hd.readBigUInt64LE(28)) * 512, Number(hd.readBigUInt64LE(36)) * 512)
console.log(`VMDK ${desc.toString().match(/createType="([^"]+)"/)?.[1]}, virtual size ${capacity} B, grain ${Number(hd.readBigUInt64LE(20)) * 512} B`)
const ofd = openSync(out, 'w')
ftruncateSync(ofd, capacity) // sparse: unwritten grains stay holes = zeros
let pos = Number(hd.readBigUInt64LE(64)) * 512
let grains = 0
let written = 0
while (pos < vmdk.size) {
const m = read(vmdk.pos + pos, 512)
const val = Number(m.readBigUInt64LE(0))
const size = m.readUInt32LE(8)
if (size) { // compressed grain: lba, size, zlib data
const raw = inflateSync(read(vmdk.pos + pos + 12, size))
writeSync(ofd, raw, 0, raw.length, val * 512)
grains++
written += raw.length
pos += Math.ceil((12 + size) / 512) * 512
} else if (m.readUInt32LE(12) === 0) break // end-of-stream marker
else pos += 512 + val * 512 // grain table / directory / footer: metadata only
}
closeSync(ofd)
console.log(`Wrote ${out}: ${grains} grains, ${written} B of data in ${capacity} B`)
// 5. check the result: a valid GPT, and the file systems where the partitions say they are
const rfd = openSync(out, 'r')
const at = (p, l) => { const b = Buffer.alloc(l); readSync(rfd, b, 0, l, p); return b }
const gpt = at(512, 92)
const crc = Buffer.from(gpt)
crc.writeUInt32LE(0, 16)
console.log(`GPT "${gpt.toString('ascii', 0, 8)}", header CRC ${(Bun.hash.crc32(crc) >>> 0) === gpt.readUInt32LE(16) ? 'OK' : 'BAD'}`)
const n = gpt.readUInt32LE(80)
const es = gpt.readUInt32LE(84)
const ents = at(Number(gpt.readBigUInt64LE(72)) * 512, n * es)
console.log(` partition array CRC ${(Bun.hash.crc32(ents) >>> 0) === gpt.readUInt32LE(88) ? 'OK' : 'BAD'}`)
for (let i = 0; i < n; i++) {
const e = ents.subarray(i * es, (i + 1) * es)
if (e.subarray(0, 16).every(x => x === 0)) continue
const first = Number(e.readBigUInt64LE(32))
const last = Number(e.readBigUInt64LE(40))
const sb = at(first * 512 + 1024, 136)
const fs = sb.readUInt16LE(56) === 0xef53 ? `ext4 label "${sb.toString('ascii', 120, 136).replace(/\0.*$/s, '')}"`
: at(first * 512, 512).toString('ascii', 82, 87) === 'FAT32' ? 'FAT32 (EFI system partition)'
: e.toString('ascii', 0, 16) === 'Hah!IdontNeedEFI' ? 'BIOS boot (GRUB for legacy BIOS)' : 'unknown'
console.log(` p${i + 1} ${String((last - first + 1) * 512 / 2 ** 20 | 0).padStart(5)} MiB ${fs}`)
}
bun ova2raw.js noble-server-cloudimg-amd64.ova noble.raw
Результат, запущено на хмарному сервері DCXV з Bun 1.4.4 2026-11-03:
OVA members: ubuntu-noble-24.04-cloudimg.ovf (11186 B), ubuntu-noble-24.04-cloudimg.mf (213 B), ubuntu-noble-24.04-cloudimg.vmdk (590749696 B)
manifest ubuntu-noble-24.04-cloudimg.vmdk: OK
manifest ubuntu-noble-24.04-cloudimg.ovf: OK
OVF: ubuntu64Guest | firmware bios (default)
CPU 2 virtual CPU(s) 2
RAM 1024MB of memory 1024
SCSI controller SCSI Controller 0 VirtualSCSI
IDE controller VirtualIDEController 1
IDE controller VirtualIDEController 0
disk Hard Disk 1
NIC Ethernet 1 VmxNet3
VMDK streamOptimized, virtual size 10737418240 B, grain 65536 B
Wrote noble.raw: 28663 grains, 1878458368 B of data in 10737418240 B
GPT "EFI PART", header CRC OK
partition array CRC OK
p1 2558 MiB ext4 label "cloudimg-rootfs"
p14 4 MiB BIOS boot (GRUB for legacy BIOS)
p15 106 MiB FAT32 (EFI system partition)
p16 913 MiB ext4 label "BOOT"
Три запуски тривали від 14.6 до 16.2 s реального часу на цьому сервері. Raw-файл має розмір 10 GiB і займає 1.8 GB на диску.
Що вивід підказує ще до перемикання
- VmxNet3 і VirtualSCSI - це пристрої VMware. На KVM вони стають virtio-net та virtio-blk або virtio-scsi. Гостьовій системі потрібні драйвери для обох ще до першого завантаження, а мережевий інтерфейс зазвичай отримує нове ім'я, тож статичний IP, прив'язаний до старого імені, не підніметься. Перше завантаження робіть через консоль.
- Прошивка має збігатися. Цей OVF нічого не задає, отже це BIOS; на диску є і розділ BIOS boot, і системний розділ EFI, тож він завантажиться в будь-якому разі. ВМ, експортованій з
firmware=efi, на цільовій платформі потрібен UEFI (OVMF), інакше вона не знайде завантажувач. - Тонкий диск лишається тонким, лише якщо ви це забезпечите. 10 GiB віртуально, 1.8 GB даних. Копіюйте інструментами, що враховують розрідженість (
cp --sparse=always,rsync -S), або використовуйте qcow2, інакше ви передаватимете й зберігатимете нулі. - Маніфест - ваша перевірка передачі. Звіряйте SHA-256 після кожного копіювання між майданчиками: обірване завантаження без жодних скарг конвертується в диск, який збоїть пізніше.
Плануйте простій
Холодний переїзд коштує: експорт + передача + конвертація + перше завантаження. Заміряйте кожен етап на одній тестовій ВМ, потім помножте. Арифметика передачі (наша, без накладних витрат протоколу): 1.8 GB на 1 Gbit/s - це близько 15 s, 1 TB - близько 2.2 години. Теплі інструменти копіюють, поки ВМ працює, і наприкінці надсилають лише змінені блоки; тестувальники The Register повідомляють про холодні й теплі прогони з Forklift і MTV. Для баз даних і навантажених застосунків реплікація плюс перемикання DNS кращі за будь-яке копіювання дисків: спершу зменшіть TTL (див. поширення DNS і TTL) і пройдіть чеклісти з інструкції з міграції на хмарний сервер.
Коли підходять хмарні або виділені сервери DCXV
- Кілька ВМ, без команди для гіпервізора: наші хмарні сервери працюють на KVM з дисками virtio. У панелі Перевстановлення -> "Власний образ (URL)" приймає файл qcow2, raw або vmdk через https; образ, більший за диск тарифу, збільшує диск і тарифікується як розширення диска, а мережеві налаштування застосовуються лише тоді, коли в образі є cloud-init. Тарифи від EUR 15.00/міс (2 vCPU, 4 GB RAM, 40 GB; перевірено 2026-11-03, без ПДВ).
- Десятки ВМ або власний кластер Proxmox: виділені сервери від EUR 210/міс з гіпервізором на ваш вибір.
- Гостьові Windows: ми перепродаємо ліцензії Windows Server; зверніться до переїзду, щоб ліцензія була готова з першого дня.
Обидва варіанти працюють у наших дата-центрах Tier III у Празі та Ковільї.
Сплануйте переїзд разом з нами
Запустіть ova2raw.js на одній експортованій ВМ і надішліть рядки з обладнанням з його виводу на sales@dcxv.com, або запустіть хмарний сервер і перевірте імпорт самостійно.
