以前我寫了兩篇文章,都是關於 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,本文到此結束,感謝閱讀。