📜 ⬆️ ⬇️

Experience in building Infrastructure-as-Code in VMware. Part 1.1: Dynamic Inventory

image

Greetings, dear reader! In the previous series, I talked about experience, the son of difficult mistakes, and promised to continue with the modification of the Powershell script.

Unfortunately, the project in this area is frozen until next year, and in order not to torment you with expectations, I decided to share my “research” in the field of dynamic VMware inventory, which is an intermediate phase for our project. If your virtualok park changes frequently, then I strongly recommend that you read the material.

The first thing that I did when I found this comment was that I ran to our infrastructure workers with the question “Do I need to cut it?”. The guys explained that Vcloud is:


Charged with motivation (“no one will help us except us”), I got into the wilds of studying the dynamics. Do not misunderstand me, dear reader, static inventory in Ansible is a very convenient thing, and you can also “write dynamically” armed with echo and sed, but why?
')

We are looking for the right script


Currently, the contributor implements the following dynamic implementations on Github: one and the other .

On the Internet, there is still such an option. I dropped it immediately - he stupidly rakes out the list of all the machines (without entrails), it works for a long time, the login and password for connecting to the vari should be hardcoded. In other words - great to play, but no more.

vmware.py also disappeared, because it works for a long time even with a cache (on my typewriter 30 minutes without a cache, and 8 with a cache), and I don’t like the “depth” of immersion in the inside of the virtual machine either.

So my choice fell vmware_inventory.py - it is very flexible, it works with a cache for 2 seconds and gives me the opportunity to group by machine parameters.

First of all, a large and long INI file is a configuration for working with variance.

vmware_inventory.ini
# Ansible VMware external inventory script settings [vmware] # The resolvable hostname or ip address of the vsphere server=virtualcenter.example.com # The port for the vsphere API #port=443 # The username with access to the vsphere API username=example\vmware_reader # The password for the vsphere API password=supersecurepassword # Verify the server's SSL certificate validate_certs = False # Specify the number of seconds to use the inventory cache before it is # considered stale. If not defined, defaults to 0 seconds. cache_max_age = 86400 # Specify the directory used for storing the inventory cache. If not defined, # caching will be disabled. cache_path = ~/.cache/ansible # Max object level refers to the level of recursion the script will delve into # the objects returned from pyvomi to find serializable facts. The default # level of 0 is sufficient for most tasks and will be the most performant. # Beware that the recursion can exceed python's limit (causing traceback), # cause sluggish script performance and return huge blobs of facts. # If you do not know what you are doing, leave this set to 1. max_object_level=2 # Lower the keynames for facts to make addressing them easier. lower_var_keys=True # Host alias for objects in the inventory. VMWare allows duplicate VM names # so they can not be considered unique. Use this setting to alter the alias # returned for the hosts. Any atributes for the guest can be used to build # this alias. The default combines the config name and the config uuid and # expects that the ansible_host will be set by the host_pattern. alias_pattern={{ config.name }} # Host pattern is the value set for ansible_host and ansible_ssh_host, which # needs to be a hostname or ipaddress the ansible controlhost can reach. #host_pattern={{ guest.ipaddress }} # Host filters are a comma separated list of jinja patterns to remove # non-matching hosts from the final result. # EXAMPLES: # host_filters={{ config.guestid == 'rhel7_64Guest' }} # host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }} # host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }} # The default is only gueststate of 'running' host_filters={{ guest.gueststate == "running" }} # Groupby patterns enable the user to create groups via any possible jinja # expression. The resulting value will the groupname and the host will be added # to that group. Be careful to not make expressions that simply return True/False # because those values will become the literal group name. The patterns can be # comma delimited to create as many groups as necessary groupby_patterns={{ guest.guestid }},{{ 'templates' if config.template else 'guests'}} # The script attempts to recurse into virtualmachine objects and serialize # all available data. The serialization is comprehensive but slow. If the # vcenter environment is large and the desired properties are known, create # a 'properties' section in this config and make an arbitrary list of # key=value settings where the value is a path to a specific property. If # If this feature is enabled, be sure to fetch every property that is used # in the jinja expressions defined above. For performance tuning, reduce # the number of properties to the smallest amount possible and limit the # use of properties that are not direct attributes of vim.VirtualMachine #[properties] prop01=name prop04=config.instanceUuid prop05=config.hardware.numCPU prop06=config.template prop07=config.name prop08=guest.hostName prop09=guest.ipAddress prop11=guest.guestState prop12=runtime.maxMemoryUsage 


First of all, I recommend having a separate user to work with Vary. In fact, we need a regular user who can read these machines, resources, folders and datastores. He does not have to have any rights to enable / disable, create / delete virtual machines. Least privilege principle!

I further emphasize:

  1. Cache lifetime (cache_max_age) in seconds. I set it for a day, but every time the machine is created, the cache will be updated (running the script with the option --refresh-cache). Also, the update will be once a day at night (as well)
  2. Recursion depth (max_object_level). Two is the best option to get more metadata about the machine. More metadata - more games with groupings and filtering. But - more json and more time the script.
  3. Typewriter pattern (alias_pattern) - the default is {{config.name + '_' + config.uuid}}, which makes the name of the machine unreadable, so I removed the config.uuid
  4. Grouping patterns (groupby_patterns) - this is the beauty in the script. Allows you to group machines based on metadata. You can group around the word "absolutely." Resource, custom pitch in notes, grid, location, even the name of the datastor. By default it is grouped according to the guest lists (read - according to the installed OS)

I will not describe any obvious things like certificate validation, but if I got into trouble with the script, write in the comments.

We try to run.

 $ time ./vmware_inventory.py > fact_from_vm_py.json real 27m59.970s user 8m33.334s sys 0m7.841s 

I repeat, reader, be careful of updating the cache, and then playbooks will be slower than a bug!

With the cache, the entire inventory is loaded in a second, and most of the time the script is running is taken by the scan. I will not give the whole script exhaust, I will give one single host:

There is a lot of data
 "edin_host": { "resourcepool": { "_moId": "resgroup-14510", "name": "example-BI" }, "customvalue": [], "permission": [], "storage": { "timestamp": { "hour": 7, "min": {}, "max": {}, "month": 12, "second": 20, "microsecond": 859999, "year": 2016, "tzinfo": {}, "resolution": {}, "day": 22, "minute": 45 }, "perdatastoreusage": [] }, "configissue": [], "parentvapp": null, "tag": [], "recenttask": [], "resourceconfig": { "changeversion": null, "lastmodified": null, "memoryallocation": { "overheadlimit": 63, "reservation": 0, "limit": -1, "shares": {}, "expandablereservation": false }, "cpuallocation": { "overheadlimit": null, "reservation": 0, "limit": -1, "shares": {}, "expandablereservation": false }, "entity": { "resourcepool": { "_moId": "resgroup-14510", "name": "example-BI" }, "alarmactionsenabled": true, "configissue": [], "tag": [], "resourceconfig": {}, "datastore": [], "triggeredalarmstate": [], "layout": {}, "guest": {}, "effectiverole": [], "storage": {}, "layoutex": {}, "config": {}, "customvalue": [], "permission": [], "parentvapp": null, "recenttask": [], "availablefield": [], "overallstatus": "green", "network": [], "guestheartbeatstatus": "green", "name": "edin_host", "rootsnapshot": [], "configstatus": "green", "value": [], "summary": {}, "capability": {}, "snapshot": null, "runtime": {} } }, "availablefield": [ { "fieldinstanceprivileges": null, "fielddefprivileges": null, "name": "Service", "key": 101 }, { "fieldinstanceprivileges": null, "fielddefprivileges": null, "name": "Group", "key": 301 }, { "fieldinstanceprivileges": null, "fielddefprivileges": null, "name": "Role", "key": 103 }, { "fieldinstanceprivileges": null, "fielddefprivileges": null, "name": "Owner", "key": 104 }, { "fieldinstanceprivileges": null, "fielddefprivileges": null, "name": "Environment", "key": 102 } ], "datastore": [ { "_moId": "datastore-57683", "name": "mydatastore" } ], "summary": { "customvalue": [], "guest": { "toolsstatus": "toolsOk", "toolsversionstatus": "guestToolsUnmanaged", "hostname": "edin_host.example.com", "toolsrunningstatus": "guestToolsRunning", "guestid": "centos64Guest", "ipaddress": "192.168.1.1", "toolsversionstatus2": "guestToolsUnmanaged", "guestfullname": "CentOS 4/5/6/7 (64-bit)" }, "config": { "memoryreservation": 0, "product": null, "instanceuuid": "502c6c2d-8b58-a01c-4ce0-a18e0a65ac3a", "name": "edin_host", "numethernetcards": 1, "numcpu": 1, "installbootrequired": false, "guestid": "centos64Guest", "memorysizemb": 2048, "vmpathname": "[mydatastore] edin_host/edin_host.vmx", "template": false, "ftinfo": null, "uuid": "422c78f0-7401-3dbc-2790-9708af08bd03", "cpureservation": 0, "annotation": null, "numvirtualdisks": 2, "guestfullname": "CentOS 4/5/6/7 (64-bit)" }, "storage": { "timestamp": {}, "uncommitted": 1064, "unshared": 85899345920, "committed": 88272314106 }, "vm": { "resourcepool": { "_moId": "resgroup-14510", "name": "example-BI" }, "alarmactionsenabled": true, "configissue": [], "tag": [], "resourceconfig": {}, "datastore": [], "triggeredalarmstate": [], "layout": {}, "guest": {}, "effectiverole": [], "storage": {}, "layoutex": {}, "config": {}, "customvalue": [], "permission": [], "parentvapp": null, "recenttask": [], "availablefield": [], "overallstatus": "green", "network": [], "guestheartbeatstatus": "green", "name": "edin_host", "rootsnapshot": [], "configstatus": "green", "value": [], "summary": {}, "capability": {}, "snapshot": null, "runtime": {} }, "quickstats": { "ftsecondarylatency": -1, "privatememory": 1877, "compressedmemory": 0, "consumedoverheadmemory": 37, "swappedmemory": 0, "ftlatencystatus": "gray", "uptimeseconds": 1898839, "ssdswappedmemory": 0, "guestheartbeatstatus": "green", "distributedmemoryentitlement": 602, "staticcpuentitlement": 1905, "balloonedmemory": 0, "guestmemoryusage": 81, "overallcpuusage": 0, "overallcpudemand": 0, "staticmemoryentitlement": 2111, "ftlogbandwidth": -1, "distributedcpuentitlement": 0, "sharedmemory": 47, "hostmemoryusage": 1916 }, "runtime": { "powerstate": "poweredOn", "featuremask": [], "onlinestandby": false, "cleanpoweroff": null, "featurerequirement": [], "question": null, "boottime": {}, "maxmemoryusage": 2048, "offlinefeaturerequirement": [], "minrequiredevcmodekey": "intel-sandybridge", "toolsinstallermounted": false, "suspendinterval": 0, "memoryoverhead": null, "needsecondaryreason": null, "vflashcacheallocation": 0, "host": {}, "maxcpuusage": 1999, "device": [], "suspendtime": null, "recordreplaystate": "inactive", "consolidationneeded": false, "connectionstate": "connected", "dasvmprotection": {}, "faulttolerancestate": "notConfigured", "nummksconnections": 0 }, "overallstatus": "green" }, "overallstatus": "green", "ansible_host": "192.168.1.1", "triggeredalarmstate": [], "network": [ { "configstatus": "green", "customvalue": [], "name": "bi-acceptatie", "effectiverole": [], "permission": [], "configissue": [], "alarmactionsenabled": true, "vm": [], "value": [], "summary": {}, "host": [], "tag": [], "recenttask": [], "availablefield": [], "overallstatus": "green", "triggeredalarmstate": [] } ], "configstatus": "green", "guestheartbeatstatus": "green", "layout": { "logfile": [ "vmware-10.log", "vmware-11.log", "vmware-12.log", "vmware-13.log", "vmware-8.log", "vmware-9.log", "vmware.log" ], "configfile": [ "edin_host.nvram", "edin_host.vmsd" ], "disk": [], "snapshot": [], "swapfile": "[mydatastore] edin_host/edin_host-60ee652b.vswp" }, "guest": { "appheartbeatstatus": "appStatusGray", "interactiveguestoperationsready": false, "toolsversion": "2147483647", "toolsversionstatus": "guestToolsUnmanaged", "toolsrunningstatus": "guestToolsRunning", "ipaddress": "192.168.1.1", "screen": { "width": 1280, "height": 768 }, "guestfamily": "linuxGuest", "generationinfo": [], "ipstack": [], "gueststate": "running", "hostname": "edin_host.example.com", "guestid": "centos64Guest", "toolsstatus": "toolsOk", "net": [], "disk": [], "appstate": "none", "guestoperationsready": true, "toolsversionstatus2": "guestToolsUnmanaged", "guestfullname": "CentOS 4/5/6/7 (64-bit)" }, "effectiverole": [ -2 ], "rootsnapshot": [], "alarmactionsenabled": true, "value": [], "name": "edin_host", "capability": { "quiescedsnapshotssupported": true, "cpufeaturemasksupported": true, "consolepreferencessupported": false, "vpmcsupported": true, "featurerequirementsupported": true, "snapshotconfigsupported": true, "bootoptionssupported": true, "changetrackingsupported": true, "vmnpivwwnupdatesupported": true, "poweredonmonitortypechangesupported": true, "nestedhvsupported": true, "poweredoffsnapshotssupported": true, "settingvideoramsizesupported": true, "settingscreenresolutionsupported": false, "virtualmmuusagesupported": true, "sesparsedisksupported": true, "s1acpimanagementsupported": true, "reverttosnapshotsupported": true, "disksharessupported": true, "vmnpivwwndisablesupported": true, "disablesnapshotssupported": false, "swapplacementsupported": true, "bootretryoptionssupported": true, "memoryreservationlocksupported": true, "recordreplaysupported": true, "settingdisplaytopologysupported": false, "vmnpivwwnsupported": true, "npivwwnonnonrdmvmsupported": true, "toolsautoupdatesupported": false, "multiplecorespersocketsupported": true, "guestautolocksupported": true, "multiplesnapshotssupported": true, "snapshotoperationssupported": true, "toolssynctimesupported": true, "hostbasedreplicationsupported": true, "locksnapshotssupported": true, "memorysnapshotssupported": true }, "snapshot": null, "ansible_uuid": "39d641a6-9b9e-4ce0-b1f8-a4e8e38c9743", "layoutex": { "timestamp": { "hour": 7, "min": {}, "max": {}, "month": 12, "second": 20, "microsecond": 860550, "year": 2016, "tzinfo": {}, "resolution": {}, "day": 22, "minute": 45 }, "disk": [], "snapshot": [], "file": [] }, "runtime": { "powerstate": "poweredOn", "featuremask": [], "onlinestandby": false, "cleanpoweroff": null, "featurerequirement": [], "question": null, "boottime": { "hour": 9, "min": {}, "max": {}, "month": 11, "second": 31, "microsecond": 402054, "year": 2016, "tzinfo": {}, "resolution": {}, "day": 30, "minute": 42 }, "maxmemoryusage": 2048, "offlinefeaturerequirement": [], "minrequiredevcmodekey": "intel-sandybridge", "toolsinstallermounted": false, "suspendinterval": 0, "memoryoverhead": null, "needsecondaryreason": null, "vflashcacheallocation": 0, "host": { "alarmactionsenabled": true, "configissue": [], "vm": [], "hardware": {}, "tag": [], "datastore": [], "triggeredalarmstate": [], "network": [], "effectiverole": [], "datastorebrowser": {}, "config": {}, "customvalue": [], "permission": [], "systemresources": {}, "configmanager": {}, "recenttask": [], "availablefield": [], "overallstatus": "green", "name": "esx1.example.com", "configstatus": "green", "value": [], "summary": {}, "capability": {}, "licensableresource": {}, "runtime": {} }, "maxcpuusage": 1999, "device": [], "suspendtime": null, "recordreplaystate": "inactive", "consolidationneeded": false, "connectionstate": "connected", "dasvmprotection": { "dasprotected": true }, "faulttolerancestate": "notConfigured", "nummksconnections": 0 }, "config": { "uuid": "422c78f0-7401-3dbc-2790-9708af08bd03", "alternateguestname": "", "npivonnonrdmdisks": null, "instanceuuid": "502c6c2d-8b58-a01c-4ce0-a18e0a65ac3a", "cpuaffinity": null, "npivdesirednodewwns": null, "memoryhotaddenabled": true, "hardware": { "virtualich7mpresent": false, "numcpu": 1, "virtualsmcpresent": false, "memorymb": 2048, "device": [], "numcorespersocket": 1 }, "vappconfig": null, "tools": { "beforegueststandby": true, "beforeguestreboot": null, "beforeguestshutdown": true, "toolsupgradepolicy": "manual", "afterresume": true, "afterpoweron": true, "synctimewithhost": false, "lastinstallinfo": {}, "pendingcustomization": null, "toolsversion": 2147483647 }, "guestfullname": "CentOS 4/5/6/7 (64-bit)", "changeversion": "2016-11-30T09:42:11.343789Z", "defaultpowerops": { "defaultresettype": "soft", "defaultsuspendtype": "hard", "suspendtype": "hard", "standbyaction": "powerOnSuspend", "defaultpowerofftype": "soft", "resettype": "soft", "powerofftype": "soft" }, "cpuhotremoveenabled": false, "vpmcenabled": false, "firmware": "bios", "npivworldwidenametype": null, "nestedhvenabled": false, "version": "vmx-11", "locationid": "564da59f-ac2b-54e9-c006-84e06f757410", "maxmksconnections": 40, "template": false, "guestid": "centos64Guest", "bootoptions": { "enterbiossetup": false, "bootorder": [], "bootdelay": 0, "bootretryenabled": false, "bootretrydelay": 10000 }, "cpufeaturemask": [], "hotplugmemorylimit": 3072, "npivnodeworldwidename": [], "cpuallocation": { "overheadlimit": null, "reservation": 0, "limit": -1, "shares": {}, "expandablereservation": false }, "files": { "vmpathname": "[mydatastore] edin_host/edin_host.vmx", "snapshotdirectory": "[mydatastore] edin_host/", "suspenddirectory": "[mydatastore] edint_host/", "logdirectory": "[mydatastore] edin_host/" }, "memoryreservationlockedtomax": false, "scheduledhardwareupgradeinfo": { "fault": null, "upgradepolicy": "never", "versionkey": null, "scheduledhardwareupgradestatus": "none" }, "initialoverhead": null, "hotplugmemoryincrementsize": 128, "guestautolockenabled": false, "latencysensitivity": { "sensitivity": null, "level": "normal" }, "npivtemporarydisabled": true, "memoryallocation": { "overheadlimit": 63, "reservation": 0, "limit": -1, "shares": {}, "expandablereservation": false }, "ftinfo": null, "npivportworldwidename": [], "annotation": null, "memoryaffinity": null, "vassertsenabled": false, "datastoreurl": [], "changetrackingenabled": false, "name": "edin_host", "npivdesiredportwwns": null, "vflashcachereservation": 0, "extraconfig": [], "networkshaper": null, "modified": { "hour": 0, "min": {}, "max": {}, "month": 1, "second": 0, "microsecond": 0, "year": 1970, "tzinfo": {}, "resolution": {}, "day": 1, "minute": 0 }, "consolepreferences": null, "swapplacement": "inherit", "flags": { "diskuuidenabled": false, "snapshotdisabled": false, "recordreplayenabled": false, "runwithdebuginfo": false, "virtualmmuusage": "automatic", "enablelogging": true, "snapshotpoweroffbehavior": "powerOff", "snapshotlocked": false, "htsharing": "any", "disableacceleration": false, "monitortype": "release", "virtualexecusage": "hvAuto", "usetoe": false }, "cpuhotaddenabled": true }, "ansible_ssh_host": "192.168.1.1" 



Great, right? :) As I said, the amount of metadata is also regulated by max_object_level, so if there is too much data, drop it to 1.

We work with the script.


You can use it in two ways: either put the script with the settings in the folder of our inventory, or set the inventory manually:

 ansible all -i /path/to/my/vmware_inventory.py -m setup 

What I like about the setup module is that it does not require a connection, but is simply driven by the typewriter.
Then you can experiment, and you can use virtual metadata in the processing of conditions. For the purity of the experiment, I will filter by resource pool.
If at start Ansible will scold, it is necessary to delete comments at the beginning of the script.

Example number 1


 - name: example 1 hosts: mail-scanner* gather_facts: False tasks: - name: say the name of the machine debug: msg: "Hello from {{ hostvars[inventory_hostname].resourcepool.name }}!" 

In this example, I take several of the same name hosts and display a variable taken not from the Ansible facts, but from the machine metadata.

Example number 2


 --- - name: example nr2 hosts: all gather_facts: False vars: resource_pool: "{{ hostvars[inventory_hostname].resourcepool._moId }}" tasks: - name: say hello, if in RP debug: msg: "Hello, fron {{ hostvars[inventory_hostname].name }}" when: resource_pool == "resgroup-1" 

Here we have to use a dirty hack, because the square brackets do not want to be processed normally in the when block, so I first declare the variable and then check it. If the machine is in the resource pool, Ansible will return her name to me.

Example number 3


You can do this kind of trick in the roles.

 -- - name: example nr3 hosts: centos64Guest gather_facts: False vars: resource_pool: "{{ hostvars[inventory_hostname].resourcepool._moId }}" roles: - { role: myrole, when: resource_pool == "resgroup-1" } 

Dear reader, the possibilities of using metadata are limited only by your imagination!

Group in our own way


It's great to use metadata for filtering in when, but this does not solve the problem of running across all machines - we want to group machines at the script level. To do this, we once again play with the groupby_patterns setting. For example, if you set the value {{resourcepool.name}}, then the pool resource will be grouped by name. You can still group by virtual network name ({{network.name}}) or by tag ({{tag}})

What is the best way to decide you, the reader.

PS By the way, you can make a bunch of groups of parents and groups of children in a static inventory. This is described in detail in the Ansible documentation (albeit using AWS as an example).

Source: https://habr.com/ru/post/318620/


All Articles