上一篇文章提到了如何使用 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 不仅提供了这个第三方库,同时基于这个库还写了 govc
,vcsim
和 toolbox
这三个命令行工具,你完全可以使用它们在命令行管理你的 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 的源码。