vsphere golang sdk govmomi 使用指南

6,256 阅读9分钟

上一篇文章提到了如何使用 python 和 vsphere 进行交互,我只能说我知道 govmomi 太晚了,不然怎么着都不会使用 python 的,毕竟 python 性能太差,并发用起来太蹩脚。在知道 govmomi 之后就有些蠢蠢欲动,于是抽空研究了下,并记录了下它的一些简单用法。

govmomi 和 pyvmomi 虽然都是基于 VMware 的 api 实现的,但是由于语言的不同,它们使用起来还是区别很大的。除了性能上的优势之外,govmomi 不仅可以使用所有操纵虚拟机的功能,还支持从内容库直接部署虚拟机,这就比我之前文章使用 vsphere-automation-sdk-python + pyvmomi 这种蹩脚的用法要强很多了。不过 govmomi 既然支持内容库,没理由 pyvmomi 不支持,莫非是我孤陋寡闻了?不过这些都不是重点,重点是 govmomi。

govmomi 其实用起来还好,但是坑一定是存在的,这个接下来会提到。我们先安装它。

推荐你们直接访问它的 github,上面有它的一些说明。上面也提到了安装的方式:

go get -u github.com/vmware/govmomi

很有意思的是,govmomi 不仅提供了这个第三方库,同时基于这个库还写了 govcvcsimtoolbox 这三个命令行工具,你完全可以使用它们在命令行管理你的 vsphere。在你安装之后,这三个工具的源码也都被你下载下来了。

其中 govc 的源码是我们需要密切关注的,因为 govmomi 本身可以说没有任何的示例告诉你如何使用,但是你需要的功能 govc 都提供了。所以你要实现什么功能就看 govc 对应的源码就行了。最重要的是,govc 源码的文件非常清晰明了,你要什么功能看对应的文件就行,这个接下来会讲到。

ok,安装安装后,我们首先需要登录 vsphere。

登录

govmomi 的登录很有特色,是我第一次见到的登录类型,只能说不够直接,有些遮遮掩掩的感觉,不是我的菜。

登录的方式有两种,第一种是通过环境变量,它需要如下环境变量:

  • GOVMOMI_URL:vsphere ip;
  • GOVMOMI_USERNAME:用户名;
  • GOVMOMI_PASSWORD:密码;
  • GOVMOMI_INSECURE:是否进行证书校验。

貌似还有其他环境变量,但是这四个是我们登录需要的。这种登录方式更多的是测试性质的,无法正式使用。因为使用这种方式意味着你需要将密码写入环境变量,并且在你需要登录多个 vsphere 时就没有办法了。当然,你可以尝试在代码内对环境变量进行修改,试他一把,但我推荐使用第二种方式。

第二种方式是通过 url 的方式,将用户名和密码都写入到 url 中。大家知道,http url 的定义是这样的:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

因此我们是完全可以将用户名和密码写入其中的。你可能担心密码中包含 :/ 这样的特殊字符,导致登录失败。其实没关系的,因为 govmomi 会将 url 解析为 url.URL,所以我们只要自己构建一个 url.URL 就行。

url 的最终格式如下:

https://user:password@IP/sdk

因此,我们可以这么做:

u := &url.URL{
    Scheme: "https",
    Host:   ip,
    Path:   "/sdk",
}
u.User = url.UserPassword(user, password)

ok,url.URL 就构建完毕了,你可以使用 fmt.Println(u) 直接将 url 打印出来,只不过密码很有可能会被编码。然后使用它登录:

ctx := context.Background()
client, err := govmomi.NewClient(p.ctx, u, true)
if err != nil {
    panic(err)
}

基本上所有 govmomi 操作都会接收 context 作为上下文,便于操作取消,因此首先需要创建一个 context 对象。

这里的 true 就是不进行证书验证,对应上面的 GOVMOMI_INSECURE 环境变量。client 就是登录后的对象,这也是我们下面所有操作的基础。当你有多个 vsphere 时,创建不同的 url.URL 就可以登录不同的 vsphere。

完整写法:

package main

import (
    "context"
    "fmt"
	"github.com/vmware/govmomi"
	"net/url"
)

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

func main() {
	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 {
		panic(err)
	}
	fmt.Println(client)
}

使用 govc

在使用之前,我们先使用 govc,先要了解它的功能。因为 govc 基本上实现了 govmomi 的所有功能,所以我们完全可以将 govc 的源码作为参考,当我们需要实现相应的功能时,就可以直接看 govc 的源码。

首先编译 govc:

go build github.com/vmware/govmomi/govc/

要通过定义环境变量来使用它,注意它的环境变量和上面的 govmomi 的环境变量的名称并不一样(意思一样),不要搞混了。我们首先定义环境变量:

export GOVC_URL=""
export GOVC_USERNAME=""
export GOVC_PASSWORD=""
export GOVC_INSECURE="true"

我只列出这四个我会用到的,更多的点击这里查看。

定义完成之后就可以使用了,比如查找当前的 vsphere 上有哪些文件夹:

./govc find / -type f

至于它有哪些功能呢?可以使用 ./govc -h 列出。可以看到它的功能非常多,你想要查看对应功能的源码只需要去 govc 源码目录查找对应的文件就行,比如我要查看 host.shutdown 的源码,只需要查看 $GOPATH/src/github.com/vmware/govmomi/govc/host/shutdown.go 文件就行。

查找虚拟机

登录成功之后,我们一般做的就是查找虚拟机了,虚拟机的查找有多种方式,可以通过名称查、ip 查、文件夹路径查、uuid 查等。不同的查找方式效果不同,性能也不同。虚拟机有两种表现形式,不同方式查找到的虚拟机形式也不同,不过它们可以相互进行转换。

通过名称查找

首先进行最 low 的查找方式,这种就是遍历所有虚拟机,然后判断主机名是否相同。

// 这里的 c 就是上面登录后 client 的 Client 属性
func findVMByName(ctx context.Context, c *vim25.Client, vmName string) {
	m := view.NewManager(c)

	v, err := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
	if err != nil {
		panic(err)
	}

	defer v.Destroy(ctx)

	// Retrieve summary property for all machines
	// Reference: http://pubs.vmware.com/vsphere-60/topic/com.vmware.wssdk.apiref.doc/vim.VirtualMachine.html
	var vms []mo.VirtualMachine
	err = v.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary"}, &vms)
	if err != nil {
		panic(err)
	}

	// Print summary per vm (see also: govc/vm/info.go)

	for _, vm := range vms {
		// 判断虚拟机名称是否相同,相同的话,vm 就是查找到主机
		if vm.Summary.Config.Name == vmName {
			fmt.Printf("%s: %s\n", vm.Summary.Config.Name, vm.Summary.Config.GuestFullName)
			break
		}
	}
}

这样虚拟机就找到了,可以看到找到的虚拟机是 mo.VirtualMachine 类型,后续会提到如何对虚拟机进行修改。

通过 ip 查找

使用 ip 查找需要虚拟机安装了 vmtools,且可以在 web 界面上能够看到它的 ip,因此虚拟机必须处于开机状态。

func findVMByIP(ctx context.Context, c *vim25.Client, ip string) {
	searchIndex := object.NewSearchIndex(c)
	// nil 是数据中心,你如果要指定的话,需要构造数据中心的结构体,这里就不指定了
	// ip 是你虚拟机的 ip
	// true 的意思我没搞懂
	reference, err := searchIndex.FindByIp(ctx, nil, ip, true)
	// 之所以只对 reference 进行判断而非对 err 是因为没有找到不算是 error
	// 也就是说 err 为 nil 并不代表就找到了,但是没找到 reference 一定为 nil
	if reference == nil {
		panic("vm not found")
	}

	// 这类的查找的对象都是 object.Reference,你需要通过对应的方法将其转换为相应的对象
	// 比如虚拟机、文件夹、模板等~
	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}

这次找到的虚拟机类型是 *object.VirtualMachine,而不是上面的 mo.VirtualMachine,不过它们是可以相互转换的。

通过文件夹路径查找

这是根据虚拟机所处的文件夹路径来查找,前提是你知道你的文件夹的路径。这个路径并不是你在 web 界面上看到的,你看到的只是路径的一部分。那么如何才能知道具体的文件夹路径是啥呢?你可以使用 govc 进行查看。

如果你的虚拟机在数据中心下面,没有放入任何文件夹,那么它的文件夹路径就是 /数据中心/vm

func findVMByPath(ctx context.Context, c *vim25.Client, folderPath, vmName string) {
	searchIndex := object.NewSearchIndex(c)
	// 通过 path.Join 将文件夹路径和虚拟机名称拼接成完整的虚拟机路径
	reference, err := searchIndex.FindByInventoryPath(ctx, path.Join(folderPath, vmName))
	if reference == nil {
		panic("vm not found")
	}

	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}

虚拟机类型和上面相同,因为查找的方式类似。

通过 uuid 查找

uuid 查找是唯一的,查找方式和上面几乎一样,除非你知道虚拟机的 uuid,否则也无法使用。

func findVMByUuid(ctx context.Context, c *vim25.Client, uuid string) {
	searchIndex := object.NewSearchIndex(c)
	// 第一个 nil 指的是数据中心
	// true 和之前的 true 意义一样,只不过我不知道是什么意思
	// 最后一个 nil 我也不知道是啥意思
	reference, err := searchIndex.FindByUuid(ctx, nil, uuid, true, nil)
	if reference == nil {
		panic("vm not found")
	}

	vm := object.NewVirtualMachine(c, reference.Reference())
	fmt.Println(vm)
}

虚拟机类型和上面相同。当然虚拟机的查找方式肯定不止这四种,这里只列出了常用的,有兴趣的可以了解其他的用法。

常用的功能

这里只是简单的列出了一些常用的功能,有其他需求的可以直接查看 govc 的源码,源码都还挺容易懂的。

两种虚拟机类型转换

第一种查找到的虚拟机类型和后面三种的不一样,不同的虚拟机类型有不同的属性和方法,因此它们存在转换的需求。

转换的方式也很简单,首先是 mo.VirtualMachine 转换为 *object.VirtualMachine

func vmConv(c *vim25.Client, mvm mo.VirtualMachine) {
	vm := object.NewVirtualMachine(c, mvm.Reference())
	fmt.Println(vm)
}

使用 mo.VirtualMachine 大多数是需要获得一些虚拟机相关的属性,以及配置参数。因此你想要将 *object.VirtualMachine 转化为 mo.VirtualMachine 可以这么做:

func x(ctx context.Context, vm *object.VirtualMachine, c *vim25.Client) {
	var mvm mo.VirtualMachine

	pc := property.DefaultCollector(c)
	// 如果想要全部属性,可以传一个空的字串切片
	err := pc.RetrieveOne(ctx, vm.Reference(), []string{"runtime.host", "config.uuid"}, &mvm)
}

也能这么做:

func x(ctx context.Context, vm *object.VirtualMachine, c *vim25.Client) {
	var o mo.VirtualMachine
	if err := vm.Properties(ctx, vm.Reference(), []string{"config.uuid"}, &o); err != nil {
		panic(err)
	}
}

关机

关机很简单,*object.VirtualMachine 对象本身就提供了关机方法。

func vmShutdown(ctx context.Context, vm *object.VirtualMachine) {
	// 第一个返回值是 task,我认为没必要处理,如果你要处理的话可以接收后处理
	_, err := vm.PowerOff(ctx)
	if err != nil {
		panic(err)
	}
}

删除

删除也简单,*object.VirtualMachine 对象本身也提供了关机方法。

func vmDelete(ctx context.Context, vm *object.VirtualMachine) {
	// task 可以处理,也可以不处理
	task, err := vm.Destroy(ctx)
	if err != nil {
		panic(err)
	}
	if task.Wait(ctx) != nil {
		panic(err)
	}
}

断开虚拟机网卡

这方面比 pyvmomi 更简单。

func disconnectNic(ctx context.Context, vm *object.VirtualMachine) {
	devidelst, err := vm.Device(ctx)
	if err != nil {
		panic("获取虚拟机的设备列表失败," + err.Error())
	}
	for _, device := range devidelst {
		switch device.(type) {
		case *types.VirtualVmxnet3:
			if devidelst.Disconnect(device) != nil {
				panic("断开网卡连接失败," + err.Error())
			}
			if vm.EditDevice(p.ctx, device) != nil {
				panic("断开网卡连接失败," + err.Error())
			}
		}
	}
}

更改虚拟机所属文件夹

将虚拟机移动到另一个文件夹。

// 先通过目标文件夹来找到其文件夹对象,然后将虚拟机移动到这个对象中
func vmMove(ctx context.Context, destDir string, c *vim25.Client,
		vm *object.VirtualMachine, searchIndex *object.SearchIndex) {
	reference, _ := searchIndex.FindByInventoryPath(ctx, destDir)
	if reference == nil {
		panic("目标目录 " + destDir + " 不存在")
	}

	folder := object.NewFolder(c, reference.Reference())
	task, err := folder.MoveInto(ctx, []types.ManagedObjectReference{vm.Reference()})
	if err != nil {
		panic(err)
	}
	if err := task.Wait(ctx); err != nil {
		panic(err)
	}
}

就写这么多了,大家有需要的话,可以直接看 govc 的源码。