Привет, Хабр! Меня зовут Габидуллин Александр, я старший инженер в отделе архитектуры и интеграции. Занимаюсь внедрением продуктов компании, и автоматизацией рутинных задач.
Сегодня хочу поделиться опытом того, как я отказался от стандартного пакета 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. А с какими неожиданными проблемами сталкивались вы, используя “системную шину данных”?
Комментарии (8)

Vecna001
04.12.2025 08:57Редкий случай, когда статья одновременно претендует на «архитектурный опыт» и демонстрирует полное непонимание того, что такое стабильные интерфейсы и эксплуатационная надёжность.
Вы «избавились от зависимости от
sssctl, но по факту:отказались от публичного, поддерживаемого API,
-
и напрямую подключились к внутренней реализации демона, подсмотренной через
dbus-monitor.Это не инженерное решение, это любительский реверс по живому production.
Про «минимальное количество зависимостей» особенно показательно:
dbus-pythonв рантайме,жёсткая привязка к infopipe,
зависимость от конкретной реализации SSSD.
Зависимостей стало не меньше — они просто стали хуже по качеству.
«Заметный прирост производительности» без единого замера — это отдельный жанр технической фантастики.
sssctlходит в тот же D-Bus. Вы ускорили ровно то же самое действие, убрав CLI-обёртку с временем работы меньше миллисекунды, но назвали это оптимизацией.Хардкод путей вида:
/org/freedesktop/sssd/infopipe/Domains/{domain.replace(".", "_2e")}— учебник по созданию латентных багов на апдейтах. Сегодня работает — завтра после обновления SSSD модуль молча ломается.
Ansible-модуль — отдельная категория:
строки вместо булевых значений,
отсутствует декларативность,
идемпотентность заявлена, но не реализована,
changedотсутствует как класс.
Это не модуль автоматизации, это Python-скрипт с завышенной самооценкой.
Обработка ошибок — формула «поймали всё и ничего не поняли».
В эксплуатации это превращается в бесконечное «Произошла ошибка» без диагностической ценности.Использование
dbus-monitorкак основы интеграции — прямой индикатор уровня: диагностический сниффер выдается за спецификацию API. Контракты не описаны, версии не зафиксированы, документация отсутствует — зато присутствует слово «архитектура».И, конечно, «импортозамещение». Его здесь нет вообще. Это просто декоративный термин без технического содержания.
Итог: статья демонстрирует, как:
заменить поддерживаемый интерфейс на внутренний,
потерять совместимость,
ничего не измерить,
назвать это «архитектурным улучшением»,
и отправить в Ansible как «модуль».
Для sandbox — допустимо.
Для production — классический пример создания технического долга в момент написания кода.
MikeyTide Автор
04.12.2025 08:57Приветствую! Очень подробно и по теме, но не со всем я готов согласиться.
На то это и форум, где каждый говорит что думает, а мой код - это опенсорс, и любой может прийти и добавить комментарии в PR.Отказались от публичного, поддерживаемого API
Я не отказался, а как вы правильно указали сделал углубленное изучение общения утилиты и системы.
Далее просто убрал утилиту, и общаюсь самостоятельно, минуя proxy в лице sssctlПро «минимальное количество зависимостей» особенно показательно
Dbus-python vs sssd-tools — это переход от функциональной зависимости к системной абстракции. D-Bus — это стандартный системный IPC-механизм, который уже присутствует в любом современном дистрибутиве. Это как заменить прямой вызов конкретной программы на использование системного сокета.
Жёсткая привязка к infopipe
Жёсткая привязка к infopipe — но это и есть официальный публичный D-Bus интерфейс SSSD, зарегистрированный в системе. Это не reverse engineering, а использование предоставленного разработчиками SSSD API, который более стабилен, чем парсинг CLI вывода.
Зависимость от конкретной реализации SSSD
Dbus-python является стандартной библиотекой для работы с системной шиной, тогда как sssd-tools — это специфичная утилита конкретного пакета. Первая является частью экосистемы системного программирования на Python, вторая — внешней CLI обёрткой.
Заметный прирост производительности» без единого замера — это отдельный жанр технической фантастики
Вы правы в том, что я не предоставил детальных замеров производительности — это действительно упущение, которое стоило бы исправить.
Однако позвольте объяснить, почему даже без точных цифр этот подход даёт системные преимущества.
Вы утверждаете, что sssctl тоже использует D-Bus, но здесь есть принципиальная разница: прямой вызов метода против запуска процесса, который внутри себя делает тот же вызов.
Даже если накладные расходы на запуск процесса составляют миллисекунды, в контексте Ansible-модуля, выполняющегося на сотнях хостов, эта разница становится значимой.
Но что ещё важнее — это качество интеграции. Для модуля Ansible, который должен быть предсказуемым и лёгким, прямой D-Bus вызов — это более чистая архитектура, даже если абсолютный прирост скорости измеряется миллисекундами на одном вызове.
Хардкод путей вида:
/org/freedesktop/sssd/infopipe/Domains/{domain.replace(".", "_2e")}Путь /org/freedesktop/sssd/infopipe/Domains/{domain} — это не хардкод в смысле "зашитых значений", а следование документально зафиксированной схеме D-Bus именования, которую использует сам SSSD.
Кодирование точки как _2e — это стандартное преобразование D-Bus для специальных символов в object paths, а не произвольное решение.
Важный нюанс: если разработчики SSSD изменят эту схему путей в будущей версии, то сломается не только мой код, но и любой другой софт, использующий этот интерфейс, включая потенциально GUI-утилиты и системные мониторинговые инструменты.
D-Bus интерфейсы, особенно зарегистрированные в системе, имеют определённые ожидания стабильности.Ansible-модуль — отдельная категория
Мой PR открыт для комментариев, на то это и open-source
https://github.com/ansible-collections/community.general/pull/11120

Tamerlan666
04.12.2025 08:57По Ansible модулю. Сама идея хорошая и контрибьют приветствуется. Только пока что модуль еще на этапе код ревью и PR не принят. Я бы на вашем месте дождался хотя бы апрува и мержа в develop, если не ближайшего релиза, прежде чем рекламировать свои достижения. Тогда модулем могли бы сразу воспользоваться все желающие, узнавшие о нем из статьи. А сейчас это доступно лишь избранным энтузиастам, не поленившимся скачать сырой код из вашего форка ради экспериментов.
Сам код модуля на вид довольно сырой. Отсутствуют integration тесты. Ну, и на данный момент, насколько я могу видеть, код не проходит даже первичные quality gates в виде линтеров вроде pep8. На вашем месте я бы освоил ansible-test и довел локально код до ума хотя бы в плане следования стандартам оформления. Чтобы вас не заворачивал на ревью тупой автолинтер по формальным признакам.

MikeyTide Автор
04.12.2025 08:57Приветствую, так я и сказал вот мой пр, чтобы люди после статьи могли придти и посмотреть.
А линтеры поругались только на тесты в их ci/cd. А сам код уже все ок, там все поправлено и сделано.
А с тестами сложно протестировать этот модуль в рамках ci/cd, так как опыт написания тестов у меня не большой и я попросил помощи коллег в ПР с тестами.

Tamerlan666
04.12.2025 08:57В CI там запускается простая конструкция ansible-test для sanity, которую вы легко бы запустили у себя локально, если бы потратили на это чуток времени.
Тесты писать не всегда просто, но без них поддерживать модуль в дальнейшем будет сложно, т.к. нет гарантий что не поймаешь какую-то регрессию при обновлении каких-то зависимостей.

MikeyTide Автор
04.12.2025 08:57Я с вами полностью согласен и не претендую на гуру модулей. Просто показал с чего начал, для чего была необходимость, а модуль появился уже для другой задачи, и решил весь путь объединить в статью.
Конечно, буду разбираться с модулями и автотестами благодаря сообществу!
Ну и вам спасибо, что хоть и критикуете, но говорите о правильных вещах и по делу.
mrkrivedko
Коллега приветствую, у меня был как раз опыт взаимодействия с sssd через dbus. Важно отметить, что для infopipe потребуются привелегии.
вот полезная ссылка по взаимодействию с sssd через dbus
По поводу преобразованию
.в_2eэто происходит согласно спецификации DBus, где допустимый объектный путь должен выполнять правило: "Каждый элемент должен содержать только символы ASCII "[A-Z] [a-z] [0-9]"", поэтому в $domain если содержится символ он будет экранирован в<hex-код ascii>.Собственно чтобы узнать список доменов на машине дергаем метод
org.freedesktop.sssd.infopipe.ListDomains(),а далее можно работать с валидными доменами.Также dbus удобно смотреть через
busctl:busctl tree org.freedesktop.sssd.infopipebusctl introspect org.freedesktop.sssd.infopipe /org/freedesktop/sssd/infopipe/Domains/$domainДля отслеживания dbus можно использовать gui утилиту
qdbusviewerОна находится в пакете
qttools5-dev-tools(для Астралинукса так называется)MikeyTide Автор
Супер! Спасибо что подробно и по теме.
Благодарю за информацию