以前我写了两篇文章,都是关于 VMware api 的:python
之因此使用 govmomi 缘由有不少,有性能的缘由,go 性能强出 Python 太多;有我的的缘由,本人比较喜欢 go;固然还有公司的缘由,公司的运维平台是 go 写的。linux
固然,不管是 govmomi 也好,pyvmomi 也好,仍是其余各类 vmomi 也好,都是 VMware 自身 api 的封装,使用起来都是大同小异,适合本身的就是最好的,不必纠结太多。git
须要说明的是,本文建立虚拟机是将 ovf 模板放入内容库后,经过内容库部署的,而非直接经过模板建立。另外,6.7 的内容库除了支持 ovf 这种模板类型以外,还支持 vm-template,我没有来得及研究,有兴趣的童鞋能够研究看看。github
OK,正文开始。golang
内容库是 VMware 6.0 新增的功能,使用它能够跨 vsphere 共享 iso 文件、虚拟机模板等,当你有多个 vsphere 时,使用起来会很便利。要使用内容库,你首先得建立一个内容库(library),给它一个名称,而后就能够上传文件啊模板啊到这个库中,每一个文件或者模板称为一个 item。数据库
当你在一个 vsphere 中建立内容库后,其余 vsphere 就订阅该内容库了,这样内容库中的文件就会同步到全部订阅它的内容库中,经过这种方式来保证多个 vsphere 内容库中文件的一致性。api
内容库的使用这里就不演示了,网上教程不少,随便就能找到。这里假设你已经有了一个内容库,而且里面已经有了一个 ovf 模板。要想使用内容库,咱们就必须先找到这个内容库对象,再经过它来找到其中的 ovf 模板这个对象。markdown
想必经过以前的文章你已经知道了怎么安装和登陆 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) } 复制代码
本文须要导入的库有这些:app
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" ) 复制代码
后面就不贴出来了,基本 IDE 都会自动补全。
这里的 client 就能够用来操做整个 vsphere 了,不过经过它没法直接操做内容库,咱们必须先要经过它来得到 rest client:
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 这么设计,直接经过 client 很差么?有了 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()
就能够了。这样就不用找库,再经过库来找 item 了。
模板有了,如今要作的就是肯定要将虚拟机部署在哪。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 或者集中存储的环境,你能够将虚拟机直接放在其中的集群资源池上(没有划分资源池也没关系,集群默认就是一个资源池);若是没有,那么就只能将虚拟机直接部署到宿主机上了。
所以你要么将虚拟机放到资源池中,要么放在宿主机上。而这种两种资源都属于集群,所以咱们首先获取集群。固然其实你不获取集群也没有关系,能够直接获取资源池或者 host。我这里只是将获取集群的方式列出来,其实全部资源都是这么获取的:
// 集群的名称为 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) 复制代码
这种遍历资源的方式其实很 low,这个先无论,先把流程走通再说。
储存也属于数据中心,既能够是 vsan,也能够是宿主机。这个是和上面的选择是一一对应的,若是你将虚拟机建在宿主机上,那么存储你就应该选择宿主机。
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 中还存在 datastoreCluste 这种资源类型,可是我没有研究。
网络属于数据中心,它会复杂一点,由于它有不少种类型:
具体有啥区别我不是很清楚,大概是若是你使用分布式交换机的话,你只须要选择端口组(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) 复制代码
这就开始部署了。
注意我这里全部的资源都是选择的第一个,只是为了演示,你在使用的时候,须要选择正确的才行。固然前面经过遍历的方式获取全部的资源方式显得很 low,并且性能不好。咱们的作法是定时去抓取全部 vsphere 的全部资源,将其存到 MySQL 中,包括资源的名称、类型(网络才须要)、路径(这是关键)、资源的 ID(也就是 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、更改 cpu 和内存的配置、增长磁盘等,注意这些操做必须在虚拟机关机的状况下才能进行,而刚部署完的虚拟机处于关机状态,咱们正好进行操做。
须要注意的是,配置 ip 依赖于 vmtools,所以你得先确保你的模板中已经存在 vmtools。CentOS 6 安装 vm_tools 可能有些麻烦,还得挂盘按照官方的要求一步步进行。可是在 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 源码了😂。
OK,本文到此结束,感谢阅读。