I recently needed to do some parameter configuration, virtual switch/net creation, virtual machine creation, VIB installation, PCI direct access, virtual machine creation, etc. on ESXi host for work reasons. So I took the time to sort out some of the pitfalls I encountered while using govc.

govc

govc is a wrapper implementation of the official VMware govmomi library. It can be used to perform a number of operations on ESXi hosts or vCenter. For example, creating virtual machines, managing snapshots, etc. Basically, everything that can be done on ESXi or vCenter has an implementation in govmomi. Currently govc supports ESXi / vCenter versions 7.0, 6.7, 6.5 , 6.0 (5.x is too old, just give up), and also supports some versions of VMware Workstation.

Using govc to connect to an ESXi host or vCenter can be done by setting environment variables or command line arguments. It is recommended to use environment variables, as there is some security risk if the user name and password are output explicitly through the command line flag.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Options:
  -cert=                           Certificate [GOVC_CERTIFICATE]
  -dc=                             Datacenter [GOVC_DATACENTER]
  -debug=false                     Store debug logs [GOVC_DEBUG]
  -dump=false                      Enable Go output
  -host=                           Host system [GOVC_HOST]
  -host.dns=                       Find host by FQDN
  -host.ip=                        Find host by IP address
  -host.ipath=                     Find host by inventory path
  -host.uuid=                      Find host by UUID
  -json=false                      Enable JSON output
  -k=false                         Skip verification of server certificate [GOVC_INSECURE]
  -key=                            Private key [GOVC_PRIVATE_KEY]
  -persist-session=true            Persist session to disk [GOVC_PERSIST_SESSION]
  -tls-ca-certs=                   TLS CA certificates file [GOVC_TLS_CA_CERTS]
  -tls-known-hosts=                TLS known hosts file [GOVC_TLS_KNOWN_HOSTS]
  -trace=false                     Write SOAP/REST traffic to stderr
  -u=https://root@esxi.yoi.li/sdk  ESX or vCenter URL [GOVC_URL]
  -verbose=false                   Write request/response data to stderr
  -vim-namespace=vim25             Vim namespace [GOVC_VIM_NAMESPACE]
  -vim-version=7.0                 Vim version [GOVC_VIM_VERSION]
  -xml=false                       Enable XML output

Specify the URL of the ESXi host or vCenter with the GOVC_URL environment variable. The login username and password can be set in the GOVC_URL or separately with GOVC_USERNAME and GOVC_PASSWORD. If the https certificate is a self-signed domain or IP, you need to set the GOVC_INSECURE=true parameter to allow unsecured https connections.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ export GOVC_URL="https://root:password@esxi.k8s.li"
$ export GOVC_INSECURE=true
$ govc about
FullName:     VMware ESXi 7.0.2 build-17867351
Name:         VMware ESXi
Vendor:       VMware, Inc.
Version:      7.0.2
Build:        17867351
OS type:      vmnix-x86
API type:     HostAgent
API version:  7.0.2.0
Product ID:   embeddedEsx
UUID:

If the username and password have special characters like \ @ /, it is recommended to set GOVC_URL, GOVC_USERNAME and GOVC_PASSWORD respectively. This will avoid some strange problems with special characters in GOVC_URL.

Get host information

govc host.info

You can get basic information about an ESXi host by using the host.info self-command

  • Path: The resource path of the current host in the cluster
  • Manufacturer: Hardware device manufacturer
  • Logical CPUs: The number of logical CPUs and the base frequency of the CPUs
  • Processor type: The specific model of the CPU, since I have ES version E-2126G, I can’t show the specific model 🤣
  • CPU usage: the load of CPU usage
  • Memory: the amount of memory installed in the host
  • Memory usage: the load of memory usage
  • Boot time: Boot time
  • State: Connection status
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ govc host.info
Name:              hp-esxi.lan
  Path:            /ha-datacenter/host/hp-esxi.lan/hp-esxi.lan
  Manufacturer:    HPE
  Logical CPUs:    6 CPUs @ 3000MHz
  Processor type:  Genuine Intel(R) CPU 0000 @ 3.00GHz
  CPU usage:       3444 MHz (19.1%)
  Memory:          32613MB
  Memory usage:    26745 MB (82.0%)
  Boot time:       2021-12-05 06:11:53.42802 +0000 UTC
  State:           connected

If you add the -json parameter you will get a json output of at least 3w lines containing all the information about the ESXi host, and then you can use the jq command to filter out some of the parameters you need.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
╭─root@esxi-debian-devbox ~
╰─# govc host.info -json=true > host_info.json
╭─root@esxi-debian-devbox ~
╰─# wc host_info.json
  34522   73430 1188718 host_info.json
╭─root@esxi-debian-devbox ~
╰─# govc host.info -json | jq '.HostSystems[0].Summary.Hardware'
{
  "Vendor": "HPE",
  "Model": "ProLiant MicroServer Gen10 Plus",
  "Uuid": "30363150",
  "MemorySize": 34197471232,
  "CpuModel": "Genuine Intel(R) CPU 0000 @ 3.00GHz",
  "CpuMhz": 3000,
  "NumCpuPkgs": 1,
  "NumCpuCores": 6,
  "NumCpuThreads": 6,
  "NumNics": 4,
  "NumHBAs": 3
}

If you add the -dump argument, the output is in the format of a Golang structure, which also contains all the information about the ESXi host, and you can use it to locate the structure of a certain piece of information relatively easily. This is very convenient for developing other functions based on govmomi. In particular, when writing unit tests, you can dump some data from here to mock. Note that not all subcommands support output in json format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
mo.HostSystem{
    ManagedEntity: mo.ManagedEntity{
        ExtensibleManagedObject: mo.ExtensibleManagedObject{
            Self:           types.ManagedObjectReference{Type:"HostSystem", Value:"ha-host"},
            Value:          nil,
            AvailableField: nil,
        },
        Parent:        &types.ManagedObjectReference{Type:"ComputeResource", Value:"ha-compute-res"},
        CustomValue:   nil,
        OverallStatus: "green",
        ConfigStatus:  "yellow",
        ConfigIssue:   []types.BaseEvent{
            &types.RemoteTSMEnabledEvent{
                HostEvent: types.HostEvent{
                    Event: types.Event{
                        Key:             1,
                        ChainId:         0,
                        CreatedTime:     time.Now(),
                        UserName:        "",
                        Datacenter:      (*types.DatacenterEventArgument)(nil),
                        ComputeResource: (*types.ComputeResourceEventArgument)(nil),
                        Host:            &types.HostEventArgument{
                            EntityEventArgument: types.EntityEventArgument{
                                EventArgument: types.EventArgument{},
                                Name:          "hp-esxi.lan",
                            },
                            Host: types.ManagedObjectReference{Type:"HostSystem", Value:"ha-host"},
                        },
                        Vm:                   (*types.VmEventArgument)(nil),
                        Ds:                   (*types.DatastoreEventArgument)(nil),
                        Net:                  (*types.NetworkEventArgument)(nil),
                        Dvs:                  (*types.DvsEventArgument)(nil),
                        FullFormattedMessage: "SSH for the host hp-esxi.lan has been enabled",
                        ChangeTag:            "",
                    },
                },
            },
        },

When writing unit tests, I often use it to mock some special hardware device information, which is much easier than writing these structs by hand. For example, a drive named mpx.vmhba<Adapter>:C<Channel>:T<Target>:L<LUN> can be used to get the NAA number of the drive using the PlugStoreTopology structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func getDiskIDByHostPlugStoreTopology(hpst *types.HostPlugStoreTopology, diskName string) string {
    for _, path := range hpst.Path {
        if path.Name == diskName {
            s := strings.Split(path.Target, "-sas.")
            return s[len(s)-1]
        }
    }
    return ""
}

// 单元测试代码如下:
var plugStoreTopology = &types.HostPlugStoreTopology{
    Path: []types.HostPlugStoreTopologyPath{
        {
            Key:           "key-vim.host.PlugStoreTopology.Path-vmhba0:C0:T1:L0",
            Name:          "vmhba0:C0:T1:L0",
            ChannelNumber: 0,
            TargetNumber:  1,
            LunNumber:     0,
            Adapter:       "key-vim.host.PlugStoreTopology.Adapter-vmhba0",
            Target:        "key-vim.host.PlugStoreTopology.Target-sas.500056b3d93828c0",
            Device:        "key-vim.host.PlugStoreTopology.Device-020000000055cd2e414dc39d4e494e54454c20",
        },
    },
}


func TestGetDiskIDByHostPlugStoreTopology(t *testing.T) {
    tests := []struct {
        name string
        want string
    }{
        {
            name: "vmhba0:C0:T1:L0",
            want: "500056b3d93828c0",
        },
        {
            name: "vmhba0:C0:T2:L0",
            want: "",
        },
        {
            name: "",
            want: "",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := getDiskIDByHostPlugStoreTopology(plugStoreTopology, tt.name); got != tt.want {
                t.Errorf("getDiskIDByHostPlugStoreTopology() = %v, want %v", got, tt.want)
            }
        })
    }
}

For example, an NVMe drive can get its serial number from the NvmeTopology data object.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
func getNVMeIDByHostNvmeTopology(hnt *types.HostNvmeTopology, diskName string) string {
    for _, adapter := range hnt.Adapter {
        for _, controller := range adapter.ConnectedController {
            for _, ns := range controller.AttachedNamespace {
                if ns.Name == diskName {
                    return strings.TrimSpace(controller.SerialNumber)
                }
            }
        }
    }
    return ""
}

// 单元测试代码如下:
var nvmeTopology = &types.HostNvmeTopology{
    Adapter: []types.HostNvmeTopologyInterface{
        {
            Key:     "key-vim.host.NvmeTopology.Interface-vmhba0",
            Adapter: "key-vim.host.PcieHba-vmhba0",
            ConnectedController: []types.HostNvmeController{
                {
                    Key:                     "key-vim.host.NvmeController-256",
                    ControllerNumber:        256,
                    Subnqn:                  "nqn.2021-06.com.intel:PHAB123502CU1P9SGN  ",
                    Name:                    "nqn.2021-06.com.intel:PHAB123502CU1P9SGN",
                    AssociatedAdapter:       "key-vim.host.PcieHba-vmhba0",
                    TransportType:           "pcie",
                    FusedOperationSupported: false,
                    NumberOfQueues:          2,
                    QueueSize:               1024,
                    AttachedNamespace: []types.HostNvmeNamespace{
                        {
                            Key:              "key-vim.host.NvmeNamespace-t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB________00035CB406E4D25C@256",
                            Name:             "t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB________00035CB406E4D25C",
                            Id:               1,
                            BlockSize:        512,
                            CapacityInBlocks: 3125627568,
                        },
                    },
                    VendorId:        "0x8086",
                    Model:           "Dell Ent NVMe P5600 MU U.2 1.6TB        ",
                    SerialNumber:    "PHAB123502CU1P9SGN  ",
                    FirmwareVersion: "1.0.0   PCIe",
                },
            },
        },
        {
            Key:     "key-vim.host.NvmeTopology.Interface-vmhba1",
            Adapter: "key-vim.host.PcieHba-vmhba1",
            ConnectedController: []types.HostNvmeController{
                {
                    Key:                     "key-vim.host.NvmeController-257",
                    ControllerNumber:        257,
                    Subnqn:                  "nqn.2021-06.com.intel:PHAB123602H81P9SGN  ",
                    Name:                    "nqn.2021-06.com.intel:PHAB123602H81P9SGN",
                    AssociatedAdapter:       "key-vim.host.PcieHba-vmhba1",
                    TransportType:           "pcie",
                    FusedOperationSupported: false,
                    NumberOfQueues:          2,
                    QueueSize:               1024,
                    AttachedNamespace: []types.HostNvmeNamespace{
                        {
                            Key:              "key-vim.host.NvmeNamespace-t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB________00035CEE23E4D25C@257",
                            Name:             "t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB________00035CEE23E4D25C",
                            Id:               1,
                            BlockSize:        512,
                            CapacityInBlocks: 3125627568,
                        },
                    },
                    VendorId:        "0x8086",
                    Model:           "Dell Ent NVMe P5600 MU U.2 1.6TB        ",
                    SerialNumber:    "PHAB123602H81P9SGN  ",
                    FirmwareVersion: "1.0.0   PCIe",
                },
            },
        },
    },
}

func TestGetNVMeIDByHostNvmeTopology(t *testing.T) {
    tests := []struct {
        name string
        want string
    }{
        {
            name: "t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB________00035CEE23E4D25C",
            want: "PHAB123602H81P9SGN",
        },
        {
            name: "t10.NVMe____Dell_Ent_NVMe_P5600_MU_U.2_1.6TB",
            want: "",
        },
        {
            name: "",
            want: "",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := getNVMeIDByHostNvmeTopology(nvmeTopology, tt.name); got != tt.want {
                t.Errorf("getNVMeIDByHostNvmeTopology() = %v, want %v", got, tt.want)
            }
        })
    }
}

Configure ESXi host parameters

The host.options.ls allows you to list all configuration options for the current ESXi host.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ govc host.option.ls
Annotations.WelcomeMessage:
BufferCache.FlushInterval:                          30000
BufferCache.HardMaxDirty:                           95
BufferCache.PerFileHardMaxDirty:                    50
BufferCache.SoftMaxDirty:                           15
CBRC.DCacheMemReserved:                             400
CBRC.Enable:                                        false
COW.COWMaxHeapSizeMB:                               192
COW.COWMaxREPageCacheszMB:                          256
COW.COWMinREPageCacheszMB:                          0
COW.COWREPageCacheEviction:                         1
Config.Defaults.host.TAAworkaround:                 true
Config.Defaults.monitor.if_pschange_mc_workaround:  false
Config.Defaults.security.host.ruissl:               true
Config.Defaults.vGPU.consolidation:                 false
Config.Etc.issue:
Config.Etc.motd:                                    The time and date of this login have been sent to the system logs.

ESXi host parameters can be set via host.option.set, for example if you want to configure the NFS storage heartbeat timeout you can do so as follows.

1
$ govc host.option.set NFS.HeartbeatTimeout 30

Enabling the ssh service

The host.service allows you to perform related operations on the services on the ESXi host.

1
2
3
4
5
6
7
8
9
$ govc host.service
Where ACTION is one of: start, stop, restart, status, enable, disable

# 启动 ssh 服务
$ govc host.service start TSM-SSH
# 将 ssh 服务设置为开机自启
$ govc host.service enable TSM-SSH
# 查看 ssh 服务的状态
$ govc host.service status TSM-SSH

Create a virtual machine

1
2
3
4
5
6
7
8
$ VM_NAME="centos-test"
$ govc vm.create -ds='datastore*' -net='VM Network' -net.adapter=vmxnet3 -disk 1G -on=false ${VM_NAME}
$ govc vm.change -cpu.reservation=%d -memory-pin=true -vm ${VM_NAME}
$ govc vm.change -g centos7_64Guest -c %d -m 16384 -latency high -vm ${VM_NAME}
$ govc device.cdrom.add -vm ${VM_NAME}
$ govc device.cdrom.insert -vm ${VM_NAME} -device cdrom-3000
$ govc device.connect -vm ${VM_NAME} cdrom-3000
$ govc vm.power -on=true ${VM_NAME}