Приветствую, дорогой читатель! В предыдущей серии я рассказывал об опыте, сыне ошибок трудных, и обещал продолжить с модификацией Powershell скрипта.
К сожалению, проект по этому направлению заморожен до следующего года, и чтобы не томить тебя ожиданиями, я решил поделиться своим «исследованием» в области динамической инвентаризации Vmware, что является промежуточной фазой для нашего проекта. Если твой парк виртуалок часто изменяется, то я настоятельно рекомендую ознакомиться с материалом.
Первым делом, что я сделал, когда обнаружил этот коммент, это побежал к нашим инфраструктурщикам с вопросом «оно мне надо, велосипед пилить?». Ребята пояснили, что Vcloud это:
- Дорого.
- Окупается, когда у тебя распределенные датацентры на разных континентах.
- Овер9000 изменений в инфраструктуре и столько же виртуалок.
- Да и вообще, мы в AWS переезжаем, зачем нам.
Зарядившись мотивацией («нам никто не поможет, кроме нас самих»), я полез в дебри изучения динамики. Не пойми меня неправильно, дорогой читатель, статичный инвентарь в Ansible штука очень удобная, да и вооружившись echo и sed в него тоже можно «писать динамически», но зачем?
Ищем нужный скрипт
На данный момент на Github в разделе contrib представлены следующие реализации динамического инвентаря: тот и этот.
На просторах интернета еще нашелся такой вариант. Я его отбросил сразу — он тупо выгребает список всех машинок (без внутренностей), работает долго, логин и пароль для соединения с варей надо хардкодить. Иными словами — здорово чтобы поиграть, но не более.
vmware.py тоже отпал, поскольку работает долго даже с кэшем (на моей машинке 30 минут без кэша, и 8 с кэшем), да и «глубина» погружения во внутренности виртуалки тоже не нравится.
Так что мой выбор пал vmware_inventory.py — он очень гибок, с кэшем работает 2 секунды и дает мне возможность группировать по параметрам машинки.
В первую очередь, большой и длинный INI файлик — конфигурация для работы с варей.
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
В первую очередь я рекомендую завести отдельного юзера для работы с варей. По сути нам нужен обычный пользователь, который может читать данные машинок, ресурспулов, папок и датасторов. Никаких прав на включение/выключение, создание/удаление виртуалок у него нет и не должно быть. Least privilege principle!
Допольнительно подчеркиваю:
- Срок жизни кэша (cache_max_age) в секундах. Я выставил на сутки, но каждый раз когда будет создаваться машинка, кэш будет обновляться (запуск скрипта с опцией --refresh-cache). Также обновление будет раз в сутки ночью (точно также)
- Глубину рекурсии (max_object_level). Двойка здесь самый оптимальный вариант, чтобы получить ЕЩЕ больше метаданных о машинке. Больше метаданных — больше игр с группировками и фильтрацией. Но — больше json и больше времени работы скрипта.
- Паттерн машинки (alias_pattern) — по умолчанию идет {{ config.name + '_' + config.uuid }}, что делает название машинки нечитабельным, так что я убрал config.uuid
- Паттерны группировки (groupby_patterns) — вот это самая прелесть в скрипте. Позволяет группировать машины на основе метаданных. Группировать можно по всему от слова «совсем». Ресурспул, кастомный вар в нотах, сетка, location, даже название датастора. По умолчанию группируется по гестам (читай — по установленной ОС)
Всякие очевидные вещи типа валидации сертификатов описывать не буду, но если попал в беду со скриптом — пиши в комментариях.
Пробуем запустить.
$ time ./vmware_inventory.py > fact_from_vm_py.json
real 27m59.970s
user 8m33.334s
sys 0m7.841s
Повторюсь, читатель, будь осторожен обновлением кэша, а то плейбуки будут медленнее черепашки!
С кэшем весь инвентарь подгружается за секунду, и основное время работы скрипта занимает сканирование вари. Я не буду приводить весь выхлоп скрипта, приведу один единичный хост:
Данных очень много
"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"
Здорово, правда? :) Как я уже сказал, количество метаданных также регулируется max_object_level, так что если данных уж слишком много — опустите до 1.
Работаем со скриптом.
Пользоваться можно двумя способами: либо кладем скрипт с настройками в папку наших инвентарей, либо задаем инвентарь вручную:
ansible all -i /path/to/my/vmware_inventory.py -m setup
Чем мне нравится модуль setup — он не требует подключения, а просто прогоняется по машинкам.
Дальше уже можно экспериментировать, причем использовать метаданные виртуалки можно и в обработке условий. Для чистоты эксперимента я буду фильтровать по ресурс пулу.
Если при запуске Ansible поругается, то необходимо удалить комментарии в начале скрипта.
Пример номер 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 }}!"
В этом примере я беру несколько одноименных хостов и вывожу переменную, взятую не из Ansible facts, а именно из метаданных машины.
Пример номер 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"
Здесь приходится применять грязный хак, поскольку квадратные скобки не хотят нормально обрабатываться в блоке when, поэтому сначала я декларирую переменную, а затем сверяюсь. Если машинка в ресурс пуле, Ansible вернет мне ее имя.
Пример номер 3
Делать подобные выкрутасы можно и в ролях.
--
- name: example nr3
hosts: centos64Guest
gather_facts: False
vars:
resource_pool: "{{ hostvars[inventory_hostname].resourcepool._moId }}"
roles:
- { role: myrole, when: resource_pool == "resgroup-1" }
Дорогой читатель, возможности использования метаданных ограничиваются лишь твоей фантазией!
Группируем по-своему
Здорово использовать метаданные для фильтрации в when, но это не решает проблему пробега по всем машинам — нам же хочется группировать машины на уровне скрипта. Для этого мы еще раз поиграем с настройкой groupby_patterns. К примеру, если выставить значение {{ resourcepool.name }} то группироваться будет по имени ресурс пула. Группировать еще можно по имени виртуальной сети ({{ network.name }}) или по таге ({{ tag }})
Какой способ лучше решать уже тебе, читатель.
P.S. Кстати, можно делать связку групп-родителей и групп-детей в статическом инвентаре. Подробно это описано в документации Ansible (пусть и на примере AWS).
Поделиться с друзьями