MAATRIX / Блог / Ansible-плейбуки под отечественные ОС: что придётся переписать

Ansible-плейбуки под отечественные ОС: что придётся переписать

MAATRIX

Плейбук, который два года без единого сбоя разворачивал сервисы на Ubuntu и Debian, на первом же прогоне против Astra Linux или РЕД ОС падает на банальном apt: модуль отработал не так, как ожидалось, или вовсе не нашёл того, что искал. Дело не в кривых руках — отечественные дистрибутивы достаточно похожи на привычные Debian и RHEL, чтобы создать иллюзию совместимости, и достаточно другие, чтобы эта иллюзия разбилась о пакетный менеджер, пути к конфигам или facts, которые Ansible собирает не так, как вы привыкли. Разберём, что обычно приходится переписывать при переносе плейбуков на Astra Linux, РЕД ОС и ALT Linux, и как с самого начала писать автоматизацию так, чтобы такой перенос не превращался в переписывание с нуля.

Три семейства — три логики пакетного менеджера

Первое, на чём спотыкается практически любой перенесённый плейбук — это управление пакетами. У трёх популярных отечественных дистрибутивов разная база, и это не косметическое отличие:

  • Astra Linux собрана на базе Debian, использует dpkg и apt в привычном виде — здесь модуль apt из ansible.builtin в большинстве задач работает так же, как на Ubuntu и Debian.
  • РЕД ОС построена на базе RHEL-совместимой линейки, пакеты в формате RPM, менеджер пакетов — семейство yum/dnf в зависимости от версии дистрибутива. Задачи, написанные через модуль apt, здесь не сработают в принципе — нужна ветка на yum/dnf.
  • ALT Linux — отдельный случай, о который спотыкаются даже те, кто уже разделил плейбук на «Debian-ветку» и «RPM-ветку». Пакеты в ALT в формате RPM, но пользовательский интерфейс управления пакетами исторически сделан через apt (связка apt-rpm) — то есть команда в терминале называется apt-get, а формат пакета и логика зависимостей — от RPM-мира. Модуль ansible.builtin.apt, рассчитанный на dpkg/apt в debian-смысле, здесь не подойдёт, а обычный yum/dnf-модуль — тоже, потому что низкоуровневый бэкенд другой. Для таких систем стоит смотреть в сторону специализированных модулей коллекции community.general, ориентированных именно на apt-rpm-системы — но точное название модуля и его поддержку под вашу версию ansible-core и коллекции нужно свежо проверять в официальной документации, а не переносить из старой статьи: в разных версиях коллекций такие модули появлялись, переименовывались и меняли поведение.

Практический выход, который снимает часть боли сразу — универсальный модуль ansible.builtin.package там, где вам достаточно «поставить/удалить пакет по имени» без тонких настроек репозитория: он сам определяет нужный бэкенд через факты хоста. Но package не умеет всё — управление репозиториями, GPG-ключами, придержанием версий (hold) всё равно требует модуля под конкретный менеджер, и ветвление здесь неизбежно. Если базовый Ansible-стенд ещё не настроен, азы разобраны в статье про установку и настройку Ansible для сервера.

Пути и структура конфигов не всегда совпадают с «родительским» дистрибутивом

Соблазн думать «Astra — это же Debian, значит, все пути такие же» понятен, но справедлив лишь отчасти. Базовая FHS-структура (/etc, /var, /usr) действительно наследуется от родительского дистрибутива, и большинство стандартных сервисов — nginx, PostgreSQL, systemd-юниты — кладут конфиги туда же, где вы их искали бы на Debian или RHEL. Но там, где отечественный дистрибутив добавляет собственную функциональность — подсистемы мандатного доступа, средства защиты информации, свои механизмы аудита — появляются дополнительные каталоги и файлы конфигурации, которых в апстриме нет, и о которых плейбук под ванильный Debian ничего не знает.

Есть и обратная ловушка: путь, «стандартный для RPM-систем» и одинаковый на CentOS и AlmaLinux, может отличаться в РЕД ОС из-за версии дистрибутива или собственной сборки компонента, которую вендор мог перепаковать со своими патчами. Совет простой, но его часто игнорируют: не зашивайте путь в задачу «потому что так было на референсном дистрибутиве» — проверяйте его на целевой системе. Задача с модулем ansible.builtin.stat перед основной операцией стоит пары секунд и экономит часы отладки в проде.

Если вы мигрируете конкретно на Astra Linux с Ubuntu-инфраструктуры, полезно заранее свериться со списком типичных мест, которые ломаются при таком переезде — это разобрано в статье что ломается при переезде с Ubuntu на Astra Linux.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Имена пакетов не совпадают один в один

Даже когда дистрибутив явно принадлежит знакомому семейству, имя конкретного пакета — не гарантированная величина. Один и тот же софт может быть упакован под другим именем, разбит на другой набор подпакетов (базовый пакет отдельно от библиотек или документации не так, как вы привыкли), либо вообще не входить в базовый репозиторий и требовать отдельного «вендорского» репозитория. Бывает и обратное — пакета, доступного через привычный менеджер на Ubuntu, в репозиториях отечественной ОС попросту нет, и его нужно собирать из исходников или искать альтернативу в реестре отечественного ПО.

Здесь принцип важнее конкретного списка: не переносите имя пакета из тьюториала под Ubuntu без проверки. Загляните в каталог пакетов дистрибутива (у каждого вендора есть свой поисковик по репозиторию) до того, как писать задачу, а не после того, как плейбук упал в проде. Это не гарантирует, что имя не изменится в следующей версии дистрибутива, но снимает большинство ошибок «пакета с таким именем не существует» на старте.

Технически это решается через вынесение имён пакетов в переменные, а не хардкод в самой задаче:

# vars/Debian.yml
web_server_package: nginx
db_client_package: postgresql-client

# vars/RedHat.yml
web_server_package: nginx
db_client_package: postgresql
- name: Подключить переменные по семейству ОС
  ansible.builtin.include_vars: "{{ ansible_facts['os_family'] }}.yml"

- name: Установить веб-сервер
  ansible.builtin.package:
    name: "{{ web_server_package }}"
    state: present

Такой подход не избавляет от необходимости знать реальные имена пакетов, но переносит развилку в один файл переменных вместо условий when: ansible_facts['distribution'] == '...', разбросанных по десяткам задач.

Facts: как эти системы себя называют — не всегда предсказуемо

Ansible определяет дистрибутив через сбор фактов (ansible_facts), которые опираются на /etc/os-release и смежные файлы-маркеры. Для мейнстримных дистрибутивов это работает предсказуемо: Ubuntu, Debian, CentOS годами поддерживают этот файл в консистентном виде, и community-модули тестируются в первую очередь на них.

С отечественными дистрибутивами гарантии тоньше. Astra Linux, РЕД ОС и ALT Linux — самостоятельные дистрибутивы со своими версиями и релизными циклами, а не просто «пересобранный Debian» или «пересобранный RHEL», и то, что попадает в os-release, зависит от конкретной сборки и версии. Иногда факты ansible_facts['distribution'] и ansible_facts['os_family'] возвращают ровно то, что вы ожидаете исходя из родительской базы, иногда — нет, особенно на специфично собранных редакциях. Полагаться на память или на статью под другую версию дистрибутива рискованно — значения нужно проверять эмпирически на конкретном хосте, а не предполагать по аналогии.

Самый надёжный способ — прогнать сбор фактов вручную против реального хоста до того, как писать логику ветвления:

ansible target-host -m ansible.builtin.setup -a "filter=ansible_distribution*"

и посмотреть, что возвращается в ansible_distribution, ansible_distribution_release, ansible_os_family и ansible_pkg_mgr. Дальше стройте условия на основе того, что реально увидели, а не того, что «должно быть» по аналогии с родительским дистрибутивом.

Важный нюанс: os_family — удобная крупная группировка («похоже на Debian» / «похоже на RedHat»), и для большинства задач с пакетами и сервисами её достаточно. Но там, где поведение расходится внутри одного семейства сильнее, чем эта группировка предполагает — как с ALT Linux, где os_family может указывать на RPM-мир, а фактический пакетный интерфейс ведёт себя иначе — одного os_family недостаточно, в условие стоит добавлять явную проверку ansible_distribution.

Мандатный доступ и другие security-надстройки ломают привычную идемпотентность

Этот раздел чаще всего упускают из виду, потому что проблема не проявляется на этапе «плейбук вроде отработал без ошибок» — а проявляется позже, при проверке фактического состояния защиты. Специализированные редакции вроде Astra Linux Special Edition добавляют поверх обычных дискреционных прав (rwx, владелец, группа) собственный слой мандатного контроля доступа. Стандартные модули Ansible — file, copy, user — управляют классическими DAC-атрибутами и ничего не знают про мандатные метки этого слоя. Формально задача отработает успешно и Ansible покажет changed или ok, но реальное состояние защиты системы при этом может остаться не тем, что нужно, потому что модуль не умеет работать с этим измерением прав доступа.

Если целевая система требует настройки мандатных меток, эту логику приходится добавлять отдельными задачами через command/shell, вызывающими нативные утилиты подсистемы. Подвох в том, что такие задачи не идемпотентны сами по себе — Ansible не понимает, было ли состояние уже нужным, если вы не оборачиваете вызов в собственную проверку (changed_when, предварительный command с разбором вывода). Задача «в лоб», без проверки состояния, при каждом прогоне будет отчитываться как «изменено», даже когда менять уже нечего — это ломает читаемость отчётов и саму идею идемпотентности. Подробный разбор конкретных грабель мандатного доступа на Astra Linux есть в статье про мандатное разграничение доступа Astra Linux.

Второй похожий момент — режим замкнутой программной среды, если он включён на целевом хосте. В этом режиме система по умолчанию блокирует запуск неподписанных или не входящих в разрешённый список исполняемых файлов, включая то, что плейбук пытается скопировать и запустить в рамках задачи. Плейбук, отлично работающий на «ванильном» тестовом стенде без такого режима, на проде с включённой замкнутой средой может тихо упасть именно на этом шаге — разбираться с причиной по факту инцидента куда дороже, чем протестировать сценарий заранее. Это разобрано в статье про замкнутую программную среду Astra Linux на практике. Не отключайте эти механизмы ради удобства тестирования — если плейбук падает именно из-за них, это сигнал добавить явную обработку, а не повод выключать защиту, ради которой систему и выбирали.

Как писать более портируемые плейбуки с самого начала

Часть боли переноса снимается не на этапе миграции, а заранее — если изначально закладывать в плейбук предположение, что он может запуститься не только на Ubuntu:

  • Используйте generic-модули по умолчанию. ansible.builtin.package и ansible.builtin.service абстрагируют менеджер пакетов и init-систему через факты хоста. Переходите на модуль конкретного семейства (apt, yum, dnf) только там, где нужна функциональность, которой в generic-модуле нет.
  • Выносите отличия в переменные, а не в условия внутри задач. Файлы вида vars/<os_family>.yml, подключаемые через include_vars: "{{ ansible_facts['os_family'] }}.yml", централизуют развилку в одном месте вместо when:-условий, разбросанных по плейбуку. Если внутри одного os_family есть системы с более тонкими различиями (случай ALT Linux в RPM-мире), добавляйте отдельный слой переменных по ansible_distribution.
  • Не гадайте о совместимости модуля — проверяйте эмпирически. Community-коллекции тестируются в первую очередь на мейнстримных дистрибутивах; то, что модуль работает на последней Ubuntu, не гарантирует того же на Astra, РЕД ОС или ALT. Прогоняйте задачи на реальном экземпляре целевой ОС, а не полагайтесь на документацию модуля как на единственный источник истины.
  • Стройте тестовую матрицу под реальные целевые ОС. Если инфраструктура смешанная, держите хотя бы по одному тестовому хосту под каждую реально используемую ОС и прогоняйте плейбук против каждой перед выкладкой в общий инвентарь.
  • Тестируйте против правильной группы хостов. Отдельная и обидная категория ошибок — плейбук написан и протестирован корректно, но прогнан не на той группе инвентаря, и правки, рассчитанные на тестовый контур, улетают на прод другой ОС. Разбор этой ошибки — в статье что делать, если плейбук прогнали не на той группе хостов.
  • Документируйте решения по каждой ОС в репозитории. Короткий комментарий в vars/-файле — какое имя пакета выбрано и почему, какой модуль используется и на какой версии коллекции проверен — экономит время следующему инженеру.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли написать один универсальный плейбук сразу под все три дистрибутива, или лучше отдельные?

Один плейбук вполне реален, если развилка вынесена в переменные и generic-модули, а не размазана условиями по каждой задаче. Отдельные плейбуки оправданы, если системы расходятся не только в пакетах, но и в логике развёртывания — тогда общий плейбук с десятком условий читается хуже, чем два простых.

Модуль apt подойдёт для ALT Linux, раз там тоже команда называется apt-get?

Нет — совпадение имени команды в терминале не означает совпадения бэкенда, на который рассчитан модуль. ansible.builtin.apt написан под dpkg/Debian-семантику; для apt-rpm систем нужен либо generic-модуль package, либо специализированный модуль — уточняйте актуальное название и поддержку в документации используемой версии коллекции community.general.

Как проверить, что плейбук действительно идемпотентен на новой ОС, а не просто «не упал»?

Прогоните его дважды подряд и сверьте отчёт: во втором прогоне changed должно быть равно нулю. Особое внимание — задачам через command/shell для управления мандатным доступом: они не идемпотентны без явной проверки состояния.

Стоит ли на тестовом стенде отключать мандатный доступ и замкнутую программную среду, чтобы плейбук проще проходил?

Не стоит — это спрячет проблему до прода, где эти механизмы включены и отключать их обычно нельзя. Лучше тестировать сразу с включёнными механизмами и добавлять явную обработку того, что они блокируют.

С чего начинать перенос плейбука, если инфраструктура смешанная — часть на Ubuntu, часть переезжает на отечественную ОС?

С вынесения имён пакетов, путей и версий сервисов в переменные по os_family/distribution, даже если весь парк пока на одной ОС. Дальше — один представительный тестовый хост новой ОС в отдельной группе инвентаря и постепенное расширение после того, как повторные прогоны стабильно показывают нулевые изменения.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →