Привет, Хабр! Меня зовут Габидуллин Александр, я старший инженер в отделе архитектуры и интеграции. Занимаюсь внедрением продуктов компании, и автоматизацией рутинных задач.
Сегодня хочу поделиться опытом того, как я отказался от стандартного пакета sssd-tools для мониторинга службы SSSD в пользу прямого общения с демоном через D-Bus и создал свой первый Ansible-модуль.
Недавно я уже сделал небольшую утилиту для мониторинга различных служб через системный трей в Linux - исходник лежит тут.
И далее подумал об импортозамещение, что было бы удобно по нажатию одной кнопки в трее смотреть: информацию о домене, применять групповые политики, и если что-то сломалось подсказать админу информацию о своей машине или выполнить какие-то команды. Так я начал писать applet для ALD Pro (Freeipa тоже поддерживается, но с меньшим функционалом).
Первоначально я использовал утилиту sssctl из пакета sssd-tools, с помощью которой я мог взаимодействовать с доменом и службой SSSD, но это добавляло лишние зависимости для моего приложения.
Зависимости и почему захотелось от них избавиться

После обсуждения с коллегами, я понял, что не стоит усложнять пакет различными зависимостями, можно общаться с SSSD напрямую через D-Bus.
Прямое взаимодействие даёт:
Более быстрый отклик
Отсутствие промежуточных обработчиков
Минимальное количество зависимости
Мой pull-request в ansible.general
D-Bus как решение: зачем и почему
D-Bus (сокращенно от «Desktop Bus») — это система межпроцессного взаимодействия (IPC), которая позволяет приложениям на одном компьютере общаться друг с другом, обмениваясь сообщениями. Она действует как «шина сообщений», через которую один процесс может отправлять запросы, сигналы и данные другим процессам.
Теперь необходимо было понять, как с ним общаться?
На помощь приходит dbus-monitor — утилита для просмотра D-Bus сообщений в реальном времени.
1. Ищем интерфейс, связанный с нашей службой SSSD:
dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames
2. Вывод, который мы получаем:
method return time=1764241336.897765 sender=org.freedesktop.DBus -> destination=:1.638205 serial=3 reply_serial=2
array [
string "org.freedesktop.DBus"
string "org.freedesktop.systemd1"
string "com.redhat.oddjob"
string "org.freedesktop.ModemManager1"
string "org.freedesktop.NetworkManager"
string "org.freeipa.server"
string "org.fedorahosted.certmonger"
string "ru.astralinux.FileManager.SmbWatcher"
string "org.freedesktop.PolicyKit1"
string "ru.astralinux.events"
string "com.redhat.idm.trust"
string "org.freedesktop.UDisks2"
string "fi.w1.wpa_supplicant1"
string "org.freedesktop.login1"
string "org.freedesktop.sssd.infopipe"
string "com.redhat.oddjob_mkhomedir"
string "org.freedesktop.UPower"
.......
]
3. Находим интересующий нас интерфейс:
string "org.freedesktop.sssd.infopipe"
4. Подключаемся и начинаем слушать сообщения от SSSD:
dbus-monitor --system "sender=org.freedesktop.sssd.infopipe" "destination=org.freedesktop.sssd.infopipe"
5. В другом терминале запускаем sssctl, и смотрим какие запросы и ответы получает наш интерфейс D-Bus:
sssctl domain-status ald.pro
6. В выводе видим:
method call time=1764242184.579534 sender=:1.638429 -> destination=org.freedesktop.sssd.infopipe serial=2 path=/org/freedesktop/sssd/infopipe/Domains/ald_2epro; interface=org.freedesktop.sssd.infopipe.Domains.Domain; member=IsOnline
method call time=1764242184.579878 sender=:1.103398 -> destination=org.freedesktop.DBus serial=60344 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetConnectionUnixUser
string ":1.638429"
method return time=1764242184.579898 sender=org.freedesktop.DBus -> destination=:1.103398 serial=1079864 reply_serial=60344
uint32 0
method return time=1764242184.580558 sender=:1.103398 -> destination=:1.638429 serial=60345 reply_serial=2
boolean true
method call time=1764242184.580681 sender=:1.638429 -> destination=org.freedesktop.sssd.infopipe serial=3 path=/org/freedesktop/sssd/infopipe/Domains/ald_2epro; interface=org.freedesktop.sssd.infopipe.Domains.Domain; member=ListServices
method return time=1764242184.581291 sender=:1.103398 -> destination=:1.638429 serial=60346 reply_serial=3
array [
string "IPA"
]
method call time=1764242184.581431 sender=:1.638429 -> destination=org.freedesktop.sssd.infopipe serial=4 path=/org/freedesktop/sssd/infopipe/Domains/ald_2epro; interface=org.freedesktop.sssd.infopipe.Domains.Domain; member=ActiveServer
string "IPA"
method return time=1764242184.583142 sender=:1.103398 -> destination=:1.638429 serial=60347 reply_serial=4
string "aldprodc1.ald.pro"
]
Это показывает нам:
Вызов метода
IsOnlineдля доменаald.pro(закодированного какald_2epro)Ответ
boolean true— домен онлайн
Именно наблюдение за реальными вызовами помогло понять структуру общения с D-Bus, что помогло заменить команды с использованием sssctl на общение через D-Bus.
Приведу пару примеров кода, который используется в приложение:
def get_dbus_iface():
try:
domain = get_domain_name()
domain = domain.replace(".", "_2e")
bus = dbus.SystemBus()
sssd_object = bus.get_object('org.freedesktop.sssd.infopipe', f"/org/freedesktop/sssd/infopipe/Domains/{domain}")
iface = dbus.Interface(sssd_object, dbus_interface='org.freedesktop.sssd.infopipe.Domains.Domain')
return iface
except Exception as e:
response = f"Ошибка при выполнении команды: {e}"
return response
@method
async def domain_status():
iface = get_dbus_iface()
try:
response = iface.IsOnline()
response = str(response)
return Success(response)
except Exception as e:
return Error(code=1, message=f"Ошибка при выполнении команды: {e}")
@method
async def get_active_dc():
try:
iface = get_dbus_iface()
response = iface.ActiveServer('IPA')
response = str(response)
return Success(response)
except Exception as e:
response = f"Ошибка при выполнении команды: {e}"
return Error(code=1, message=response)
Как можно увидеть, структура D-Bus в части sssd выглядит так:
org.freedesktop.sssd.infopipe # Сервис
└── /org/freedesktop/sssd/infopipe/Domains/ # Объекты доменов
└── /org/freedesktop/sssd/infopipe/Domains/example_2ecom # Конкретный домен
└── org.freedesktop.sssd.infopipe.Domains.Domain # Интерфейс
Таким образом перебираем все возможные и нужные команды, и переписываем их на работу с D-Bus, а далее функции и идемпотентность кода.
Также стало удобно определять домен, к которому принадлежит машина, и взаимодействовать с ним напрямую через D-Bus или через другой код при необходимости.
Идея Ansible-модуля: от мониторинга к автоматизации
Идея модуля возникла на стыке двух задач: с одной стороны, это был факультативный проект для освоения возможностей Ansible, с другой — желание применить DevOps-практики, уйдя от модулей cmd и shell для работы с доменом через sssctl.
Поэтому необходимо было реализовать удобный, идемпотентный и среда-независимый модуль.
Ключевые особенности реализации
1. Кодирование доменных имен
Важно заметить, что символ . не используются для общения с D-Bus SSSD демона, он заменяется на _2e для корректного формирования D-Bus путей.
def _get_domain_path(self, domain: str) -> str:
"""Convert domain name to D-Bus path format.
Args:
domain: Domain name to convert.
Returns:
D-Bus path for the domain.
"""
return f'/org/freedesktop/sssd/infopipe/Domains/{domain.replace(".", "_2e")}'
2. Поддержка разных типов серверов
Так как домен не обязательно может быть на базе Linux, важно учесть возможность указания типов серверов. Таким образом можно использовать IPA или AD тип, в зависимости от необходимой информации о статусе домена.
def get_active_servers(self, domain: str, server_type: str) -> dict:
"""Get active servers for domain.
Args:
domain: Domain name to get servers for.
server_type: Type of servers ('IPA' or 'AD').
Returns:
Dictionary with server types as keys and server names as values.
"""
domain_obj = self._get_domain_object(domain)
iface = dbus.Interface(domain_obj, dbus_interface=self.DOMAIN_INTERFACE)
if server_type == 'IPA':
server_name = f'{server_type} Server'
return {server_name: iface.ActiveServer(server_type)}
elif server_type == 'AD':
return {
'AD Global Catalog': iface.ActiveServer(f'sd_gc_{domain}'),
'AD Domain Controller': iface.ActiveServer(f'sd_{domain}')
}
return {}
3. Детальная обработка ошибок D-Bus
Разбираем D-Bus ошибки по именам для понятных сообщений.
try:
if action == 'domain_status':
result['online'] = sssd.check_domain_status(domain)
elif action == 'domain_list':
result['domain_list'] = sssd.get_domain_list()
elif action == 'active_servers':
result['servers'] = sssd.get_active_servers(domain, server_type)
elif action == 'list_servers':
result['list_servers'] = sssd.list_servers(domain, server_type)
except dbus.exceptions.DBusException as e:
dbus_error_name = e.get_dbus_name()
if dbus_error_name == 'org.freedesktop.DBus.Error.UnknownObject':
module.fail_json(msg=f'Domain not found: {domain}', **result)
elif dbus_error_name == 'org.freedesktop.DBus.Error.UnknownInterface':
module.fail_json(msg=f'Interface not found for domain: {domain}', **result)
# ... другие специфические ошибки
Использование в Ansible
После получение одобрения от сообщества, можно будет использовать мой модуль из публичного хранилище модулей community.general
Пример playbook:
- name: SSSD health check
hosts: all
tasks:
- name: Get domain list
sssd_info:
action: domain_list
register: domains
- name: Check status for each domain
sssd_info:
action: domain_status
domain: "{{ item }}"
loop: "{{ domains.domain_list }}"
register: domain_status
- name: Alert if any domain is offline
debug:
msg: "Domain {{ item.item }} is offline!"
loop: "{{ domain_status.results }}"
when: item.online == 'offline'
Преимущества решения
Надежность:
Стабильный ответ вместо парсинга текста;
Детальная обработка ошибок;
Совместимость между версиями SSSD.
Функциональность:
Поддержка всех основных операций мониторинга;
Структурированный JSON вывод;
Интеграция с Ansible-экосистемой;
Идемпотентность модуля.
Заключение

В итоге я освоил работу с D-Bus и нашел способ замены системных утилит на прямое взаимодействие — это сразу облегчило приложение и дало заметный прирост производительности.
Такой подход оказался быстрее и надежнее, плюс — он идеально встраивается в автоматизацию. А для изучения шины данных незаменимым помощником стал dbus-monitor.
И также я надеюсь помочь сообществу свои модулем, который я отправил в ansible-community.
Вопрос для дочитавших данный материал
А как вы используете D-Bus в своих проектах?
Работа с D-Bus полна трудностей, в моем примере символ . не использовался для общения с SSSD демоном, он заменяется на _2e. А с какими неожиданными проблемами сталкивались вы, используя “системную шину данных”?