Опыт построения Infrastructure-as-Code в VMware. Часть 1.1: Динамическая инвентаризация

от автора

image

Приветствую, дорогой читатель!

В предыдущей серии я рассказывал об опыте, сыне ошибок трудных, и обещал продолжить с модификацией 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!

Допольнительно подчеркиваю:

  1. Срок жизни кэша (cache_max_age) в секундах. Я выставил на сутки, но каждый раз когда будет создаваться машинка, кэш будет обновляться. Также обновление будет раз в сутки ночью
  2. Глубину рекурсии (max_object_level). Двойка здесь самый оптимальный вариант, чтобы получить ЕЩЕ больше метаданных о машинке. Больше метаданных — больше игр с группировками и фильтрацией. Но — больше json и больше времени работы скрипта.
  3. Паттерн машинки (alias_pattern) — по умолчанию идет {{ config.name + ‘_’ + config.uuid }}, что делает название машинки нечитабельным, так что я убрал config.uuid
  4. Паттерны группировки (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).
ссылка на оригинал статьи https://habrahabr.ru/post/318620/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *