Работа с пользовательскими модулями grain, state и execution
Модули state, grain и execution — это Python-файлы, хранящиеся на сервере управления (master) или в S3-совместимом хранилище, которые обеспечивают управление конфигурациями и автоматизацию в SaltStack, а именно:
-
state-модули;
Модули состояния, которые позволяют управлять конфигурацией системы декларативным образом. Они реализуют функции, используемые в файлах состояний (YAML-файлах с расширением
.sls) и содержат логику для выполнения определенных действий. Каждый модуль отвечает за конкретную функцию, которая может быть вызвана из файлов состояний, например,pkg.installed(для установки пакетов) илиservice.running(для управления сервисами). -
grain-модули;
Модули, которые собирают статическую информацию о системе, включая ОС, версию, аппаратные характеристики, IP-адреса и другие метаданные. Сервер управления (master) распространяет их на агенты (minions) и запускает по заданному расписанию.
-
execution-модули.
Модули выполнения, которые позволяют запускать команды на агентах (minions) в реальном времени, управляя пакетами, файловой системой и службами. Их можно вызывать через командную строку Salt или API для немедленных действий.
Расширение функциональности
Помимо встроенных модулей продукт позволяет расширять функциональность модуля координации (SaltStack) и загружать пользовательские модули (Python-файлы) в S3-совместимое хранилище через пользовательский интерфейс «Кабинет администратора».
При создании пользовательского state-модуля его имя по умолчанию соответствует имени Python-файла, в котором он хранится. Например, файл с именем my_custom_module.py будет называться модулем my_custom_module. Если необходимо изменить имя модуля, сохранив имя файла, используйте функцию _virtual_.
В разделах ниже рассматриваются примеры создания пользовательских модулей:
Cоздание пользовательского state-модуля
| Подробное описание см. в официальной документации. |
Обработка ошибок при импорте state-модуля
Если какой-либо модуль не может быть импортирован из-за ошибок, это не помешает загрузке агента (minion). В результате агент продолжит свою работу, а модуль с ошибками просто будет пропущен. Это позволяет системе оставаться работоспособной даже в случае проблем с отдельными модулями.
Пример написания state-модуля
Ниже представлен пример пользовательского state-модуля my_custom_state_module в файле my_custom_state_module.py, который управляет состоянием некоторого объекта, взаимодействуя с execution-модулем (описание execution-модуля см. в разделе ниже).
Содержимое файла my_custom_state_module.py
# File: my_custom_state_module.py
import salt.exceptions
def enforce_custom_thing(name, foo, bar=True):
"""
Принудительно установить состояние пользовательского объекта
Этот модуль состояния выполняет пользовательское действие. Он вызывает
модуль выполнения my_custom_module для проверки текущего состояния
системы и выполнения необходимых изменений.
name
Объект, к которому нужно что-то сделать
foo
Обязательный аргумент
bar : True
Аргумент со значением по умолчанию
"""
ret = {
"name": name,
"changes": {},
"result": False,
"comment": "",
}
# Начинать с базовой проверки ошибок. Все ли переданные параметры имеют смысл
# и согласуются друг с другом?
if bar == True and foo.startswith("Foo"):
raise salt.exceptions.SaltInvocationError(
'Аргумент "foo" не может начинаться с "Foo", если аргумент "bar" равен True.'
)
# Проверить текущее состояние системы. Нужно ли что-то менять?
current_state = salt["my_custom_state_module.current_state"](name)
if current_state == foo:
ret["result"] = True
ret["comment"] = "Система уже в корректном состоянии"
return ret
# Состояние системы нужно изменить. Проверка работы в корректном режиме
# test=true.
if opts["test"] == True:
ret["comment"] = 'Состояние "{0}" будет изменено.'.format(name)
ret["changes"] = {
"old": current_state,
"new": "Описание, разница, что угодно о новом состоянии",
}
# Возврат None при работе с test=true.
ret["result"] = None
return ret
# Внесение фактического изменения и возврат результата.
new_state = salt["my_custom_state_module.change_state"](name, foo)
ret["comment"] = 'Состояние "{0}" было изменено!'.format(name)
ret["changes"] = {
"old": current_state,
"new": new_state,
}
ret["result"] = True
return ret
Распространение state-модуля на агенты (minions)
После загрузки state-модуля в S3-совместимое хранилище, он синхронизируется на сервер управления (master), а затем распространяется среди агентов (minions) согласно заданному расписанию state.highstate, или при выполнении команд синхронизации.
Использование state-модуля в файле состояния
State-модули используются в файлах состояний формул SaltStack. Для использования созданного пользовательского state-модуля добавьте его вызов внутри формулы SaltStack.
Cоздание пользовательского grain-модуля
| Подробное описание см. в официальной документации. |
Перед созданием нового grain-модуля определите, какие данные вы хотите собирать. Рекомендуется использовать grain-модули для сбора статических данных. Если данные часто меняются, рассмотрите другие средства (например, execution-модули). Используйте grain-модули для данных, позволяющих отбирать агентов (minions), например, для включения в коллекции. Если агенты (minions) работают на разных ОС, код grain-модуля должен поддерживать все целевые операционные системы или приложения.
Пример написания grain-модуля
Чтобы отделить пользовательские grain-модули от модулей, включенных в поставку, имя файла должно начинаться с префикса custom_grain_.
|
Данные из grain-модуля собираются путем выполнения всех публичных функций (тех, которые не начинаются с подчеркивания), найденных в пользовательских grain-модулях. Функции в grain-модуле должны возвращать словарь Python, где ключи словаря являются именами grains, а значение каждого ключа — это значение для соответствующего grain.
Ниже представлен пример пользовательского grain-модуля с именем my_custom_grain_module в файле my_custom_grain_module.py.
Содержимое файла my_custom_grain_module.py
import subprocess
def some_function():
# Инициализация словаря grains
grains = {}
# Логика, устанавливающая значения grains
grains["os_version"] = subprocess.check_output("lsb_release -a | grep 'Description:' | cut -d ':' -f2- | xargs", shell=True).decode('utf-8').strip()
grains["os_installation_time"] = subprocess.check_output("ls -l --full-time /etc/hostname | awk '{print $6" "$7" "$8}'", shell=True).decode('utf-8').strip()
grains["os_uptime"] = subprocess.check_output("uptime --pretty", shell=True).decode('utf-8').strip()
# Возврат словаря
return grains
Тестирование работы функции
Для локального тестирования работы функции можно использовать следующую команду:
> python3 -c 'import custom_grain_test; import json; print(json.dumps(custom_grain_test.some_function(), indent=4))'
Пример вывода:
{
"os_version": "Ubuntu 24.04.2 LTS",
"os_installation_time": "2024-07-2611:07:54.164623415+0300",
"os_uptime": "up 23 hours, 6 minutes"
}
Распространение grain-модуля на агенты (minions)
После загрузки grain-модуля в S3-совместимое хранилище, он синхронизируется на сервер управления (master), а затем распространяется среди агентов (minions) согласно заданному расписанию state.highstate, или при выполнении команд синхронизации.
После синхронизации grain-модуль можно использовать в общем расписании. Собранные данные будут поступать в Kafka. Для my_custom_grain_module данные отображаются в следующем виде:
os_installation_time:
2025-03-0610:34:23.808001871+0300
os_uptime:
up 6 hours, 59 minutes
os_version:
Astra Linux 1.7 x86-64
Отображение собираемых данных в пользовательском интерфейсе «Кабинет Администратора»
После распространения grain-модуля на агенты (minions) и получения данных на сервере управления (master), вы можете настроить отображение собираемых данных через пользовательский интерфейс «Кабинет Администратора».
|
В пользовательском интерфейсе «Кабинет Администратора» можно выводить только значения типа строка или дата. Значения типа словарь или массив не поддерживаются. Поэтому при добавлении атрибутов на UI необходимо указывать полный путь (Json Path) к требуемому значению. В данном случае путь будет следующим:
|
Cоздание пользовательского execution-модуля
| Подробное описание см. в официальной документации. |
Пример написания execution-модуля
Ниже представлен пример пользовательского execution-модуля my_custom_execution_module в файле my_custom_execution_module.py. С помощью этого модуля можно просматривать домашние директории, не связанные с пользователями.
Содержимое файла my_custom_execution_module.py
'''
Возвращает список директорий, к которым больше не привязаны пользователи.
Пример использования в CLI::
salt '*' my_custom_execution_module.get_orphan_home_dirs
'''
def get_orphan_home_dirs():
ret = dict() # Словарь для хранения результатов
root = '/home' # Корневая директория для поиска
dirs = os.listdir(root) # Получаем список директорий в корне
for dir_ in [d for d in dirs if os.path.isdir(os.path.join(root, d))]: # Проходим по всем директориям
found = False # Флаг для отслеживания найденного пользователя
for p in [u for u in pwd.getpwall() if (u.pw_uid >= 1000)]: # Игнорируем системных пользователей
if p.pw_dir == os.path.join(root, dir_): # Проверяем, существует ли пользователь с данной директорией
found = True
break
if not found: # Если пользователь не найден
ret[root + '/' + dir_] = 'Пользователь {} не найден!'.format(dir_) # Добавляем результат в словарь
return ret # Возвращаем результаты
Тестирование execution-модуля
Перед тестированием execution-модуля, необходимо выполнить синхронизацию агентов (minions) с сервером управления (master).
Чтобы протестировать execution-модуль, выполните команду:
root@master:~$ salt 'minion-1' get_orphan_home_dirs
minion-1:
----------
/home/does-not-exist:
Пользователь does-not-exist не найден!
root@master:~$