Создайте виртуальную машину VMware с помощью govmomi

Эксплуатация и обслуживание

Ранее я написал две статьи, обе про API VMware:

Есть много причин для использования govmomi.Есть причины производительности.Go намного сильнее, чем Python.Есть личные причины, я предпочитаю go.

Конечно, будь то govmomi, pyvmomi или различные другие vmomi, все они являются инкапсуляциями собственного API VMware, и все они похожи в использовании.

Следует отметить, что виртуальная машина, созданная в этой статье, развертывается через библиотеку содержимого после размещения шаблона ovf в библиотеке содержимого, а не создается напрямую через шаблон. К тому же библиотека контента 6.7 поддерживает vm-template помимо шаблонного типа ovf.У меня не было времени изучить.Заинтересованные детские ботиночки могут изучить.

Хорошо, текст начинается.

Библиотека контента

Библиотека контента — это новая функция VMware 6.0. С ее помощью вы можете обмениваться iso-файлами, шаблонами виртуальных машин и т. д. между всферами.Когда у вас несколько всфер, это будет очень удобно в использовании. Чтобы использовать библиотеку контента, сначала нужно создать библиотеку контента (библиотеку), дать ей имя, а затем загрузить в библиотеку файлы, шаблоны и шаблоны.Каждый файл или шаблон называется элементом.

Когда вы создаете библиотеку контента в одной vsphere, другие vsphere подписываются на библиотеку контента, так что файлы в библиотеке контента будут синхронизированы со всеми библиотеками контента, которые на нее подписаны.Таким образом, можно гарантировать несколько библиотек контента vsphere. , Согласованность документа.

Здесь не будет продемонстрировано использование библиотеки контента, есть много онлайн-уроков, которые можно найти случайно. Это предполагает, что у вас уже есть библиотека содержимого с шаблоном ovf. Чтобы использовать библиотеку содержимого, мы должны сначала найти объект библиотеки содержимого, а затем использовать его для поиска объекта шаблона ovf.

Вы уже должны знать, как установить и войти в vsphere из предыдущей статьи, поэтому войдите прямо здесь:

const (
    ip = ""
    user = ""
    password = ""
)

u := &url.URL{
    Scheme: "https",
    Host:   ip,
    Path:   "/sdk",
}

ctx := context.Background()
u.User = url.UserPassword(user, password)
client, err := govmomi.NewClient(ctx, u, true)
if err != nil {
    fmt.Fprintf(os.Stderr, "Login to vsphere failed, %v", err)
    os.Exit(1)
}

Библиотеки, которые необходимо импортировать в этой статье:

import (
	"context"
	"fmt"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vapi/library"
	"github.com/vmware/govmomi/vapi/rest"
	"github.com/vmware/govmomi/vapi/vcenter"
	"net/url"
	"os"
)

Он не будет опубликован позже, и базовая среда разработки автоматически его завершит.

Клиент здесь может использоваться для управления всей vsphere, но он не может напрямую управлять библиотекой контента, мы должны сначала получить через него остальной клиент:

rc := rest.NewClient(client.Client)
if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
    fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
    os.Exit(1)
}

Это эквивалентно повторному входу в систему.Я не знаю, почему VMware так спроектировала, не плохо ли передать клиент напрямую? Когда у вас есть rc, вы можете манипулировать библиотекой содержимого:

func getLibraryItem(ctx context.Context, rc *rest.Client) (*library.Item, error) {
	const (
		libraryName = ""
		libraryItemName = ""
		libraryItemType = "ovf"
	)

	// 需要通过 rc 来获得 library.Manager 对象
	m := library.NewManager(rc)
	// 通过内容库的名称来查找内容库
	libraries, err := m.FindLibrary(ctx, library.Find{Name: libraryName})
	if err != nil {
		fmt.Printf("Find library by name %s failed, %v", libraryName, err)
		return nil, err
	}

	// 判断是否找到
	if len(libraries) == 0 {
		fmt.Printf("Library %s was not found", libraryName)
		return nil, fmt.Errorf("library %s was not found", libraryName)
	}

	if len(libraries) > 1 {
		fmt.Printf("There are multiple libraries with the name %s", libraryName)
		return nil, fmt.Errorf("there are multiple libraries with the name %s", libraryName)
	}

	// 在内容库中通过 ovf 模板的名称来找到 ovf 模板
	items, err := m.FindLibraryItems(ctx, library.FindItem{Name: libraryItemName,
		Type: libraryItemType, LibraryID: libraries[0]})

	if err != nil {
		fmt.Printf("Find library item by name %s failed", libraryItemName)
		return nil, fmt.Errorf("find library item by name %s failed", libraryItemName)
	}

	if len(items) == 0 {
		fmt.Printf("Library item %s was not found", libraryItemName)
		return nil, fmt.Errorf("library item %s was not found", libraryItemName)
	}

	if len(items) > 1 {
		fmt.Printf("There are multiple library items with the name %s", libraryItemName)
		return nil, fmt.Errorf("there are multiple library items with the name %s", libraryItemName)
	}

	item, err := m.GetLibraryItem(ctx, items[0])
	if err != nil {
		fmt.Printf("Get library item by %s failed, %v", items[0], err)
		return nil, err
	}

	return item, nil
}

Как видите, найти шаблон ovf довольно сложно. Если вы убедитесь, что ваша библиотека контента и ovf в ней не будут удалены и пересобраны, вы можете сначала найти шаблон ovf и отметить его id. Если вы хотите найти его в следующий раз, позвоните ему напрямуюm.GetLibraryItem()Вот и все. Таким образом, вам не нужно искать библиотеку, а затем находить элемент через библиотеку.

Тип ресурса

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

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

Итак, мы сначала находим эти ресурсы по имени, начиная с центра обработки данных.

Дата центр

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

// 通过这个 finder 你可以列出 VMware 中的所有资源
finder := find.NewFinder(client.Client)
dcs, err := finder.DatacenterList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list data center at vc %s, %v\n", ip, err)
    os.Exit(1)
}

for _, dc := range dcs {
    // 这个唯一名称是 vshpere 中的 id,大概类似于数据库中的自增 id,同一个 vsphere 中唯一,多个 vsphere 不唯一
    dcUniqName := dc.Reference().Value
    // 类型就是 DataCenter
    dcType := dc.Reference().Type
    // 数据中心的名称
    dcName := dc.Name()
    // 数据中心的路径,VMware 中的资源类似于 linux 的文件系统,从根开始,每个资源都有它唯一的路径
    // 如果你知道一个资源的 path,那么你就可以直接通过这个路径找到这个资源,后续会提到
    dcPath := dc.InventoryPath

    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", dcUniqName, dcName, dcPath, dcType)
}

Здесь перечислены все центры обработки данных.

кластер

В кластере много ресурсов, есть пулы ресурсов и esxi (то есть хост, который в VMware называется HostSystem). В среде с vsan или централизованным хранилищем можно разместить виртуальную машину прямо в пуле ресурсов кластера (не важно, если пул ресурсов не разделен, кластер по умолчанию является пулом ресурсов), если нет, то можно развертывать виртуальную машину только непосредственно на хост-компьютере.

Таким образом, вы либо помещаете виртуальную машину в пул ресурсов, либо ставите ее на хост. И оба этих ресурса принадлежат кластеру, поэтому сначала мы получаем кластер. Конечно, не имеет значения, если вы не получите кластер, вы можете напрямую получить пул ресурсов или хост. Я просто перечисляю здесь способы получения кластера, собственно все ресурсы добываются таким образом:

// 集群的名称为 ClusterComputeResource,不要搞错了
clusters, err := finder.ClusterComputeResourceList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list cluster at vc %s, %v", ip, err)
    os.Exit(1)
}
for _, cluster := range clusters {
    clusterUniqName := cluster.Reference().Value
    clusterType := cluster.Reference().Type
    clusterName := cluster.Name()
    clusterPath := cluster.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", clusterUniqName, clusterName, clusterPath, clusterType)
}

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

resourcePools, err := finder.ResourcePoolList(ctx, "*")
hosts, err := finder.HostSystemList(ctx, "*")

Конечно, вы также можете получить собственный пул ресурсов напрямую через кластер:

clusters[0].ResourcePool(ctx)

Этот способ обхода ресурсов на самом деле очень низкий, давайте оставим его в покое, давайте сначала пройдем процесс.

место хранения

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

datastores, err := finder.DatastoreList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list datastore at vc %s, %v", ip, err)
    os.Exit(1)
}
for _, datastore := range datastores {
    datastoreUniqName := datastore.Reference().Value
    datastoreType := datastore.Reference().Type
    datastoreName := datastore.Name()
    datastorePath := datastore.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", datastoreUniqName, datastoreName, datastorePath, datastoreType)
}

В VMware также есть тип ресурса datastoreCluster, но я его не изучал.

Интернет

Сеть относится к дата-центру, она немного сложнее, потому что имеет много типов:

  • Network
  • OpaqueNetwork
  • DistributedVirtualPortgroup
  • DistributedVirtualSwitch
  • VmwareDistributedVirtualSwitch

Конкретной разницы не знаю, наверное если вы используете распределенный свитч, то вам нужно выбрать только группу портов (DistributedVirtualPortgroup) (свич выбирать не нужно), иначе можно выбратьNetwork.

networks, err := finder.NetworkList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list network at vc %s, %v", ip, err)
    os.Exit(1)
}

for _, network := range networks {
    networkUniqName := network.Reference().Value
    // 这就是它的 type,需要注意区分
    networkType := network.Reference().Type
    // 没有 name,name 可以通过 path 来获取
    networkPath := network.GetInventoryPath()
    fmt.Printf("id => %s\npath => %s\ntype => %s\n", networkUniqName, networkPath, networkType)
}

папка

Папки принадлежат дата-центру и получаются так же:

folders, err := finder.FolderList(ctx, "*")
if err != nil {
    fmt.Fprintf(os.Stderr, "Failed to list folder at vc %s, %v", ip, err)
    os.Exit(1)
}

for _, folder := range folders {
    folderUniqName := folder.Reference().Value
    folderType := folder.Reference().Type
    folderName := folder.Name()
    folderPath := folder.InventoryPath
    fmt.Printf("id => %s\nname => %s\npath => %s\ntype => %s\n", folderUniqName, folderName, folderPath, folderType)
}

Развертывание виртуальных машин

Ресурсы уже доступны, но перед развертыванием нам нужно получить сеть и хранилище шаблона ovf, который будет использоваться позже. Чтобы получить их, возможно, когда вы будете развертывать виртуальные машины через него позже, замените их.

Способ получения очень прост:

rc := rest.NewClient(client.Client)
if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
    fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
    os.Exit(1)
}

// 先获取 ovf 模板,这个函数定义在前面
item, err := getLibraryItem(ctx, rc)
if err != nil {
    panic(err)
}

m := vcenter.NewManager(rc)
// 这里需要前面获取到的资源池和文件夹,当然宿主机和文件夹也行,就是要将 ResourcePoolID 换成 HostID
// 至于为什么这么做我也不清楚,只要可以获得我们需要的结果就行
fr := vcenter.FilterRequest{Target: vcenter.Target{
    ResourcePoolID: resources[0].Reference().Value,
    FolderID:       folders[0].Reference().Value,
},
}
r, err := m.FilterLibraryItem(ctx, item.ID, fr)
if err != nil {
    fmt.Fprintf(os.Stderr, "FilterLibraryItem error, %v\n", err)
    os.Exit(1)
}
// 模板中的网卡和磁盘可能有多个,我这里当做一个来处理,建议模板中只有一个网卡的磁盘,因为创建虚拟机的时候可以加
// 这两个 key 后面会用到
networkKey := r.Networks[0]
storageKey := r.StorageGroups[0]
fmt.Println(networkKey, storageKey)

Следующим шагом является развертывание:

deploy := vcenter.Deploy{
    DeploymentSpec: vcenter.DeploymentSpec{
        // 虚拟机名称
        Name:               "test",
        DefaultDatastoreID: datastores[0].Reference().Value,
        AcceptAllEULA:      true,
        NetworkMappings: []vcenter.NetworkMapping{{
            Key:   networkKey,
            Value: networks[0].Reference().Value,
        }},
        StorageMappings: []vcenter.StorageMapping{{
            Key: storageKey,
            Value: vcenter.StorageGroupMapping{
                Type:         "DATASTORE",
                DatastoreID:  datastores[0].Reference().Value,
                // 精简置备
                Provisioning: "thin",
            },
        }},
        // 精简置备
        StorageProvisioning: "thin",
    },
    Target: vcenter.Target{
        ResourcePoolID: resources[0].Reference().Value,
        FolderID:       folders[0].Reference().Value,
    },
}

ref, err := vcenter.NewManager(rc).DeployLibraryItem(ctx, item.ID, deploy)
if err != nil {
    fmt.Printf("Deploy vm from library failed, %v", err)
    return
}

f := find.NewFinder(client.Client)
obj, err := f.ObjectReference(ctx, *ref)
if err != nil {
    fmt.Fprintf(os.Stderr, "Find vm failed, %v\n", err)
    os.Exit(1)
}

// 这是虚拟机本尊,后面会用到
vm = obj.(*object.VirtualMachine)

Это запускает развертывание.

Обратите внимание, что все ресурсы, которые я выбираю здесь, являются первыми, просто для демонстрации, вам нужно выбрать правильный, когда вы их используете. Конечно, предыдущий способ получения всех ресурсов путем обхода очень низкий, и производительность очень низкая. Наш подход заключается в периодическом захвате всех ресурсов vsphere и сохранении их в MySQL, включая имя ресурса, тип (необходим только для сети), путь (это ключ) и идентификатор ресурса (то есть uniqName).

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

si := object.NewSearchIndex(client.Client)
inventoryPath, err := si.FindByInventoryPath(ctx, "/beijing/datastore/store1")
if inventoryPath == nil {
    fmt.Fprintf(os.Stderr, "Get datastore object failed, %v", err)
    return
}

// 如果是其他资源就换成其他资源就行
ds := object.NewDatastore(client.Client, inventoryPath.Reference())

полный код

package main

import (
	"context"
	"fmt"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/vapi/library"
	"github.com/vmware/govmomi/vapi/rest"
	"github.com/vmware/govmomi/vapi/vcenter"
	"net/url"
	"os"
)

func getLibraryItem(ctx context.Context, rc *rest.Client) (*library.Item, error) {
	const (
		libraryName     = "Librarysub_from_49.100"
		libraryItemName = "CentOS 7.5"
		libraryItemType = "ovf"
	)

	m := library.NewManager(rc)
	libraries, err := m.FindLibrary(ctx, library.Find{Name: libraryName})
	if err != nil {
		fmt.Printf("Find library by name %s failed, %v", libraryName, err)
		return nil, err
	}

	if len(libraries) == 0 {
		fmt.Printf("Library %s was not found", libraryName)
		return nil, fmt.Errorf("library %s was not found", libraryName)
	}

	if len(libraries) > 1 {
		fmt.Printf("There are multiple libraries with the name %s", libraryName)
		return nil, fmt.Errorf("there are multiple libraries with the name %s", libraryName)
	}

	items, err := m.FindLibraryItems(ctx, library.FindItem{Name: libraryItemName,
		Type: libraryItemType, LibraryID: libraries[0]})

	if err != nil {
		fmt.Printf("Find library item by name %s failed", libraryItemName)
		return nil, fmt.Errorf("find library item by name %s failed", libraryItemName)
	}

	if len(items) == 0 {
		fmt.Printf("Library item %s was not found", libraryItemName)
		return nil, fmt.Errorf("library item %s was not found", libraryItemName)
	}

	if len(items) > 1 {
		fmt.Printf("There are multiple library items with the name %s", libraryItemName)
		return nil, fmt.Errorf("there are multiple library items with the name %s", libraryItemName)
	}

	item, err := m.GetLibraryItem(ctx, items[0])
	if err != nil {
		fmt.Printf("Get library item by %s failed, %v", items[0], err)
		return nil, err
	}
	return item, nil
}

func main() {
	const (
		ip       = ""
		user     = ""
		password = ""
	)

	u := &url.URL{
		Scheme: "https",
		Host:   ip,
		Path:   "/sdk",
	}
	ctx := context.Background()
	u.User = url.UserPassword(user, password)
	client, err := govmomi.NewClient(ctx, u, true)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Login to vsphere failed, %v", err)
		os.Exit(1)
	}

	finder := find.NewFinder(client.Client)
	resourcePools, err := finder.ResourcePoolList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list resource pool at vc %s, %v", ip, err)
		os.Exit(1)
	}
	//hosts, err := finder.HostSystemList(ctx, "*")
	datastores, err := finder.DatastoreList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list datastore at vc %s, %v", ip, err)
		os.Exit(1)
	}

	networks, err := finder.NetworkList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list network at vc %s, %v", ip, err)
		os.Exit(1)
	}

	folders, err := finder.FolderList(ctx, "*")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to list folder at vc %s, %v", ip, err)
		os.Exit(1)
	}

	rc := rest.NewClient(client.Client)
	if err := rc.Login(ctx, url.UserPassword(user, password)); err != nil {
		fmt.Fprintf(os.Stderr, "rc.Login failed, %v", err)
		os.Exit(1)
	}

	item, err := getLibraryItem(ctx, rc)
	if err != nil {
		return
	}

	m := vcenter.NewManager(rc)
	fr := vcenter.FilterRequest{Target: vcenter.Target{
		ResourcePoolID: resourcePools[0].Reference().Value,
		FolderID:       folders[0].Reference().Value,
	},
	}
	r, err := m.FilterLibraryItem(ctx, item.ID, fr)
	if err != nil {
		fmt.Fprintf(os.Stderr, "FilterLibraryItem error, %v\n", err)
		os.Exit(1)
	}
	networkKey := r.Networks[0]
	storageKey := r.StorageGroups[0]

	deploy := vcenter.Deploy{
		DeploymentSpec: vcenter.DeploymentSpec{
			Name:               "test",
			DefaultDatastoreID: datastores[0].Reference().Value,
			AcceptAllEULA:      true,
			NetworkMappings: []vcenter.NetworkMapping{{
				Key:   networkKey,
				Value: networks[0].Reference().Value,
			}},
			StorageMappings: []vcenter.StorageMapping{{
				Key: storageKey,
				Value: vcenter.StorageGroupMapping{
					Type:        "DATASTORE",
					DatastoreID: datastores[0].Reference().Value,
					Provisioning: "thin",
				},
			}},
			StorageProvisioning: "thin",
		},
		Target: vcenter.Target{
			ResourcePoolID: resourcePools[0].Reference().Value,
			FolderID:       folders[0].Reference().Value,
		},
	}

	ref, err := vcenter.NewManager(rc).DeployLibraryItem(ctx, item.ID, deploy)
	if err != nil {
		fmt.Printf("Deploy vm from library failed, %v", err)
		return
	}

	f := find.NewFinder(client.Client)
	obj, err := f.ObjectReference(ctx, *ref)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Find vm failed, %v\n", err)
		os.Exit(1)
	}

	vm = obj.(*object.VirtualMachine)
}

установить IP

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

Обратите внимание, что настройка ip зависит от vmtools, поэтому вы должны убедиться, что vmtools уже существует в вашем шаблоне. Установка vm_tools в CentOS 6 может быть немного хлопотной, и вам придется установить диск и шаг за шагом следовать официальным требованиям. Но в CentOS 7 вам просто нужно установить open-vm-tools, а затем установить perl.

type ipAddr struct {
	ip       string
	netmask  string
	gateway  string
	hostname string
}

func (p *ipAddr) setIP(ctx context.Context, vm *object.VirtualMachine) error {
	cam := types.CustomizationAdapterMapping{
		Adapter: types.CustomizationIPSettings{
			Ip:         &types.CustomizationFixedIp{IpAddress: p.ip},
			SubnetMask: p.netmask,
			Gateway:    []string{p.gateway},
		},
	}

	customSpec := types.CustomizationSpec{
		NicSettingMap: []types.CustomizationAdapterMapping{cam},
		Identity:      &types.CustomizationLinuxPrep{HostName: &types.CustomizationFixedName{Name: p.hostname}},
	}

	task, err := vm.Customize(ctx, customSpec)
	if err != nil {
		return err
	}

	return task.Wait(ctx)
}

Настройте процессор и память

func setCPUAndMem(ctx context.Context, vm *object.VirtualMachine, cpuNum int32, mem int64) error {
	spec := types.VirtualMachineConfigSpec{
		NumCPUs:             cpuNum,
		NumCoresPerSocket:   cpuNum / 2,
		MemoryMB:            1024 * mem,
		CpuHotAddEnabled:    types.NewBool(true),
		MemoryHotAddEnabled: types.NewBool(true),
	}
	task, err := vm.Reconfigure(ctx, spec)
	if err != nil {
		return err
	}

	return task.Wait(ctx)
}

добавить диск

Вам нужно передать ему объект хранилища данных:

func addDisk(ctx context.Context, vm *object.VirtualMachine, diskCapacityKB int64, ds *types.ManagedObjectReference) error {
	devices, err := vm.Device(ctx)
	if err != nil {
		log.Errorf("Failed to get device list for vm %s, %v", vm.Name(), err)
		return err
	}

    // 这里要看你的磁盘类型,如果你有 nvme,就选择 nvme;否则就选 scsi。当然还有 ide,但是还有人用么
	controller, err := devices.FindDiskController("scsi")
	if err != nil {
		log.Errorf("Failed to find disk controller by name scsi, %v", err)
		return err
	}

	device := types.VirtualDisk{
		CapacityInKB: diskCapacityKB,
		VirtualDevice: types.VirtualDevice{
			Backing: &types.VirtualDiskFlatVer2BackingInfo{
				DiskMode:        string(types.VirtualDiskModePersistent),
				ThinProvisioned: types.NewBool(true),
				VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
					Datastore: ds,
				},
			},
		},
	}

	devices.AssignController(&device, controller)
	DeviceSpec := &types.VirtualDeviceConfigSpec{
		Operation:     types.VirtualDeviceConfigSpecOperationAdd,
		FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
		Device:        &device,
	}

	spec := types.VirtualMachineConfigSpec{}
	spec.DeviceChange = append(spec.DeviceChange, DeviceSpec)
	task, err := vm.Reconfigure(ctx, spec)
	if err != nil {
		log.Errorf("Failed to add disk for vm %s, %v", vm.Name(), err)
		return err
	}

	if err := task.Wait(ctx); err != nil {
		log.Errorf("Failed to add disk for vm %s, %v", vm.Name(), err)
		return err
	}
	return nil
}

ботинок

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

func powerOn(ctx context.Context, vm *object.VirtualMachine) error {
	task, err := vm.PowerOn(ctx)
	if err != nil {
		log.Errorf("Failed to power on %s", vm.Name())
		return err
	}

	return task.Wait(ctx)
}

У govmomi много функций. Я использую очень маленькую часть здесь. Если она не может удовлетворить все ваши потребности, вам может понадобиться посмотреть исходный код govc.

Хорошо, это конец этой статьи, спасибо за чтение.