Виртуализация, на мой взгляд, всё ещё остаётся одной из самых важных технологий в администрировании ЦОД. Да, конечно “все” будут рассказывать, что контейнеры намного более удобные, и всё надо запихивать в Кубер, и всё такое… Но после гигантского нагромождения никому не нужных конфигов, в какой-то момент ты начинаешь понимать, что зашёл слишком далеко.

И действительно. Мы пишем ПО для обслуживания целого ЦОДа. Изначально всё должно было быть контейнером, и всё должно было распространяться через CI/CD, но когда дело доходит до дела, ты начинаешь понимать, что нет ничего проще установленного линукса, на котором напрямую запускается твоя утилита, написанная на golang.

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

Под катом, давайте окунёмся в мир работы с QEMU и подёргаем сам эмулятор. Конечным результатом должна быть клонированная через golang Debian Linux.

▍ Вступление


Итак, я думаю, все понимают разницу между виртуализацией и контейнерами. Если нет, то рекомендую ознакомиться. Материалов по этой теме — просто несчётное количество. Вышеприведённая ссылка — первая из поисковой выдачи, и вы сможете найти множество документации.

Основной плюс контейнеров в том, что ими легко управлять, и намного проще делить ресурсы межу программами. А вот виртуальные машины не настолько удобные. Если у тебя есть что-то, что жрёт 2 гига памяти, что жрать оно эти 2 гига будет.

▍ ТЗ


Что мы делаем здесь? Мы будем упрощать жизнь в управлении виртуальными машинами, и напишем небольшую утилиту, которая позволит напрямую работать с QEMU через консоль, создавая эти машины на ходу. Исходники утилиты будут доступны в конце статьи.

Ещё раз, повторюсь — мы напишем простую утилиту, которая позволит создавать, стартовать, удалять и запускать виртуальные машины на QEMU. Цель написания этой утилиты — показать, как вы можете с помощью языка Golang программно создавать виртуальные машины.

Казалось бы, у нас в руках есть virsh, но, как показала практика, он не такой удобный и полезный, как мне бы того хотелось. Virsh умеет управлять QEMU из консоли, но по факту — это просто текстовый интерфейс, который не очень хорошо работает в скриптах и внешних программах. Для того чтобы мне было удобно управлять виртуальными машинами, мне надо будет написать свою утилиту, которая просто будет принимать параметры на вход, выполнять команды и завершаться.

▍ Немного матчасти


Напомним, что мы смотрим на один из компонентов целого набора систем для управления виртуализацией.

У нас есть сам гипервизор — подсистема в ядре вашей ОС, которая позволяет создавать виртуальное пространство в процессоре и памяти физического компьютера. В данном случае мы будем использовать гипервизор KVM.

Далее — сам эмулятор. Это обёртка, которая превращает гипервизор в то, что выглядит как компьютер. К этому компьютеру добавляются виртуальные порты, устройства, системы ввода и вывода, и в итоге у нас появляется то, что выглядит как компьютер, нарисованный на экране. QEMU это наш эмулятор. Он умеет работать поверх KVM, HVF или уже богом забытого проприетарного кода.

Система управления всем этим добром. Для того чтобы отправлять команды туда и обратно, вам нужна библиотека, которая эти команды может дёргать. В данном случае мы будем использовать libvirt.

Сам UI для управления виртуальными машинами. Мы можем воспользоваться консольным virsh или более гуманным virtual machine manager, который идёт в комплекте с QEMU. Но, в данной статье мы будем переписывать именно этот компонент. Поскольку с ним не так-то просто работать, как хотелось бы.

▍ Приступим


Писать будем на golang, потому что на нём только ленивый не пишет. Хотя работать с libvirt можно и на других языках, выбирайте что хотите.

Самый неприятный момент в разработке утилит на libvirt — это зубодробительная документация. К сожалению, ребята руководствовались принципом, что если это писалось сложно, то и пониматься должно сложно.

Для наших целей давайте возьмём обёртку на golang вот отсюда.

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

▍ Что же, давайте разбираться


Для начала — давайте серьёзно упростим подключение к нашему эмулятору и уберём всё сложное и ненужное. Это — пример того, как написать код для подключения к libvirt без каких-либо ворнингах об устаревании кода.

var v *libvirt.Libvirt
func virtinit() {
	v = libvirt.NewWithDialer(dialers.NewLocal(dialers.WithLocalTimeout(time.Second * 2)))
	if err := v.Connect(); err != nil {
		log.Fatalf("failed to connect: %v", err)
	}
}

Эта функция подключит вас к QEMU, запущенному на локальной машине. С использованием v вы сможете вызывать любые нужные функции libvirt для управления вашими машинами.

Вопрос только в том, какие функции вам надо запускать?

К сожалению, вот тут вас ждёт подвох. По-хорошему единственная вменяемая документация к libvirt живёт на сайте самого libvirt.

Если вы пройдёте по этому адресу и посмотрите на доки, то вы увидите, что они всеобъемлющи и написаны на C++. Что не удивительно. Наша обёртка на Golang позволяет запускать все эти команды без необходимости перевода параметров в непонятные структуры и тому подобное. Пользоваться этим удобно.

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

Иногда знание команды virsh может помочь найти нужную функцию в libvirt, но такое бывает не всегда.

▍ Давайте начнём с того, что перезагрузим машину


// VirtualMachineSoftReboot reboots a machine gracefully, as chosen by hypervisor.
func VirtualMachineSoftReboot(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainReboot(d, libvirt.DomainRebootDefault)
	herr(err)

	hok(fmt.Sprintf("%v was soft-rebooted successfully", id))
}

Код достаточно прост. Нам нужно сначала найти указатель на машину, с которой мы работаем, потом вызвать int virDomainReboot(virDomainPtr domain, unsigned int flags). Вызываем мы это на экземпляре нашего соединения, которое мы установили в самом начале.

В имплементации, в golang мы получаем указатель на виртуальную машину по её имени, и вызываем функцию virDomainReboot, которая в golang, называется просто DomainReboot.

Плюс, как я говорил, у libvirt есть свой «язык». Например, domain это то, что в данной статье я буду называть “виртуальной машиной”. Возможно, это не будет самым хорошим переводом с точки зрения номенклатуры libvirt, но вот для наших целей — подходит как нельзя лучше.

Давайте быстро посмотрим на функции обработки ошибок,

func herr(e error) {
	if e != nil {
		fmt.Printf(`{"error":"%v"}`, strings.ReplaceAll(e.Error(), "\"", ""))
		os.Exit(1)
	}
}

func hok(message string) {
	fmt.Printf(`{"ok":"%v"}`, strings.ReplaceAll(message, "\"", ""))
	os.Exit(0)
}

func hret(i interface{}) {
	ret, err := json.Marshal(i)
	herr(err)
	fmt.Print(string(ret))
	os.Exit(0)
}

Здесь всё просто, мы выходим из программы через os.Exit и возвращаем код ошибки. С кодами заморачиваться не будем. 1 — есть ошибка, 0 — нет ошибки.

Как вы видите, сам вывод программы отформатирован в json. Правильно, потому что мне надо будет дёргать этот код через web, и в конце концов вывод этой утилиты будет скормлен большому брату, который управляет серверами.

Посему я решил, что json-formatted вывод будет наиболее удобным в данном случае.

Итак, проверяем нашу программу, запускаем её на локальном компьютере, всё работает, виртуальная машина перезагружается! Отлично!

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

Поехали дальше.

▍ Создадим виртуальную машину


// VirtualMachineCreate creates a new VM from an xml template file
func VirtualMachineCreate(xmlTemplate string) {

	xml, err := ioutil.ReadFile(xmlTemplate)
	herr(err)

	d, err := v.DomainDefineXML(string(xml))
	herr(err)

	hret(d)
}

Тут всё просто. Libvirt и QEMU описывают виртуальные машины в XML формате. Создав такой файл, вы описываете все настройки необходимой виртуальной машины и после этого можете клепать их направо и налево.

Для того чтобы получить такой файл, я рекомендую воспользоваться virsh dumpxml VM1 > VM1.xml

Этот код позволит вам записать все данные о текущей виртуальной машине в xml файл. Прочитав файл, вы запросто разберётесь в том, что и как в нём надо менять. Единственное что, прошу убедиться, что вы изменили ID и GUID виртуальной машины.

А теперь, давайте перейдём к тому, почему я взялся за написание своей утилиты. Virsh не позволяет по-роботски получить данные о виртуальных машинах. Для того чтобы узнать количество процессоров, памяти и того подобных вещей, мне надо было парсить вывод командной строки virsh.

// VirtualMachineState returns current state of a virtual machine.
func VirtualMachineState(id string) {
	var ret tylibvirt.VirtualMachineState

	d, err := v.DomainLookupByName(id)
	herr(err)

	state, maxmem, mem, ncpu, cputime, err := v.DomainGetInfo(d)
	herr(err)

	ret.CPUCount = ncpu
	ret.CPUTime = cputime
	// God only knows why they return memory in kilobytes.
	ret.MemoryBytes = mem * 1024
	ret.MaxMemoryBytes = maxmem * 1024
	temp := libvirt.DomainState(state)
	herr(err)

	switch temp {
	case libvirt.DomainNostate:
		ret.State = tylibvirt.VirtStatePending
	case libvirt.DomainRunning:
		ret.State = tylibvirt.VirtStateRunning
	case libvirt.DomainBlocked:
		ret.State = tylibvirt.VirtStateBlocked
	case libvirt.DomainPaused:
		ret.State = tylibvirt.VirtStatePaused
	case libvirt.DomainShutdown:
		ret.State = tylibvirt.VirtStateShutdown
	case libvirt.DomainShutoff:
		ret.State = tylibvirt.VirtStateShutoff
	case libvirt.DomainCrashed:
		ret.State = tylibvirt.VirtStateCrashed
	case libvirt.DomainPmsuspended:
		ret.State = tylibvirt.VirtStateHybernating
	}

	hret(ret)
}

А вот в данном случае – мы получаем информацию о состоянии определённой машины прямо в консоли в виде отличного JSON. Теперь наша маленькая утилита становится очень полезной утилитой. Она позволяет быстро и удобно сканировать все виртуалки на сервере.

В добавку к этому коду приведу очень полезный набор констант.


// VirState represents current lifecycle state of a machine
// Pending = VM was just created and there is no state yet
// Running = VM is running
// Blocked = Blocked on resource
// Paused = VM is paused
// Shutdown = VM is being shut down
// Shutoff = VM is shut off
// Crashed = Most likely VM crashed on startup cause something is missing.
// Hybernating = Virtual Machine is hybernating usually due to guest machine request
// TODO:
type VirtState string

const (
	VirtStatePending     = VirtState("Pending")     // VM was just created and there is no state yet
	VirtStateRunning     = VirtState("Running")     // VM is running
	VirtStateBlocked     = VirtState("Blocked")     // VM Blocked on resource
	VirtStatePaused      = VirtState("Paused")      // VM is paused
	VirtStateShutdown    = VirtState("Shutdown")    // VM is being shut down
	VirtStateShutoff     = VirtState("Shutoff")     // VM is shut off
	VirtStateCrashed     = VirtState("Crashed")     // Most likely VM crashed on startup cause something is missing.
	VirtStateHybernating = VirtState("Hybernating") // VM is hybernating usually due to guest machine request
)

Как я уже говорил, читать коды возврата libvirt — не очень удобно и понятно. После получаса сидения в гугле и stakoverflow я собрал данные о том, что же означают коды остановки виртуальных машин.

▍ Всё! Всё готово


Теперь вас может остановить только ваше воображение.

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

Запускаем нашу утилиту и пытаемся включить одну из виртуальных машин на моём компьютере:

 ./tarsvirt --id debian11 --virtual-machine-start
{"error":"Cannot access storage file '/dev/tars_storage/vol2': No such file or directory"} 

Итак, система попыталась запустить виртуальную машину, и завершила работу с ошибкой. Об ошибке мы узнали в приятном JSON формате, и нам не придётся парсить вывод virsh для того, чтобы понять, что что-то не так.

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

./tarsvirt --id debian11 --virtual-machine-start
{"ok":"debian11 was started"}

Красота. Всё запустилось. Проверяем через VM Manager:



Так и есть, машина работает.

Теперь, просто из интереса, проверяем статус виртуальной машины:

./tarsvirt --id debian11 --virtual-machine-state
{"State":"Running","MaxMemoryBytes":1073741824,"MemoryBytes":1073741824,"CPUCount":2,"CPUTime":27560000000,"StateDate":null}

Отлично, мы знаем, что мы запустились на одном гигабайте памяти с двумя процессорами. И машина работает исправно.

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

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

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

Удобно, надёжно, а главное – очень просто. Если вам интересно как — могу поделиться данными в будущих статьях. А пока — удачного ломания libvirt.

PS. Утилита настолько простая, что вот вам программный код:


Как в старые, добрые, ламповые времена, когда код распространялся напечатаным в журналах
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"github.com/spf13/pflag"

	"uncloudzone/libtars/tylibvirt"

	"github.com/digitalocean/go-libvirt"
	"github.com/digitalocean/go-libvirt/socket/dialers"
)

type VirtualMachineStatus string

const (
	VirtualMachineStatusDeleted      = VirtualMachineStatus("deleted")
	VirtualMachineStatusCreated      = VirtualMachineStatus("created")
	VirtualMachineStatusReady        = VirtualMachineStatus("ready")
	VirtualMachineStatusStarting     = VirtualMachineStatus("starting")
	VirtualMachineStatusImaging      = VirtualMachineStatus("imaging")
	VirtualMachineStatusRunning      = VirtualMachineStatus("running")
	VirtualMachineStatusOff          = VirtualMachineStatus("off")
	VirtualMachineStatusShuttingDown = VirtualMachineStatus("shutting_down")
)

// Versions - originally created for testing purposes, not actually something we would need.
// var libvirtVersion = *pflag.Bool("libvirt-version", false, "Returns result with version of libvirt populated")
// var virshVersion = *pflag.Bool("virsh-version", false, "Returns result with version of virsh populated")
// var tarsvirtVersion = *pflag.Bool("tarsvirt-version", false, "Returns result with version of tarsvirt populated")

// VirtualMachine commands
var virtualMachineState = pflag.Bool("virtual-machine-state", false, "Returns result with a current machine state")
var virtualMachineSoftReboot = pflag.Bool("virtual-machine-soft-reboot", false, "reboots a machine gracefully, as chosen by hypervisor. Returns result with a current machine state")
var virtualMachineHardReboot = pflag.Bool("virtual-machine-hard-reboot", false, "sends a VM into hard-reset mode. This is damaging to all ongoing file operations. Returns result with a current machine state")
var virtualMachineShutdown = pflag.Bool("virtual-machine-shutdown", false, "gracefully shuts down the VM. Returns result with a current machine state")
var virtualMachineShutoff = pflag.Bool("virtual-machine-shutoff", false, "kills running VM. Equivalent to pulling a plug out of a computer. Returns result with a current machine state")
var virtualMachineStart = pflag.Bool("virtual-machine-start", false, "starts up a VM. Returns result with a current machine state")
var virtualMachinePause = pflag.Bool("virtual-machine-pause", false, "stops the execution of the VM. CPU is not used, but memory is still occupied. Returns result with a current machine state")
var virtualMachineResume = pflag.Bool("virtual-machine-resume", false, "called after Pause, to resume the invocation of the VM. Returns result with a current machine state")
var virtualMachineCreate = pflag.Bool("virtual-machine-create", false, "creates a new machine. Requires --xml-template parameter. Returns result with a current machine state")
var virtualMachineDelete = pflag.Bool("virtual-machine-delete", false, "deletes an existing machine.")

var id = pflag.String("id", "", "id of the machine to work with")
var xmlTemplate = pflag.String("xml-template", "", "path to an xml template file that describes a machine. See qemu docs on xml templates.")

var v *libvirt.Libvirt

// TODO: cool things you can do with Domain, but do not know how to:
// virDomainInterfaceAddresses - gets data about an IP addresses on a current interfaces. Mega-tool.
// virDomainGetGuestInfo - full data about a config of the guest OS
// virDomainGetState - provides the data about an actual domain state. Why is it shutoff or hybernating. Requires copious amount of magic fuckery to find out the actual reason with multiplication and matrix transforms, but can be translated into a redable form.
func main() {

	pflag.Parse()

	virtinit()

	switch {
	case *virtualMachineState:
		VirtualMachineState(*id)
	case *virtualMachineSoftReboot:
		VirtualMachineSoftReboot(*id)
	case *virtualMachineHardReboot:
		VirtualMachineHardReboot(*id)
	case *virtualMachineShutdown:
		VirtualMachineShutdown(*id)
	case *virtualMachineShutoff:
		VirtualMachineShutoff(*id)
	case *virtualMachineStart:
		VirtualMachineStart(*id)
	case *virtualMachinePause:
		VirtualMachinePause(*id)
	case *virtualMachineResume:
		VirtualMachineResume(*id)
	case *virtualMachineCreate:
		VirtualMachineCreate(*xmlTemplate)
	case *virtualMachineDelete:
		VirtualMachineDelete(*id)
	}

}

// VirtualMachineState returns current state of a virtual machine.
func VirtualMachineState(id string) {
	var ret tylibvirt.VirtualMachineState

	d, err := v.DomainLookupByName(id)
	herr(err)

	state, maxmem, mem, ncpu, cputime, err := v.DomainGetInfo(d)
	herr(err)

	ret.CPUCount = ncpu
	ret.CPUTime = cputime
	// God only knows why they return memory in kilobytes.
	ret.MemoryBytes = mem * 1024
	ret.MaxMemoryBytes = maxmem * 1024
	temp := libvirt.DomainState(state)
	herr(err)

	switch temp {
	case libvirt.DomainNostate:
		ret.State = tylibvirt.VirtStatePending
	case libvirt.DomainRunning:
		ret.State = tylibvirt.VirtStateRunning
	case libvirt.DomainBlocked:
		ret.State = tylibvirt.VirtStateBlocked
	case libvirt.DomainPaused:
		ret.State = tylibvirt.VirtStatePaused
	case libvirt.DomainShutdown:
		ret.State = tylibvirt.VirtStateShutdown
	case libvirt.DomainShutoff:
		ret.State = tylibvirt.VirtStateShutoff
	case libvirt.DomainCrashed:
		ret.State = tylibvirt.VirtStateCrashed
	case libvirt.DomainPmsuspended:
		ret.State = tylibvirt.VirtStateHybernating
	}
	v.DomainGetState(d, 0)
	hret(ret)
}

// VirtualMachineCreate creates a new VM from an xml template file
func VirtualMachineCreate(xmlTemplate string) {

	xml, err := ioutil.ReadFile(xmlTemplate)
	herr(err)

	d, err := v.DomainDefineXML(string(xml))
	herr(err)

	hret(d)
}

// VirtualMachineDelete deletes a new VM from an xml template file
func VirtualMachineDelete(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)
	err = v.DomainUndefineFlags(d, libvirt.DomainUndefineKeepNvram)
	herr(err)
	hok(fmt.Sprintf("%v was deleted", id))
}

// VirtualMachineSoftReboot reboots a machine gracefully, as chosen by hypervisor.
func VirtualMachineSoftReboot(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainReboot(d, libvirt.DomainRebootDefault)
	herr(err)

	hok(fmt.Sprintf("%v was soft-rebooted successfully", id))
}

// VirtualMachineHardReboot sends a VM into hard-reset mode. This is damaging to all ongoing file operations.
func VirtualMachineHardReboot(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainReset(d, 0)
	herr(err)

	hok(fmt.Sprintf("%v was hard-rebooted successfully", id))
}

// VirtualMachineShutdown gracefully shuts down the VM.
func VirtualMachineShutdown(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainShutdown(d)
	herr(err)

	hok(fmt.Sprintf("%v was shutdown successfully", id))
}

// VirtualMachineShutoff kills running VM. Equivalent to pulling a plug out of a computer.
func VirtualMachineShutoff(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainDestroy(d)
	herr(err)

	hok(fmt.Sprintf("%v was shutoff successfully", id))
}

// VirtualMachineStart starts up a VM.
func VirtualMachineStart(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	//v.DomainRestore()
	//_, err = v.DomainCreateWithFlags(d, uint32(libvirt.DomainStartBypassCache))
	err = v.DomainCreate(d)

	herr(err)

	hok(fmt.Sprintf("%v was started", id))
}

// VirtualMachinePause stops the execution of the VM. CPU is not used, but memory is still occupied.
func VirtualMachinePause(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainSuspend(d)
	herr(err)

	hok(fmt.Sprintf("%v is paused", id))
}

// VirtualMachineResume can be called after Pause, to resume the invocation of the VM.
func VirtualMachineResume(id string) {
	d, err := v.DomainLookupByName(id)
	herr(err)

	err = v.DomainResume(d)
	herr(err)

	hok(fmt.Sprintf("%v was resumed", id))
}

func herr(e error) {
	if e != nil {
		fmt.Printf(`{"error":"%v"}`, strings.ReplaceAll(e.Error(), "\"", ""))
		os.Exit(1)
	}
}

func hok(message string) {
	fmt.Printf(`{"ok":"%v"}`, strings.ReplaceAll(message, "\"", ""))
	os.Exit(0)
}

func hret(i interface{}) {
	ret, err := json.Marshal(i)
	herr(err)
	fmt.Print(string(ret))
	os.Exit(0)
}

func virtinit() {
	v = libvirt.NewWithDialer(dialers.NewLocal(dialers.WithLocalTimeout(time.Second * 2)))
	if err := v.Connect(); err != nil {
		log.Fatalf("failed to connect: %v", err)
	}
}


Конкурс статей от RUVDS.COM. Три денежные номинации. Главный приз — 100 000 рублей.

Комментарии (17)


  1. aborouhin
    30.08.2022 13:02
    +1

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

    А чем родной CLI QEMU (qm) для этой задачи не угодил?


    1. Nurked Автор
      30.08.2022 16:17

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


      1. aborouhin
        30.08.2022 16:20
        +2

        Я, собственно, про virsh и не писал... я про родной qm.


        1. Nurked Автор
          30.08.2022 22:17

          Ах, это, да, извиняюсь, неправильно понял. На самом деле, момент заключался в том, что мне из своей программы надо управлять виртуалками. Для этого всегда есть два пути - первое, пытаться переточить утилиты CLI, второе - управлять напрямую через общедоступные API.

          Почему бы не попробовать API?


          1. aborouhin
            30.08.2022 22:33

            Ну так Вы же с помощью API в итоге написали собственную CLI утилиту. Которая, на первый взгляд, ничего такого, что не умеет qm, не делает (разве что шаблон хочет на входе в виде XML-файла, а в qm все параметры можно прямо в аргументах задать, что для скриптинга даже удобнее, или использовать сохранённые штатными средствами той же qm шаблоны). Вот я и поинтересовался.


  1. kale
    30.08.2022 13:12
    +1

    Казалось бы, у нас в руках есть virsh,
    но, как показала практика, он не такой удобный и полезный, как мне бы
    того хотелось. Virsh умеет управлять QEMU из консоли, но по факту — это
    просто текстовый интерфейс, который не очень хорошо работает в скриптах и
    внешних программах.

    А вот здесь можно подробнее, что с virsh не так?


    1. 13werwolf13
      30.08.2022 13:34
      +8

      1) xml
      2) кто блин придумал использовать для такой банальной вещи как остановка вм слово DESTROY

      но это придирки, работать не мешают

      а автору топика стоит погуглить что такое lxd (и да он не только lxc контейнерами может рулить но и виртуалками) он решает весь спектр задач по сопровождению vm как синглноды так и кластера, он ужобен, и у него есть api. в если ставить его НЕ из snap то он ещё и не подводит.


      1. Godless
        30.08.2022 13:59
        +3

        да, destroy бесит капитально...


      1. Busla
        30.08.2022 14:33
        +2

        1) xml

        и?

        вроде всё читаемо и элементарно извлекается:

        <domain type='qemu'>
          <name>QEmu-fedora-i686</name>
          <uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
          <memory>219200</memory>
          <currentMemory>219200</currentMemory>
          <vcpu>2</vcpu>
          <os>
            <type arch='i686' machine='pc'>hvm</type>
            <boot dev='cdrom'/>
          </os>
          <devices>
            <emulator>/usr/bin/qemu-system-x86_64</emulator>
            <disk type='file' device='cdrom'>
              <source file='/home/user/boot.iso'/>
              <target dev='hdc'/>
              <readonly/>
            </disk>
          </devices>
        <domain>


        1. 13werwolf13
          30.08.2022 14:39
          +4

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


        1. Nurked Автор
          30.08.2022 16:19
          +1

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


        1. 13werwolf13
          30.08.2022 16:45
          +4

          так на вскидку прикинул как подобный конфиг мог бы выглядеть в yaml формате (за ошибки и опечатки не ругайте, писал с телефона сидя в машине в ожидании)

          domain:
              name: fedora-test
              uuid: xxx-xxx-xxx
              memory: 4096
              vcpu:
                  sockets: 1
                  cores: 2
              os:
                  type:
                      arch: aarch64
                      machine: pc
                  boot:
                      dev: cdrom
              devices:
                  emulator: /usr/bin/qemu-system-x86_64
                  disk:
                      type: file
                      device: cdrom
                      file: /mnt/data/iso/darch.iso
                      target:
                          dev: sda
                      radonly: yes
          

          строк получилось больше, но читаемость намного лучше ИМХО.
          может быть я избалован этими вашими ансиблами, но по опыту могу сказать что недоконца проснувшись поздней ночью от экстренного алерта yaml читать всё же поприятнее чем xml.. может быть это всё придирки, так как и virsh с его xml ужасом я юзаю эвридей уже много лет без проблем, но хотелось бы что-то поудобнее.


          1. Nurked Автор
            30.08.2022 22:18

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


            1. mc2
              31.08.2022 02:32
              +1

              Только поддержка томла в питоне лишь с 3.11((


      1. Nurked Автор
        30.08.2022 16:20
        +2

        Вот про destroy - это вы просто за живое задели!


      1. Pinkbyte
        31.08.2022 15:58
        +3

        Да, за destroy хочется посмотреть в фасеточные глаза того инопланетянина, который это придумал.


        1. hrust_russia
          31.08.2022 19:12
          +2

          Я в первый раз про destroy несколько раз в разных источниках перечитывал, когда изучал virsh, мозг отказывался ломать машину,