暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

vsphere golang sdk govmomi 使用指南

陈顺吉 2019-07-29
1202

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

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

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

推荐你们直接访问它的 https://github.com/vmware/govmomi
,上面有它的一些说明。同时也提到了安装的方式:

    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"

                  我只列出这四个我会用到的,更多的查看 https://github.com/vmware/govmomi/tree/master/govc

                  定义完成之后就可以使用了,比如查找当前的 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 进行判断是因为这个库的 bug
                        // 也不能说是 bug,我感觉是库的开发者有些瞎搞,挺随意的感觉
                        // 就算没找到,它也不会有 err,但是 reference 一定为 nil,我之前就被坑了
                        // 因此不要直接对 err 进行判断,后续的所有相关判断也都应该如此
                        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 的源码。


                                          文章转载自陈顺吉,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                          评论