Серия «Кудахтеры: Ansible»

13

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу

Для лиги лени: какое-то пособие для совсем отсталых
Ansible для детского сада в скольки то частях. Часть 1.Про все сразу.
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS
Ansible для детского сада в скольки то частях. Часть 3 Безопасность

Для кого этот текст, и про что тут. Избыточно длинное введение

Ситуация «Я и Ansible» кому-то может показаться странной, кому-то обычной. Я его использую, много кто использует, и много кто использует гораздо лучше меня. И, при этом, много кто не использует, или не использует для каких-то очевидных для меня задач. Какие-то базовые вопросы ставят меня в тупик.
По абсолютно не понятным для меня причинам, Ansible, Terraform, прочие инструменты, время от времени записывают в «инструменты девопс». Это странно, потому что Ansible самый обычный инструмент, применяйте куда хотите. Хотя в русскоязычном сообществе почему то сменилась терминология, devops +-= linux system administrator, даже если у вас нет ни CI, ни CD, ни SDLC и тем более SSDLC, хотя бы на базовом уровне Microsoft SDL.
Поэтому я подумал, подумал, и решил написать одну, может две, статьи по теме, заодно в своей голове уложу какие-то вещи.

В ходе написания статьи выяснилось интересное, про Opensource в целом, как движение и .. образ мысли? Возможно, это абсолютно неправильная точка зрения, но .. GNU/Linux, точнее GNU как движение, точнее The Open Source Initiative / The Open Software Foundation, Inc. (OSF) , The Apache Software Foundation не умирают, но изменяются и трансформируются, и делают это каким-то путем.. Повелителя перемен. Ну хорошо хоть не Слаанеш, хотя местами чемпионы сразу двух.

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу

Изначальное движение «кто-то пишет код, кто-то проверяет код» постепенно закончилось лет 10-15 назад. Критическими точками стали:
18 апреля 2016, с выходом статьи The Linux scheduler: a decade of wasted cores. Полный текст тут
9 декабря 2021, когда в Log4j выявили CVE-2021-44228. Не замеченную ни корпоративным миром, ни Apache Software Foundation с 2013 года. И это еще ничего, сейчас пошла волна вайб кодинга, там просто караул.

Причины "исхода" понятны – повсеместное внедрение тайм трекеров, систем управления, рост сложности кода, etc, не дают возможности массово заниматься «чем-то еще», и вклад в сообщество не монетизируется. Разработчики и команды забрасывают проекты, достаточно вспомнить RatticDB и Ralph, и последние движения везде.
В том числе поэтому страдает не только сам код, но и документация и сценарии к нему. Особенно статьи, написанные исключительно в стиле «как надо» и «как у меня получилось». Сценарии «как у меня не получилось и почему» оседают в глубинах форумов или (запрещенная в РФ социальная сеть) групп. Поэтому, иногда проще сделать самому «как не надо», сломать раз 10, написать «так точно не работает», и написать «как работает у меня и почему».
Мне почему-то проще так, через «сломай сам и напиши что сломал».

Теперь к теме

Важно: Вышло обновление 2.19, и вышло давно! :

Ansible Core 2.19 was officially released on July 21, 2025.

Important: The ansible-core 2.19/Ansible 12 release has made significant templating changes that might require you to update playbooks and roles. The templating changes enable reporting of numerous problematic behaviors that went undetected in previous releases, with wide-ranging positive effects on security, performance, and user experience. You should validate your content to ensure compatibility with these templating changes before upgrading to ansible-core 2.19 or Ansible 12. See the porting guide to understand where you may need to update your playbooks and roles. ROADMAP.

Теоретическая часть

В чем плюс Microsoft Active Directory Domain Services (MS AD DS) ? Он просто работает. Я про это уже писал вот тут (SSSD или приключения Linux в домене Windows (MS AD)) и повторяться не нужно. И он документирован сотнями примеров и миллионами пользователей.

В чем минусы Ansible? Точнее, в чем минусы, если его не настраивать.

Первый минус.
Можно сказать, что очевидный минус в том, что это не pull система.
Хотя в документации и есть раздел Ansible-Pull, цитата:

You can invert the Ansible architecture so that nodes check in to a central location instead of you pushing configuration out to them. Ansible-Pull

То есть, каждый включенный в MS AD сервер «сам» ходит за новыми настройками, и «сам» их обновляет.
Для Ansible можно при первом проходе добавить в CRON
git pull && ansible-playbook
Но это не наш метод. И все равно, нужна система типа Netbox или хотя бы php ipam или хотя бы, если вы совсем немощный, GLPI - Gestionnaire libre de parc informatique.
GLPI, как оказалось, неплохой проект. Для целей именно ИТ в серверному сегменте, и управлению конфигурациями пригоден меньше, чем Netbox, но коллеги из РФ прикручивали к GLPI костыли, и кое как это ездило. Даже какое-то русское сообщество есть. Ralph, говорят, тоже неплох. Был.
Ralph 3 - Asset Management / CMDB
Github allegro /ralph

Цитата

Discontinued

"Effective January 1, 2024, all development and maintenance activities associated with the Ralph project on Allegro's GitHub will be discontinued. This means that no further updates or modifications will be applied to the codebase. However, the current version of the code available in the repository will remain the property of the community, allowing individuals to continue contributing to and developing Ralph as they deem appropriate."

As Allegro, we continue to publish Ralph's source code and will maintain its development under a "Sources only" model, without guarantees. We believe this approach will be beneficial, allowing everyone to use and build upon the software. Our current goal is to modernize the software and ensure its long-term maintainability, and we're investing into it in 2025.

However, we are not operating under a contribution-based model. While we welcome discussions, we do not guarantee responses to issues or support for pull requests. If you require commercial support, please visit http://ralph.discourse.group.

We sincerely appreciate all past contributions that have shaped Ralph into the powerful tool it is today, and encourage the community to continue using it.
README.md


IT Asset Management нужен, но это система управления, а не система настройки. Хотя, может и к ней есть какой-то готовый модуль выгрузок, или его можно написать, именно как модуль.

Вторая минус, с которым я, может быть, попробую разобраться, хотя меня до вчера она не волновала, это AAA, точнее какую учетную запись использует Ansible, и как он это делает. От рута все запускать, конечно, легко. Один рут везде, один пароль. Удобно.

Второй с половиной минус, это ограничения по исполнению для разных типов учетных записей и прочего sudoers .

Вопрос использования Ansible, скорее, относится к координации отделов. Если отдел ИТ един, и при создании очередной виртуальной машины, в облаке или на земле, VM или сервис добавляется в Ansible, или изначально VM и физика добавлены, или есть автоматическое обновление, то проблемы нет.
Но ситуация «забыли добавить» и бесконтрольных Windows машин касается. Если Windows машину в домен не добавить, то волшебным образом она там не появится, и на локальном WSUS отмечаться не будет.

Отсутствие контроля плохо что так, что эдак.

Что дает еще одно направление для инвентаризации, про которое я как-то раньше не думал, но про это напишу статью - Инвентаризация инфраструктуры и сети. Пометки для начинающих. Черновик заведен, дальше как время будет получится.

Развертывание Ansible

Про это я уже писал в статьях:
Переход на Proxmox Часть 3. Ansible и
Переход на Proxmox Часть 6. Возвращаемся к запуску Ansible.

После всех тестов у меня остался:
Кое-как настроенный Ansible
Никак толком не настроенный Linux с SSSD
Git, просто Git. Причем в контейнере

Ansible AWX у меня не заработал. Не очень и хотелось. Можно было поиграть в семафор.

В репозитории  AWX Project написано, цитата:

The last release of this repository was released on Jul 2, 2024. Releases of this project are now paused during a large scale refactoring.

Event-Driven Ansible has been a huge success, but we’ve noticed the same limitations with our existing architecture when trying to combine it with this new system. We need Jobs to be able to report their status in different ways and in different places. We need credentials and projects to be usable in a secure way by this system. Inventory management for Ansible today isn’t just about Servers or Containers, they are about Cloud and Service APIs as well as Embedded and Edge capabilities.
Upcoming Changes to the AWX Project

Размер VM для тестов

Судя по тестовой VM, для связки Nexus + Gitlab + Ansible – 4 Гб на одной VM не достаточно.
30% памяти забирает Java, 20% - Ruby, остальное расходится туда-сюда.
5 Гб минимум для тестов в моей конфигурации. Это не самая большая сложность, когда на нормальном ноутбуке уже 64 Гб памяти (на топовых уже по 96 Гб оперативной памяти).

Проверяем легаси конфиг Ansible
Делаем: ansible --version
Получаем:
ansible --version == ansible [core 2.18.7]; config file = /etc/ansible/ansible.cfg

Делаем: ansible-inventory --list -y
Получаем, цитата:

all:
children:
proxmox:
hosts:
192.168.1111.1111:
ansible_python_interpreter: /usr/bin/python3

servers:
hosts:
server1:
ansible_host: 192.168.1111.2222
ansible_port: 2424
ansible_python_interpreter: /usr/bin/python3

(я в курсе, что IP 192.168.1111.1111 принципиально неправильный, но мне лень как-то иначе переписывать данные реального стенда)

Посмотрим что там:
nano /srv/ansible/hosts.ini
Как оказалось, /srv/ansible/hosts.ini – это какой-то недоделанный файл из какой-то старой инструкции. Не нужен
nano /etc/ansible/hosts

Получаем тот же конфиг, что выше, но в формате конфига:

[servers]
server1 ansible_host=192.168.1111.1111

[servers:vars]
ansible_port=2424

[proxmox]
192.168.1111.2222

[all:vars]
ansible_python_interpreter=/usr/bin/python3

Не знаю, можно ли назвать это вообще конфигом, или минимальным конфигом для работ, но мне хватает. Точно так же, я не уверен, что надо записывать сервера по IP, а не по DNS имени, или не как-то иначе. Точнее, уверен что лучше по имени, но можно и так.

2424 – это у меня SSH переставлен, в /etc/ssh/sshd_config. Потому что на 22 порту этой VM живет gitlab в docker. Можно было наоборот.

В интернетах пишут то, что мне нравится, то есть: используйте FQDN. цитата:

I tend to disregard the commonly accepted best practice of using short names for hosts, and use FQDNs instead. In my opinion, it has many advantages.
It makes it impossible for host names and group names to collide.
Creating an additional variable to carry the host's expected FQDN becomes unnecessary.
It is immediately obvious which (actual) host caused some failure.
Ansible naming conventions

В Ansible docs пишут то же самое: Inventory basics: formats, hosts, and groups

Но можно использовать и алиасы: раздел Inventory aliases в документации.

Что имеем перед началом движения:
не обновленный Ansible с простейшей конфигурацией.
Еще раз напоминаю, что в 2.19 (у меня ansible [core 2.18.7] поведение поменялось.

Обновиться в лабе просто так можно, но нельзя, через:
pip install --upgrade ansible-core
Можно выстрелить себе в ногу, потому что:
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.

ansible 11.8.0 requires ansible-core~=2.18.7, but you have ansible-core 2.19.2 which is incompatible.
Successfully installed ansible-core-2.19.2


Придется обновлять целиком:
python3 -m pip install --upgrade --user ansible

Found existing installation: ansible 11.8.0 ; Successfully installed ansible-12.0.0

Такое, конечно, только в лабе и можно делать, без бекапа, без плана отката, вообще без ничего.

Переходим к фактам, просто потому что они мне были нужны
Самая простая команда для сбора фактов -
ansible all -m setup
Читается просто.
Ansible – понятно.
All – группа по умолчанию

Дальше интересно, потому что «-m» описан в Ansible ad hoc commands, как
$ ansible [pattern] -m [module] -a "[module options]"

Там же описана параллельное исполнение команд, цитата:

By default, Ansible uses only five simultaneous processes. If you have more hosts than the value set for the fork count, it can increase the time it takes for Ansible to communicate with the hosts. To reboot the [atlanta] servers with 10 parallel forks

Кто подскажет, почему я тупой, и нашел перечень в выводе man ansible
и не нашел какой-то подробной статьи в интернетах?

-m == module; в том числе -m ansible.builtin.setup , что равно -m setup
-a == command
-u username == user
--ask-become-pass ; -K; --ask-pass – запрос пароля;
--check == Running playbooks in check mode
-i; --inventory == файл с перечнем хостов
-e; ‐‐extra-vars
-l; ‐‐limit


man ansible

usage: ansible [-h] [--version] [-v] [-b] [--become-method BECOME_METHOD] [--become-user BECOME_USER] [-K | --become-password-file BECOME_PASSWORD_FILE]
[-i INVENTORY] [--list-hosts] [-l SUBSET] [--flush-cache] [-P POLL_INTERVAL] [-B SECONDS] [-o] [-t TREE] [--private-key PRIVATE_KEY_FILE]
[-u REMOTE_USER] [-c CONNECTION] [-T TIMEOUT] [--ssh-common-args SSH_COMMON_ARGS] [--sftp-extra-args SFTP_EXTRA_ARGS]
[--scp-extra-args SCP_EXTRA_ARGS] [--ssh-extra-args SSH_EXTRA_ARGS] [-k | --connection-password-file CONNECTION_PASSWORD_FILE] [-C] [-D]
[-e EXTRA_VARS] [--vault-id VAULT_IDS] [-J | --vault-password-file VAULT_PASSWORD_FILES] [-f FORKS] [-M MODULE_PATH]
[--playbook-dir BASEDIR] [--task-timeout TASK_TIMEOUT] [-a MODULE_ARGS] [-m MODULE_NAME]

Проблема такого опроса лишь в том, что это состояние «на сейчас, для всех». Поэтому, если у вас удаленный сервер выключен, или не настроен, или что-то еще, то вы получите, цитата

server1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh

И еще, для моего сценария «ничего не настроено», нужно делать не
ansible all -m setup
а
ansible all -m setup --ask-pass

Или индивидуально для группы серверов:
ansible proxmox -m setup --ask-pass
не говоря про остальное написанное в прошлых текстах:
ansible proxmox -m command -a "uptime" --ask-pass
ansible proxmox -m command -a "pveversion" --ask-pass
ansible proxmox -m command -a "echo ' Hello World '" --ask-pass

И тогда, в ответ на
ansible all -m setup --ask-pass
вы получите не только
(IP) | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [

Но и огромную xml выгрузку по массе параметров.

Дальше нужно переходить к использованию фильтров
вместо ansible all -m setup --ask-pass
запросить ansible all -m setup -a "filter=ansible_cmdline" --ask-pass

и, кроме использования фильтров, переходить на использование групп, где proxmox – это группа, описанная в /etc/ansible/hosts, и строка будет выглядеть как:

ansible proxmox -m setup --ask-pass -a "filter=ansible_cmdline" --ask-pass

Или придется вспоминать базовейший баш, и выгрузить все полученное в файл,
ansible proxmox -m setup --ask-pass > ansible_out_01.txt

Внутри выгруженного файла посмотреть список того, что можно получать в фильтрах, например:
ansible_all_ipv4_addresses
ansible_default_ipv4
ansible_uptime_seconds
ansible_kernel

И получать именно их:
ansible proxmox -m setup -a "filter=ansible_uptime_seconds" --ask-pass

С выводом:
"ansible_facts": {
"ansible_uptime_seconds": 3416

Переходим к первому плейбуку
nano /home/user/uptime_report.yml

Плейбук целиком утащен из интернета или даже написан AI: цитата:

---

- name: Get and display system uptimes
hosts: all
gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds
tasks:
- name: Display uptime for each host
ansible.builtin.debug:
msg:
Hostname: {{ inventory_hostname }}
Uptime: {{ (ansible_uptime_seconds / 86400) | int }} days,
{{ ((ansible_uptime_seconds % 86400) / 3600) | int }} hours,
{{ (((ansible_uptime_seconds % 86400) % 3600) / 60) | int }} minutes,
{{ (((ansible_uptime_seconds % 86400) % 3600) % 60) | int }} seconds
when: ansible_uptime_seconds is defined

Выполним что получилось. Без всякой фильтрации хостов и без ничего.

ansible-playbook uptime_report.yml --ask-pass --user root

Получим 1 [ERROR]: Task failed: Failed to connect to the host via ssh:, и  получим 1 ОК.

Ansible playbook для первой группы хостов
На следующем этапе первым делом надо разобраться вот с чем.
Команда:
ansible-playbook -i /path/to/your/hosts_file.ini your_playbook.yml
позволяет работать со списком хостов и групп хостов в hosts_file.ini
по умолчанию это /etc/ansible/hosts

Очень хочется сделать по образцу:
Вместо: ansible proxmox -m setup -a "filter=ansible_uptime_seconds" --ask-pass
сделать:
ansible-playbook proxmox uptime_report.yml --ask-pass --user root

Так работать не будет!

man ansible-playbook

NAME
ansible-playbook - Runs Ansible playbooks, executing the defined tasks on the targeted hosts.
SYNOPSIS
usage: ansible-playbook [-h] [--version] [-v] [--private-key PRIVATE_KEY_FILE]
[-u  REMOTE_USER]  [-c  CONNECTION]  [-T TIMEOUT] [--ssh-common-args SSH_COMMON_ARGS] [--sftp-extra-args SFTP_EXTRA_ARGS] [--scp-extra-args
SCP_EXTRA_ARGS]  [--ssh-extra-args  SSH_EXTRA_ARGS]  [-k  |  --connection-password-file  CONNECTION_PASSWORD_FILE]  [--force-handlers]
[--flush-cache]  [-b]  [--become-method  BECOME_METHOD]  [--become-user BECOME_USER] [-K | --become-password-file BECOME_PASSWORD_FILE] [-t TAGS] [--skip-tags SKIP_TAGS] [-C] [-D] [-i INVENTORY] [--list-hosts] [-l SUBSET] [-e EXTRA_VARS] [--vault-id VAULT_IDS] [--ask-vault-pass‐

word  |  --vault-password-file  VAULT_PASSWORD_FILES]  [-f  FORKS]  [-M MODULE_PATH] [--syntax-check] [--list-tasks] [--list-tags] [--step]

[--start-at-task START_AT_TASK] playbook [playbook ...]

Значит, что остаётся делать?
Остается читать: Ioannis Moustakis Ansible Inventory (есть русский перевод от slurm: Изучаем Ansible Inventory: основы и примеры использования)

Делаем: ansible-playbook uptime_report.yml --ask-pass --user root --inventory proxmox
И ничего подобного, ТАК НЕ РАБОТАЕТ

казалось бы, очевидный лично для меня вариант, когда команда исполняется для какой-то выборки из группы хостов в файле «по умолчанию».
Ничего подобного. Будьте добры делать, как положено:
ansible-playbook uptime_report.yml --ask-pass --user root --inventory  /etc/ansible/hosts  --limit “proxmox”

То есть, конечно, можно и покороче:
ansible-playbook uptime_report.yml --ask-pass --user root --limit “proxmox”


но через limit, если вы хотите «чтобы бралось по умолчанию, но не все».
Лично мне вот это было совершенно не очевидным моментом, что это делается именно так.
Проблема роста – когда ты имеешь развитую инфраструктуру, где все это давно используется, и закопано в наслоениях легаси, можно сделать по образцу и, скорее всего, будет работать. Когда ты пытаешься разобраться, как и почему сделано вот так, это уже совсем другое.

Ну да ладно.
Скопирую конфиг себе и буду пробовать
cp /etc/ansible/hosts /home/user/1st_hosts.ini

Удалю лишнее, оставлю только

[proxmox]
192.168.1111.2222

[all:vars]
ansible_python_interpreter=/usr/bin/python3

И сделаю
ansible-playbook uptime_report.yml --ask-pass --user root --inventory  /home/user/1st_hosts.ini

И все отлично, и все работает.

Заключение.

Система как система, ничем не хуже другой. Первый час сложно, ничего не понятно. Потом попроще.

Литература
Переход от SDLC к SSDLC
GLPi
Ansible AWX
Upcoming Changes to the AWX Project
Classic SysAdmin: Configuring the Linux Sudoers File

Ansible docs Discovering variables: facts and magic variables
Ansible docs Run Your First Command and Playbook
Redhat An introduction to Ansible facts
Specify hosts in ansible-playbook command line
stackoverflow  How to list all currently targeted hosts in an Ansible play

Ioannis Moustakis Ansible Inventory (есть русский перевод от slurm: Изучаем Ansible Inventory: основы и примеры использования)

@editors, мне бы теги Ansible и CMDB, пожалуйста! Не работает их добавление у меня, ничего не могу с этим сделать!

Показать полностью 1
13

Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS

Почему я вообще пишу эту статью? Почему нет готового решения «делайте хорошо, плохо не делайте»?
Как жесток и несправедлив этот мир!

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS - Linux Server Update Services (LSUS)
Ansible для детского сада в скольки то частях. Часть 3. Настраиваем подобие безопасности и все остальное
Ansible для детского сада в скольки то частях. Часть 4. Приделываем костыли

Рассуждения про LSUS - Linux Server Update Services

Я вообще очень удивлен не тому, что в опенсорс инсталляциях творится бардак похуже, чем в  Windows среде. Больше меня удивляет то, что комьюнити с момента появления Linux, а это 17 сентября 1991 года, не сделало какого-то документа «делать так точно хорошо».
У Microsoft был Baseline Security Analyzer
У Microsoft есть Security Compliance Toolkit (SCT)
У Microsoft есть Azure Update Manager operation(AUM).

В опенсорсе был Spacewalk. Последний релиз - 2.10 / March 18, 2020
У RH был Satellite. Это Foreman + katello+ support. Foreman 3.16 and Katello 4.18
Ivanti Patch for Endpoint Manager ? Ага, цитата

Release DateSeptember 18, 2025  The U.S. Cybersecurity and Infrastructure Security Agency (CISA) has published an analysis of the malware deployed in attacks exploiting vulnerabilities affecting Ivanti Endpoint Manager Mobile (EPMM). The Cybersecurity and Infrastructure Security Agency (CISA) obtained two sets of malware from an organization compromised by cyber threat actors exploiting CVE-2025-4427 and CVE-2025-4428 in Ivanti Endpoint Manager Mobile (Ivanti EPMM). Each set contains loaders for malicious listeners that enable cyber threat actors to run arbitrary code on the compromised server. Malicious Listener for Ivanti Endpoint Mobile Management Systems

Rudder ? Ничего про него не знаю.

По беглому обзору, Katello и его функции Lifecycle Ennvironments Content View, выглядят достаточно интересно.

Но единственная «быстро» найденная статья на русском по недоразумению лежит на портале Минцифры, и не содержит описания работы Katello-agent. Который должен был быть, цитата:

Katello-agent is deprecated and will be removed in the next release. Transition your workloads to use the remote execution feature. Red Hat Satellite 6.9

Есть статья на английском. Katello and Foreman in the process of patch management. Картинок достаточно, перевод сейчас в браузере есть.

Проект (Foreman 3.16 and Katello 4.18) выглядит интересным, поскольку содержит интеграцию с Ansible, Configuring hosts by using Ansible, и это позволяет не тащить туда-сюда агенты. Но его установка и интеграция с Ansible и отзывы на него, в целом, неоднозначные.
На официальном сайте есть новость:
betadots GmbH joins the group of companies that provide professional services for Foreman!

Так что вопрос в сложности развертывания, это совсем не WSUS с его далее – далее - готово – пропишите WSUS в GPO.

Рассуждения про структуру LSUS - Linux Server Update Services

Какую задачу я решаю? Наверное, я пытаюсь доказать сам себе, что чего-то упускаю. Не может же быть такого, что нет инструмента для сбора нужной мне статистики в отрыве от системы управления или системы инвентаризации. Которая мне просто и легко сгенерирует таблицу сервер – ядро – версия основного приложения – аптайм и покажет «пока обновляться».
Самый простой вариант – сделать самому. Заодно питон вспомню.

LSUS Фаза 1. Тащим данные
LSUS Фаза 2. ?
LSUS Фаза 3. Profit

Забрать данные из Ansible facts проще всего в текстовый файл любой структуры. Хотите xml \ yaml, хотите что угодно.
Получить эталонные данные проще всего из эталонного образца нужного дистрибутива. У меня везде Debian разных версий, вот на них и буду опираться. Потому что в закрытом или частично (с прокси репозиториями) контуре внешние данные с какого-то сайта \ репозитория не получить.
Хотя и можно на проверяемом сервере делать apt get update и смотреть на счетчик пакетов для обновления.

Но на первой стадии, получения «хоть какого-то то прототипа» гораздо, гораздо проще взять данные из текстового файла \ xml \ любой другой структуры, даже бинарного формата, чем из внешнего источника. Есть же Python pickle, который и положит данные в любой удобный мне формат, и заберет их оттуда. Но, это все после, а пока

Добавление еще одного хоста Debian к Ansible

Вроде изян.
Редактируем 1st_hosts.ini, добавляем туда еще один IP, делаем как в первой части:
ansible-playbook uptime_report.yml --ask-pass --user root --inventory  /home/user/1st_hosts.ini

И, конечно, получаем на воротник, потому что в Debian по умолчанию в /etc/ssh/sshd_config запрещено root ssh – параметром PermitRootLogin. Что правильно.

Но в таком случае придется держать под руками скрипт -
sudo adduser ansible
sudo usermod -aG sudo ansible # If you want the ansible user to have sudo privileges
ssh-copy-id ansible@<debian_12_ip_address>

Или, скорее, положить этот скрипт в гит, и брать его из гит. Если вы разворачиваете VM с ноля, а не из шаблона.

Изян: stackoverflow
curl -s http://server/path/script.sh | bash -s arg1 arg2

LSUS Linux Server Update Services и структура данных

Если нужно что-то строить, то потребуется:
A Single Source of Truth (SSOT). То есть источник данных по требуемой версии. Можно сделать такой «автоматизированный для сразу всего». Это долго: планировать, напланировать, сделать. Это водопад. Можно сделать криво, руками, и так и оставить. Это Agile. Вот так и сделаю.
Похоже, это будет таблица, в виде:
Debain 10-11-12-13 и последних версий ядер к ним, а, значит, и Debian в контейнере или в VM, для того чтобы брать данные из него. Можно сделать и Debian VM, на тонком диске, без ПО. Такая VM займет 2-3 гигабайта.
Придется держать Ubuntu 18-20-22-24 и даже 25. С той же целью. Наверное, если чуть-чуть подумать, то можно «потом» сделать и автоматизацию, когда VM или контейнер запускаются, обновляются, и данные с них забирает система учета. Или в саму систему учета встроить десяток репозиториев и смотреть последние версии хотя бы ядер в репозиториях. С одной стороны это проще, с другой в тестовые виртуальные машины можно положить еще массу всякого софта, то есть соорудить еще одно dev окружение. Но это, скорее, даже не фаза 2 (?), и не фаза 3, а фаза 4 – развиваем то, что есть.
На фазе 1 хватит и простой таблицы.

С целевой структурой данных ситуация сложнее. Для своего предпоследнего пет проекта под похожие задачи я просто развернул базу данных (Postgre), и туда клал разное. Нужно ли на первом шаге такое решение? Не знаю, мне не нужно, мне и бинарной таблицы хватит. Но что туда класть? Очевидно, туда должны попасть: FQDN, IP, дистрибутив, версия дистрибутива, ядро сейчас, последние дата и время доступности, аптайм. Должно ли туда попадать предыдущее состояние объекта, и какие-то еще настройки? Не очень важно, всегда можно расширить схему данных, добавить к объекту еще пару свойств. Как, впрочем, можно и пересоздать и перезалить базу.

Заключение

Для построения LSUS Linux Server Update Services на фазе 1 вам понадобятся:
Git \ Gitlab. Можно в контейнере.
Ansible с настроенным доступом
Python. Почему не Rust и не Go? Потому что компилировать не хочется, а Python работает и так. И объемы небольшие.

Литература

SpaceWalk:Satellite
MS Azure Automatic Guest Patching for Azure Virtual Machines and Scale Sets
MS Azure Update Manager
MS Azure How Update Manager works
MS techcommunity Step-by-Step: How to update an Azure Linux VM using Update management
Ivanti Patch for Endpoint Manager
Katello и Foreman в процессе patch management
Katello and Foreman in the process of patch management
Katello (old)
Foreman 3.16 and Katello 4.18
Foreman Quickstart Guide for Foreman with Katello on RHEL/CentOS
RH Chapter 11. Host Management Without Goferd and Katello Agent
Github Katello
RedOS Настройка GLPI-сервера (для инвентаризации оборудования)

@editors, можно мне все же выдать тег Ansible ?

Показать полностью
9

Ansible для детского сада. Часть 3. Настраиваем подобие безопасности и все остальное

Почему я вообще пишу эту статью? Почему нет готового решения «делайте хорошо, плохо не делайте»?
Как жесток и несправедлив этот мир!
У меня постоянное ощущение того, что я описываю не велосипед с костылями, а велосипед, который давно изобретен, на котором все катались лет 15 назад, если не 20. Что-то типа «введение в линукс и все вокруг для 10 класса». Что на информатике учат.
Серия «Кудахтеры: Ansible»

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS - Linux Server Update Services (LSUS)
Ansible для детского сада. Часть 3. Настраиваем подобие безопасности и все остальное
Подготовка Git
Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли

Еще раз объясняю принципы СПО.
Видишь голую жопу - сшей трусы и отдай владельцу жопы.
Не умеешь шить - сообщи владельцу жопы, где трусы можно купить.
Не знаешь где купить - просто скажи владельцу жопы "А у вас жопа голая!".
Не хочешь делать ничего из предложенного - заткнись, не твое дело.

Интересно разделился ИТ мир

В одном отделе (у соседей) n8n подняли и тыкают в него палкой. В другом, у бывших коллег на новом месте работы, оказывается, нет инструмента для сбора статистики обновлений. И вообще ничего нет, кроме папки с сотнями Excel файлов. И те не актуальны.

Ничего против Excel не имею. Инструмент удобный, и внутри можно сделать много чего на VBA – но нужно ли? Но, текст не про философию.

Для того, чтобы Ansible отработал задачу на хосте, необходимо:

Чтобы на целевом (target) хосте существовал нужный пользователь, с нужными правами на исполнение
Чтобы целевой пользователь имел права на вход по ssh
Чтобы (если требуется) на целевом хосте были прописаны, или стали прописаны, нужные репозитории
Чтобы Ansible доверял ssh сертификату целевого хоста. Потому что до первого входа, по умолчанию, и так далее, ничего подобного не будет, никакого доверия.

Дальше мы переходим к вопросу регулярной смены или пароля, или сертификата, или и пароля и сертификата для служебного пользователя.

Становится очевидно, что, чем больше пользователей имеет доступ к Ansible серверу, тем желательнее (скорее, обязательно) заводить отдельный сервер для инфраструктурных задач, в отдельной подсети, с серьезными ограничениями по доступу к серверу, и отдельный сервер для dev\test окружений, с своими правилами и задачами. В идеальной среде, где все профессионалы и друг друга уважают, и думают, что делают.. В идеальном мире можно сразу дать всем пароль от рута. В реальном мире необходим баланс между ограничениями всеми и всего.

Придется начинать с установки и настройки fail2ban и настройкой полуторафакторной авторизации (сертификат с паролем) для серверов с git и ansible. При этом к git нужен будет доступ «от всех серверов локальной сети», а к ansible, без pull модели, не со всех.

Видимо, придется потом сесть и писать длинную статью «я так вижу про безопасность в Linux».

Будет еще одна недописанная статья, кроме «Инвентаризация инфраструктуры и сети. Пометки для начинающих». Что-то такое один дьяк писал в 1415, со словами «житие мое..» , будет «10 лет спустя».

Пока думаю, и пока коллеги думают, сделаю в стиле «для домашней лаборатории сойдет» -

В гите есть public repo, в котором лежит:
1 ssh файл для прописывания первичных настроек и юзера для Ansible.
2 открытая часть сертификата юзера для Ansible, при этом сертификат генерируется с паролем
3 Остальные преднастройки для работы Kerberos + AD

Подготовка Git

Что в наличии: Бесплатный Gitlab в контейнере, Version v18.2.1 (gitlab/gitlab-ce; latest).

Создам там юзера из GUI - (панель админки внизу слева, рядом с help)
Admin > Users > New user > Name: Preset; mail: firstname.lastname@example.com
Admin > Users > Preset > Password: Pa$$word1234
Создам токен; Name: Token01
Через : Admin – Users - Preset Impersonation Tokens for preset с правами:
read_repository: Grants read-only access to repositories on private projects using Git-over-HTTP or the Repository Files API.

Получу токен. Токен выглядит вот так:
glpat-MFuiWVjB1BTngs-6wyHj

Там же, в управлении токенами, сразу сделаю ему rotate.

Создам группу: Linuxpregoup2.
тип: Private (The group and its projects can only be viewed by members.)
Почему так: просто так, потому что все равно токен сделал.

Создам в Gitlab проект:
имя и все прочее: linuxpreset01
Тип: Private (Project access must be granted explicitly to each user. If this project is part of a group, access is granted to members of the group.
Можно делать и Internal, и Public (The project can be accessed without any authentication.), но зачем ?

Создам в проекте файл first.sh
С текстом

#!/bin/bash
date

И сделаю copy permalink. Получу, поскольку у меня не настроен DNS и мне не хочется прописывать что-то в /etc/host -
http://192.168.1111.2222/linuxpregoup2/linuxpreset01/-/blob/какойтодлинныйтокен/first.sh

Добавление пользователя preset к проекту linuxpreset01

Gitlab содержит 7 ролей из коробки: Guest (This role applies to private and internal projects only.) ; Planner; Reporter ; Developer;  Maintainer;  Owner; Minimal Access (available for the top-level group only)

Дам права Guest и попробую зайти.
При входе получу предупреждение:
Update password for Preset . Чтож, сменю пароль и зайду уже с новым паролем, и увижу проект, и в проекте увижу ничего.
По прямой ссылке тоже увижу то самое ничего. И с правами reporter то же самое, то есть ничего. Только роль Reporter (и выше) имеет права на чтение файлов. Ничуть не удивлюсь, если при аудите окажется, что всем подряд выданы роли Maintainer.
Понять это из документации, наверно, можно. Но я не смог.
Хорошие новости: отредактировать файл все равно нельзя.
Новости так себе: можно сделать fork и получить две ветки. Можно почитать Default branch file and directory locks, и все равно задуматься.
Что еще интереснее, я, как user Preset вижу ветку patch-1, а как админ – не вижу. И это несколько странно, как и то ,что создался новый проект - Preset/linuxpreset01
Но, на данном этапе не важно.

Токен к проекту (Project access tokens) я не создавал, а, наверное, зря. Судя по теме How to curl single file using deploy token, я не один такой тупенький.
Создам . User – Preferences – access tokens - Personal access tokens
Add a project access token
Name: Token02Personal; read_repository
Замечу, что эти злые люди переделали (опять) интерфейс, и теперо Project ID живет не там, где раньше

Что я забыл?

практика показала, что, раз я сделал только read_repository, но не сделал в токене:
read_user (Grants read-only access to your profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users. )
read_api (Grants read access to the API, including all groups and projects, the container registry, and the package registry.)

То получу {"message":"401 Unauthorized"}

Это еще ничего. Потому что если адрес совсем неправильный, то я получу
<html><body>You are being <a href="http://192.168.1111.2222/users/sign_in">redirected</a

Управление токенами в бесплатной версии поначалу вызывает того самого кота

Потому что выглядит вот так.

Токен можно посмотреть один раз при перевыпуске, и все, давайте досвиданья.
Посмотреть значение уже выпущенного токена нельзя.
Можно перевыпустить или отозвать
Можно посмотреть сразу после выпуска. Все.

Токен

Токен

Теперь понятно, почему на управление токенами тоже могут забивать. Потому что это надо 5 минут потратить, разобраться.

Если почти все правильно, кроме токена, то будет ошибка:
{"error":"invalid_token","error_description":"Token was revoked. You have to re-authorize from the user."}

Если токен корректен, но что-то не то с путем, то получим:
{"message":"404 Commit Not Found"}

Итого

Неправильно: использовать токен с недостаточными правами, или выписанный «не там». Роль «вон того токена» надо изучить отдельно.

Неправильно: использовать путь из WEB, например
curl --header "PRIVATE-TOKEN: glft-h5DSfGmqiVESDZ7kQJMz" http://192.168.1111.2222/linuxpregoup2/linuxpreset01/-/raw/main/first.sh

Неправильно: прописывать master, хотя у тебя ветка main, например:
curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/first.sh/raw?ref=master -o ot05.txt

Работает:
прописывать токен с нужными правами, прописывать путь через API и project ID, использовать нужную ветку (main):
curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/first.sh/raw?ref=main -o ot08.txt

И, конечно, неправильно публиковать любые токены в статьях. Вдруг они еще рабочие.
но я не очень беспокоюсь за мою домашнюю виртуальную машину, и перевыпустил токен еще до того, как дописал это предложение.

Дальше проще. Изян: stackoverflow

curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/first.sh/raw?ref=main -o

http://192.168.1111.2222/linuxpregoup2/linuxpreset01/-/blob/(какой-то UUID)/ansible

и остается только положить нужные файлы (открытую часть сертификата) в gitlab, и переписать стартовый скрипт.

Итоговый скрипт у меня на домашнем стенде у меня будет выглядеть так, что оказалось что проще написать отдельную статью:

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента

Заключение

У меня постоянное ощущение того, что я описываю не велосипед с костылями, а велосипед, который давно изобретен, на котором все катались лет 15 назад, если не 20. Что-то типа «введение в линукс и все вокруг для 10 класса». Что на информатике учат.
Потому что тут коллеги n8n крутят, а я описываю, как с гита файл скачать с токеном.

Костыли и велосипеды

Костыли и велосипеды

Литература

Основы Ansible для сетевых инженеров
Etckeeper - ставим под контроль изменения конфигурации сервера
Ansible Community Documentation Managing vault passwords
Ansible Community Documentation 2.8 Using Vault in playbooks > The documentation regarding Ansible Vault has moved. Encrypting content with Ansible Vault

RBAC Ansible Tower
RBAC Ansible AWX
RBAC Red Hat Ansible Automation Platform

gitlab signup users without email confirmation

Gitlab docs Roles and permissions
Перевод из 2017 года от ruvds Bash-скрипты: начало. Оригинал: Shell scripting step by step tutorial
Gitlab forum How to curl single file using deploy token
Gitlab forum Curling raw file fails
Medium How to curl single file using access token in gitlab
digitalocean How to Download Files with cURL
Reddit Creating System Users for Ansible Execution

@editors, мне бы тег Ansible, выдайте пожалуйста

Показать полностью 5
8

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента

У меня постоянное ощущение того, что я описываю не велосипед с костылями, а велосипед, который давно изобретен, на котором все катались лет 15 назад, если не 20. Что-то типа «введение в линукс и все вокруг для 10 класса». Что на информатике учат.
Теперь еще и форматирование при оформлении слетает, хотел быстренько выложить, пока обед, а придется потратить полчаса.

Для лиги лени: много примеров, как делать не надо, и как точно не работает
Ansible для детского сада в скольки то частях. Часть 1.Про все сразу
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS - Linux Server Update Services (LSUS)
Ansible для детского сада. Часть 3. Настраиваем подобие безопасности и все остальное
Подготовка Git

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
4.1 Теоретически все просто
4.2 Сначала готовим открытую часть сертификата без пароля через Putty в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh
4.3 Детский скрипт, и как не надо делать
4.4 Отлаживаем SSH, снова и опять
4.5 Проблемы с переносом строки и не только
4.6 Проверяем, что получилось
4.7 Почти переходим к второму Ansible плейбуку
4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи
4.9 Возвращаемся к основной задаче

Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента

В предыдущей части оказалось, что в системе есть узкое место – первичная настройка.

4.1 Теоретически все просто:

пишем отдельный плейбук типа такого: Reddit Creating System Users for Ansible Execution

# From a clean system with root ssh access:
# - Install sudo
# - Add the ansible user
# - Enable wheel group for sudo

- name: add wheel group
group:
name: wheel
state: present

- name: add ansible user
user:
name: ansible
groups: wheel
password: "{{ ansible_become_password }}"
createhome: yes
home: /var/local/ansible
state: present
shell: /bin/bash
uid: 20000

- name: install sudo
apt:
name: sudo
state: installed
when: ansible_os_family == 'Debian'

- name: configure sudoers
lineinfile:
dest: /etc/sudoers.d/ansible
line: "%wheel ALL=(ALL) ALL"
state: present
create: yes
validate: 'visudo -cf %s'
owner: root
group: root
mode: 0440

- name: create ansible user authorized_keys file from id_ed25519
authorized_key:
user: ansible
manage_dir: no
path: /etc/ssh/ansible
key: "{{ lookup('file', ansible_ssh_key) }}"

Практически это означает ручную настройку для рута «еще раз». Как раз этого хочется избежать, потому что в том же Debian по умолчанию ssh для root закрыт. Изменить настройку можно. Но не нужно.

4.2 Сначала готовим открытую часть сертификата без пароля через Putty (PUTTYGEN) в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh

Это все помнят, но необходимо повторить еще раз.

По результатам опытов и вопросов «что ж не работает то» ключ я сделал на целых 4096 бит, а вовсе не по умолчанию в 2048. Вроде бы, должно и так работать, но ведь нет!

Когда PUTTYGEN генерирует пару ключей, открытый и закрытый ключ, то мы получаем два файла.

В моем примере:

ex1_public.pkb – файл с открытым ключем. Можно было сделать без расширения, значения это не имеет
ex1_private.ppk – файл с закрытой частью ключа.
ex1_public выглядит как:

---- BEGIN SSH2 PUBLIC KEY ----

Comment: "key-4-pikabu"

AAAABС(длинный ключ)

---- END SSH2 PUBLIC KEY ----

ex1_private.ppk выглядит как:

PuTTY-User-Key-File-3: ssh-rsa

Encryption: none

Comment: key-4-pikabu

Public-Lines: 6

AAAABС (длинный ключ на 6 строк, он же лежит в ex1_public.pkb)

Private-Lines: 14

AAABAF(длинный ключ)A==

Private-MAC: f(длинная последовательность)

если вы нажимали еще какие-то кнопки и настройки, то могут встречаться строки типа:

Key-Derivation: Argon2id

Argon2-Memory: 8192

Argon2-Passes: 34

В /home/user/.ssh/ authorized_keys лежит только строка из открытого ключа в формате:

ssh-rsa AAAABС.. (и так далее) вплоть до конца строки.

PUTTYGEN так и показывает , КАК НАДО.

4.3 Детский скрипт, и как не надо делать

Перепишу все, что выше, обратно в ssh \ bash:

В процессе отладки выяснилось, что есть такое ограничение, как регистр имени пользователя в Linux, описанное как
User/group names must match [a-z_][a-z0-9_-]*[$],

то есть можно и нужно было посмотреть в тот же chkname.c., и увидеть там

* is_valid_user_name(), is_valid_group_name() - check the new user/group

дебиан сорцы, наведение через ЖЖ - Формат имени пользователя в Linux.

Поэтому, сделать

username=Ansible1

можно, и даже пароль ему поставить можно, но потом разбираться с chmod\chown для \home\ Ansible1\.ssh – это какой-то совсем не очевидный процесс. Хотя при входе с паролем такое имя пользователя работает, но разбираться еще и с этим в рамках статьи было лень, и, значит, пусть все будет строчное.

username=ansible1

/usr/sbin/useradd -m -s /bin/bash "$username"

# как не надо изобретать свои костыли

# Superrandom1=$RANDOM*$RANDOM+$RANDOM-$RANDOM*$RANDOM

# это строка, и математика так не сработает. Это и не важно

# PASS1=$(echo -n $Superrandom1 | sha256sum)

# echo $PASS > ps1.txt

# echo ${PASS:0:64} > ps2.txt

# PASS2=`date`

# PASS3=${PASS2:0:3}-${PASS:0:60}

#PASS2=${PASS:0:61}

# echo -e $PASS2\n$PASS2 | passwd "$username"

# так не работает, потому что в sha256sum попадаются простые сочетания.# echo $PASS3

# echo $username:$PASS3 | /usr/sbin/chpasswd #тут почему-то denied

PASS4=`cat /dev/urandom | fold -w 32 | head -n 1`

echo $username:$PASS4 | /usr/sbin/chpasswd

Пароль поставили. Но он не нужен, потому что вход будет по сертификату. Пока переделывал оформление, вспомнил, что не отключил вход по паролю.

Добавлю группу и юзера в группу

groupname=ans_demo1

/usr/sbin/groupadd $groupname

/usr/sbin/usermod --append --groups $groupname $username

Теперь sudoers для группы:

mkdir -p /etc/sudoers.d

и дальше

curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/ansible/raw?ref=main -o /etc/sudoers.d/ansible

где 2 – ID проекта, а токен ваш токен. Токен в примере выше я сменил, и это токен из домашней лабы, и с этим токеном не так много можно увидеть даже в домашней лабе. Можно увидеть открытую часть ключа, и метод генерации случайного пароля.

Содержание sudoers.d/ansible

## Allow root to run any commands anywhere

root ALL=(ALL) ALL

## Allows people in group wheel to run all commands

%ans_demo1 ALL=(ALL) ALL

## Same thing without a password

%ans_demo1 ALL=(ALL) NOPASSWD: ALL

и остается сделать только

curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/authorized_keys/raw?ref=main -o /home/$username/.ssh/authorized_keys --create-dirs

Думали все так просто? Ничего подобного. Так работать без дополнительной магии не будет.

4.4 Отлаживаем SSH, снова и опять

Эти пара абзацев написаны после того, как в разделе 4.9 ничего не заработало, потому что я попросту забыл настроить права на файл. Вроде, столько раз делал, и опять забыл.

По результатам перегенерации всех паролей и настроек я имею:

Локально, на ПК с Windows,
Было ex1_public.pkb, стало pikabu_part4_public – файл с открытым ключем. Расширение значения не имеет
Было ex1_private.ppk, стало pikabu_part4_private.ppk - файл с закрытой частью ключа.

Это два текстовых файла. Конечно, лучше бы файл с закрытой частью ключа хранить на зашифрованном разделе, или в волте, или как-то еще, «закрыто».

Файл pikabu_part4_export4ssh. Это экспорт файла для ssh, потом будет нужен.
В гите, в файле authorized_keys, лежит, в одну строку, длинная строка с началом

ssh-rsa AAAAB… и окончанием == и какой-то комментарий, который вы сами пропишете при создании ключа.

Поскольку дело происходит в Debian, то речь про
tail -f /var/log/auth.log – не идет, а в
journalctl -ru ssh | grep 'Could not open user' -
будет
Could not open user 'Ansible1' authorized keys '/home/Ansible1/.ssh/authorized_keys': Permission denied

В интернетах пишут: сделайте chmod да chown, и будет вам радость да счастье. Возьму да сделаю!

chmod 600 /home/ansible1/.ssh/authorized_keys
chmod 700 /home/ansible1/.ssh/
chown ansible1 /home/ansible1/.ssh/ -R
systemctl restart ssh.service

Про вот такое еще пишут знающие люди в интернетах, но я пожалуй, воздержусь.

chown ansible1:ans_demo1 /home/ansible1/.ssh –R

И вот замечу я, что когда все буковки в имени пользователя стали маленькие, то волшебным образом все заработало. А пока user был Ansible1, то есть с большой буквы, то сделав

passwd Ansible1

все работало, а через ssh – нет. И магия это мне не понятна, поскольку и ключи я переделал, и все переписал, и три раза перегрузил, а ишь ты – не работало, и хоть ты что делай.

Потому делал я всякое непотребное,
pkill -KILL -u ansible1
и
/usr/sbin/deluser ansible1

и неоднократно.

4.5 Проблемы с переносом строки и не только

Если вы копипастите из винды \ текстового файла (в формате винды с переносом строки и возвратом каретки), и потом тащите этот файл в git, и потом на Linux машину, не забудьте сменить формат переноса строки через

sed -i 's/\r$//' filename

Потому что иногда так получается, что скрипт в две строки, из примера в третьей части, работает нормально. Вот такой еще работает,

#!/bin/bash
date

А что-то длиннее начинает ругаться на
command not found
или
unexpected token `newline

Прям печаль и беда. Что остается? Костыли.

curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/second.sh/raw?ref=main -o ansible4.txt && sed -i 's/\r$//' ansible4.txt && bash ansible4.txt

Конечно, правильнее хранить токены не в коде, а в vault. Но если у этого токена доступ только к этому проекту, то .. и так сойдет.
И, конечно токен не стоит хранить в коде, а имеет смысл передавать как параметр в командной строке. Или нет.

4.6 Проверяем, что получилось

Во первых. Оказалось, что я забыл установить пакет sudo. Нет его в Debian12 и Debian 13 by Proxmox. Не забудьте дописать
Во вторых. Оказалось, что я на одном из хостов не прописал Nexus в /etc/apt/sources.list, отчего обновления не шли. Тоже не забывайте.
В третьих. На одном хосте словил: bash: curl: command not found
Потому что curl там лежит, почему-то, только в:
/usr/share/bash-completion/completions/curl
/usr/lib/python3/dist-packages/curl

как это в одном дебиане работает, в другом нет, я даже не пытаюсь понять. Поэтому:
сначала nano /etc/resolv.conf (где у меня с времен тестов SSSD прописан DNS от AD DC), потом

apt install curl -y

Итого я забыл:

apt install curl -y
apt install sudo -y

заменить или исправить /etc/resolv.conf
заменить или исправить /etc/apt/sources.list. Причем, при использовании Nexus, это будут разные sources.list.

Или нет ? Прописать то я могу что угодно.
Отредактировать /etc/ssh/sshd_config – для входа только по сертификату, сделать PasswordAuthentication no

И про велосипед.

Вопрос «что лучше, исправить или заменить» - мне не ясен. И та и другая операция приводит к одному виду, а что «правильнее» ?

В скрипте выше в строке
PASS4=`cat /dev/urandom | fold -w 32 | head -n 1`
возникают ошибки
ansible4.txt: line 17: warning: command substitution: ignored null byte in input
Да и путь -o ansible4.txt правильнее переписать как -o ~/ansible4.txt, но опять же, если это все работает не от рута, потому что у рута домашняя директория (~) вовсе не /home/user.

И что с этим делать, мне тоже не понятно. То есть, мне то понятно, ничего не делать.
Осталось удалить созданную в ходе опытов директорию ''$'\r'?. Оказалось, что проще всего ее сначала переименовать, иначе удаление делает некоторые фокусы, особенно если сделать rm $'*

Еще от влияния переноса строки образовалась не только \home\Ansible1, но и:
'Ansible1'$'\r' и 'Ansible1'$'\r\r'
И в скрипт можно бы и дописать, rm ansible5.txt.
Или даже и apt update && apt upgrade -y, вдруг забыл кто-то, кто систему ставил, сделать сразу же после настройки /etc/apt/sources.list.

4.7 Почти переходим к второму Ansible плейбуку

Вначале было слово, потом был вообще не плейбук, а команда
ansible proxmox -m setup -a "filter=ansible_uptime_seconds" --ask-pass

Первый плейбук, /home/user/uptime_report.yml, описан в первой статье, как и перечень хостов к нему,/home/user/1st_hosts.ini.
Итоговая команда была:

ansible-playbook uptime_report.yml --ask-pass --user root --inventory /home/user/1st_hosts.ini

При этом, я еще и умудрился файл uptime_report.yml сломать. Точнее, случайно удалить, и при переписывании получить:

[ERROR]: YAML parsing failed: This may be an issue with missing quotes around a template block.
Origin: /home/user/uptime_report.yml:11:19
9 msg:
10 Hostname: {{ inventory_hostname }}
11 Uptime: {{ (ansible_uptime_seconds / 86400) | int }} days,
^ column 19

Вот она, проблема vibe кода. Какой запрос был, я, конечно, не помню. Почему сломалось – не знаю, и написано не понять. Починить? Могу, но.
Но я так люблю возню с пробелами, кавычками, и даже со скобками, вы не представляете насколько.

4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи

В статье Slurm: Примеры Ansible-Playbook есть максимально простой пример (пробелы в начале строки заменены на ! для лучшей считаемости числа пробелов). Файл: example01.yml

- hosts: all

!!tasks:

!!!!- name: Проверить доступность

!!!!!!ping:

Такой сценарий работает, и ему, кстати, безразлична возня с переносами строки.

Если же я пойду в справочник Ansible ansible.builtin.debug module – Print statements during execution, и сделаю

---

- name: Print the gateway for each host when defined

ansible.builtin.debug:

msg: System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}

when: ansible_default_ipv4.gateway is defined

То я получу новую ошибку:

[ERROR]: 'ansible.builtin.debug' is not a valid attribute for a Play

То есть, пример Simple Ansible playbook to get the Linux host uptime – работает, а пример из документации – не работает, то есть не совсем не работает, но это очередной случай «в документации забыли прописать очевидное тем, кто ее пишет, и не очевидное тем, кто ее читает первый раз».

Возьму часть примера из статьи Simple Ansible playbook to get the Linux host uptime и продолжу с ним

Пример 2 (файл: example2) «Не работает!!!!!111111». Пробелы заменены на 1234.

Ошибка: [ERROR]: YAML parsing failed: Colons in unquoted values must be followed by a non-space character.

- name: Print the gateway for each host when defined

12hosts: all

12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds

12tasks:

12- name: Display uptime for each host

123456debug:

12345678msg: " Examlpe test 2 "

123456when: (uptime_days | int) > 30

Пример 3 (файл: example3) так работает проверка синтаксиса, но не исполнение yml. Пробелы заменены на 1234

- name: Print the gateway for each host when defined

12hosts: all

12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds

12tasks:

1234- name: Display uptime for each host

123456debug:

12345678msg: " Examlpe test 3 "

123456when: (uptime_days | int) > 30

Как легко заметить (не легко, но можно):
Не работающий пример:
12- name: Display uptime for each host
Пример с работающей проверкой синтаксиса:
1234- name: Display uptime for each host

Пример 3 (выше), хотя и проходит проверку, но не будет работать с ошибкой:
[ERROR]: Task failed: Error while evaluating conditional: 'uptime_days' is undefined

Еще раз читаем статью Simple Ansible playbook to get the Linux host uptime, и пытаемся сделать так же. Можно найти статью Отладка плэйбуков ansible.

Пример «НЕ Работает #21 ». Пробелы заменены на 1234
Точнее, не проходит проверку синтаксиса командой
ansible-playbook --syntax-check report21.yml, с ошибкой
YAML parsing failed: This may be an issue with missing quotes around a template block.

---

- name: Get and display system uptimes 21

12hosts: all

12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds

12tasks:

1234- name: Display uptime for each host

123456ansible.builtin.debug:

12345678msg:

1234567890Hostname: {{ inventory_hostname }}

1234567890Uptime: {{ (ansible_uptime_seconds / 86400) | int }} days,

1234567890{{ ((ansible_uptime_seconds % 86400) / 3600) | int }} hours,

1234567890{{ (((ansible_uptime_seconds % 86400) % 3600) / 60) | int }} minutes,

1234567890{{ (((ansible_uptime_seconds % 86400) % 3600) % 60) | int }} seconds

123456when: ansible_uptime_seconds is defined

Пример «Работает #22». Пробелы заменены на 1234

Это то, что генерит google AI для примеров, и оно даже работает. Пока не надо отлаживать.

---

- name: Show uptime for each host in seconds

12hosts: all

12gather_facts: true # Ensure facts are gathered

12tasks:

1234- name: Display uptime in seconds

123456ansible.builtin.debug:

12345678msg: "Host {{ inventory_hostname }} has been up for {{ ansible_uptime_seconds }} seconds."

Пример «Работает #23».

Только картинкой, отличия от #21 очевидны – кавычки открыли, кавычки закрыли.

12345678msg:
и
12345678msg: "

4.9 Возвращаемся к основной задаче

Для следующего подхода нам потребуется два, а лучше три файла.
первый - second_inventory.ini

В комментариях мне в файл с настройками ткнули, все правильно сделали, но еще не время переходить к такой конфигурации.
Поэтому сначала:
cp /etc/ansible/hosts /home/user/second_inventory.ini.

Не забыв прочитать Ansible How to build your inventory, и вписав в конфигурацию секцию для закрытой части ssh ключа.

[ansible4]

192.168.1111.2222 ansible_user= ansible1

192.168.1111.3333 ansible_user= ansible1

192.168.1111.4444 ansible_user= ansible1

# compare with: files/second.sh

[all:vars]

ansible_python_interpreter=/usr/bin/python3

[defaults]

private_key_file = /home/ansible1/.ssh/ansible_key

Какой-то странный хардкод получается. Потому что при смене имени я должен прописать новое имя и в скрипте настройки, и в коде для Ansible. Если бы я вел документацию, то подумал бы, как сделать иначе, и прописал бы это где-то в ридми.

Не забываем, что закрытый ключ из Puttygen в Windows нужно экспортировать в другой формат. Как это сделать, знает даже AI,

To copy a private key generated by PuTTYgen for use with Ansible, which typically expects OpenSSH format keys, you need to convert it and then copy the resulting OpenSSH-formatted private key.

Steps to Convert and Copy the Private Key:

Open PuTTYgen: Launch the PuTTY Key Generator application.

Load the Private Key: Click the "Load" button and navigate to your existing private key file (usually with a .ppk extension) generated by PuTTYgen. Enter the passphrase if prompted.

Export as OpenSSH Key:

Go to the "Conversions" menu.

Select "Export OpenSSH key."

You will likely be prompted to save it without a passphrase; choose "Yes" if you want to use it without a password in Ansible (which is common for automated tasks).

Save the OpenSSH Key: Choose a location and filename for the exported OpenSSH-formatted private key (e.g., id_rsa or ansible_key). This file will not have a .ppk extension.

Copy the Key to your Ansible Control Machine: Transfer this newly exported OpenSSH private key file to your Ansible control machine. A common location for private keys on Linux-based systems is ~/.ssh/.

Set Permissions (on Ansible Control Machine): Ensure the private key file has appropriate permissions, typically read-only for the owner to prevent unauthorized access.

chmod 400 ~/.ssh/ansible_key

Не забываем настроить WinSCP,

Open WinSCP: and navigate to the main menu.

Select Options > Preferences.

In the Preferences dialog box, go to the Panels tab.

Locate the option "Show hidden files" and check the box next to it.

Click OK to apply the changes.

Работать так все равно не будет, если вы, конечно, не пошли от рута. Поэтому положим куда попало и переложим от рута локально
переложим файл pikabu_part4_export4ssh в /home/user и сделаем
mv pikabu_part4_export4ssh /home/ansible1/.ssh/ansible_key

Проверим что получилось.
ansible-playbook report23.yml --inventory /home/user/second_inventory.ini

и получим

Failed to parse inventory with 'ini' plugin: Failed to parse inventory: Expected key=value host variable assignment, got: Ansible1

Кто нашел ошибку сам – тот молодец.

кто не нашел, может сравнить

192.168.1111.2222 ansible_user= ansible1
192.168.1111.2222 ansible_user=ansible1

Отдельно надо сказать про ошибку с блоком [defaults]

[defaults]
private_key_file

потому что достаточно прочитать обсуждение ошибки private_key_file vs ansible_ssh_private_key_file #79186 , связанную с определение порядка переменных - Understanding variable precedence, но это уже другая история.

Поэтому, этот блок из конфигурации я пока удалю, и сделаю ручную проверку,

ansible-playbook report23.yml --inventory /home/user/1st_hosts.ini --user ansible1 --private-key /home/ansible1/.ssh/ansible_key

Которая не сработает, и поэтому переходить дальше, к команде

ansible-playbook report23.yml --inventory /home/user/second_inventory.ini --private-key /home/ansible1/.ssh/ansible_key

еще рано.

Потому что команда может и не сработает, с сообщением Server refused our key.
Потому что после копирования ключа, атрибуты на файл /.ssh/ansible_key никто не перепроверил.
Потому что все, что выше, было написано до появления раздела 4.4 Отлаживаем SSH, снова и опять

Потому что 192.168.1111.2222 настроен на порт 2424, а на порту 22 висит ssh до одного из контейнеров, и надо прописать не

192.168.1111.2222 ansible_user=ansible1
а
192.168.1111.2222 ansible_user=ansible1 ansible_port=2424

С чем еще пришлось столкнуться.

С недостатком памяти на VM. Я, в ходе опытов, снизил оперативную память VM с 5000 до 4750 мегабайт. Как оказалось, 4750 МАЛОВАТО.

В остальном поздравляю, вы великолепны, и можете сделать то, ради чего все затевалось, но про это будет уже в пятой части.

Литература

stackexchange Proper way to add a user account via bash script
stackexchange Why is this random password flagged saying it is too simplistic/systematic?
stackoverflow Change user password with one Bash command line
S/KEY
Ansible Connection methods and details
Ansible How to build your inventory
Ansible ansible.builtin.debug module – Print statements during execution
Статья: Отладка плэйбуков ansible

Перевод: Как убить вашу сеть с помощью Ansible. Оригинал: How to kill your network with Ansible
Перевод: Плейбуки Ansible — советы и примеры. Оригинал: Working with Ansible Playbooks — Tips & Tricks with Examples
slurm: Примеры Ansible-Playbook
пример Simple Ansible playbook to get the Linux host uptime

Тег Ansible не добавляется, ну что тут сделать.

@editors, как теги то добавлять??

Показать полностью 3
13

Ansible для детского сада. Часть 5. Приделываем костыли

У меня постоянное ощущение того, что я описываю не велосипед с костылями, а велосипед, который давно изобретен, на котором все катались лет 15 назад, если не 20. Что-то типа «введение в линукс и все вокруг для 10 класса». Что на информатике учат.

Для лиги лени: много примеров, как делать не надо, и как точно не работает

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS - Linux Server Update Services (LSUS)
Ansible для детского сада. Часть 3. Настраиваем подобие безопасности и все остальное
Подготовка Git

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
4.1 Теоретически все просто
4.2 Сначала готовим открытую часть сертификата без пароля через Putty в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh
4.3 Детский скрипт, и как не надо делать
4.4 Отлаживаем SSH, снова и опять
4.5 Проблемы с переносом строки и не только
4.6 Проверяем, что получилось
4.7 Почти переходим к второму Ansible плейбуку
4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи
4.9 Возвращаемся к основной задаче

Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли
5.1 Теоретическое обоснование костылей, или «зачем».
5.2 Выбор dataflow
5.3 Собираем Ansible facts без плейбука
5.4 Разбираем полученную выгрузку

Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли

5.1 Теоретическое обоснование костылей, или «зачем».

С какого-то возраста появился вопрос: «Зачем?» Вот раньше тебе звонил приятель, например, говорил: «Слушай, я познакомился с двумя девушками, у них отдельная квартира в Отрадном, я выпить купил. Поехали!» И ты сразу ехал. Если бы тебя спросили: «А зачем?», ты бы сказал: «Ну как, зачем? Ты че, дурак? Две девушки, отдельная квартира! Посидим, выпьем, ну...» А сейчас...

Перед тем, как идти дальше, необходимо принять решение про архитектуру Linux Server Update Services (LSUS) на первом этапе. Поскольку сейчас я хочу только собирать и показывать статистику в нужном мне виде, с нужной детализацией. Часть информации я могу получать, не изобретая новую систему учета, а взяв имеющуюся, и переформатировав отчет из нее. Но насколько это будет сложно и долго? Нужен ли мне будет настоящий разработчик для такой задачи, потому что разработчик сделает ее быстрее, но сделает ли так, как мне надо? Учитывая, что я сам не знаю, как мне надо, значит, остается только попробовать сделать.

5.2 Выбор dataflow
Про что это? В результате обработки (входных данных) я получу (обработанные входные данные). Затем мне надо понять:
Где я буду обрабатывать сырые данные, на хосте с ansible или сделаю файл «как есть», положу его в общую папку (или как-то еще ?), заберу с хоста с Windows и там обработаю. Мне все равно, под что писать простой обработчик, под Windows или под Linux, и на чем писать – могу на баш (много секса, мало смысла), могу на питоне, могу на powershell. При малых объемах нет принципиальной разницы, обработается ли файл на 100 – 1.000 – 10.000 хостов за минуту, или за две.
Куда я помещу обработанные входные данные, в csv, xlsx, xml, или в web\html.
Если в web, то на каком веб-сервере? Апач, нжинкс, IIS, или даже MiniWeb и Small HTTP Server.
Нужно ли хранить исторические данные ?

Все это, и даже больше, я писал во второй части –

С целевой структурой данных ситуация сложнее. Для своего предпоследнего пет проекта под похожие задачи я просто развернул базу данных (Postgre), и туда клал разное. Нужно ли на первом шаге такое решение? Не знаю, мне не нужно, мне и бинарной таблицы хватит. Но что туда класть? Очевидно, туда должны попасть: FQDN, IP, дистрибутив, версия дистрибутива, ядро сейчас, последние дата и время доступности, аптайм. Должно ли туда попадать предыдущее состояние объекта, и какие-то еще настройки? Не очень важно, всегда можно расширить схему данных, добавить к объекту еще пару свойств.

Пока получается так, что сначала надо разобрать входной объект (файл, выгрузку), собрать из него данные в удобный мне массив объектов (или в список объектов, но это вопрос выбора алгоритма и понимания разницы работы с массивом и работы со списком). Когда будет массив данных в нужном мне виде, из него можно собрать уже то, что нужно мне.

5.3 Собираем Ansible facts без плейбука

Во второй части факты собирались одной командой,
ansible proxmox -m setup -a "filter=ansible_uptime_seconds" --ask-pass
В предыдущей (4-й) части я остановился на выполнении плейбука

ansible-playbook report23.yml --inventory  /home/user/second_inventory.ini --private-key  /home/ansible1/.ssh/ansible_key

Нужно ли делать плейбук именно для сбора фактов? Получается, что нет. Достаточно еще раз прочитать про ключи к ansible, и сделать:
[-i INVENTORY] [--list-hosts] [-l SUBSET] [--flush-cache]
[--private-key PRIVATE_KEY_FILE] [-u REMOTE_USER]

То есть выполнить все в одну команду:

ansible all --module-name setup --inventory  /home/user/second_inventory.ini --private-key  /home/ansible1/.ssh/ansible_key

не забывайте про all, даже если указываете --inventory  /home/user/second_inventory.ini.

на выводе вы получите текст ужасающей длины, в формате с {}, [], и еще чем-то. 150 килобайт на мои 3 (три) тестовые виртуальные машины.

Из этого огромного массива представляет интерес:

FQDN, IP, дистрибутив, версия дистрибутива, ядро сейчас, последние дата и время доступности, аптайм. То есть, в нужный мне объект должны попасть:
Удачность соединения: 192.168.1111.2222 | SUCCESS => {
FQDN: "ansible_fqdn"
Hostname: "ansible_hostname"
IP: "ansible_all_ipv4_addresses"
IPv6: "ansible_all_ipv6_addresses":
тип: "ansible_board_name": "Virtual Machine",
Текущие сессии: "SSH_CONNECTION":
Текущий пользователь "USER": "ansible1",. С этим параметром надо поработать, а то есть любители забыть закрыть сессию.
MAC "macaddress": ". Нужен, но не для сетевых настроек, а для сверки, не сделал ли кто-то, случайно, клонирование с совпадающим MAC адресом. Бывает и такое легаси.
"ansible_kernel": "6.1.0-40-amd64",
"ansible_kernel_version": "#1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20)",
"ansible_lsb": {
"codename": "bookworm",
"description": "Debian GNU/Linux 12 (bookworm)",
"id": "Debian",
"major_release": "12",
"release": "12"

Все вместе выглядит или как начало поиска готового решения «как свернуть xml в набор объектов», или как задача первого (для начинающих) уровня сложности с AlgoMap.io  \ Codility \ LeetCode \ итд, свертывание xml в объект со свойствами.  Не взяв xml.etree.ElementTree, а все сам, все руками. Дело не сложное, но надо вспоминать лаго алгоритм

Но, у меня сейчас дел вдруг привалило, что я даже не знаю, когда дописать цикл. Новый проект, новые задачи.

Поэтому сделаю быстро, грязно, на powershell под windows, и так делать, вообще говоря, не нужно.

Еще придется не забыть сразу сделать выгрузку с примером ошибки недоступности хоста, и что-то с этим делать.

5.4 Разбираем полученную выгрузку

Поскольку все сделано в непревзойденном стиле «давайте не будем спрашивать нейросеть», то не удивляйтесь, что написано не просто плохо, а отвратительно плохо. Ирония не в том, могу ли я сделать задачу «сверните xml в массив вложенных объектов», как на интервью для джуна, а в том, хочу ли я потом искать, в какой путь это свернулось.

Поэтому вот вам еще один образец, как делать плохо и неправильно. Еще и неполный образец. Еще и на Powershell. Короче, сплошной позор.

# Ansible facts parser

Write-Host "Start 01 ================="

$Version = "AFP001"

$ScriptPath = $PSScriptRoot

$DataFromFile001 = Get-Content ($ScriptPath + "\" + "resultv01.txt")

Write-Host 'Read from file total' $DataFromFile001.count

Write-Host 'Read from file first' $DataFromFile001[0]

Write-Host 'Read from file last' $DataFromFile001[$DataFromFile001.count -1]

$SplitStartMark = '| SUCCESS => {'


Write-Host "Start 02 ================="

for ($Mark = 0; $Mark -lt $DataFromFile001.count; $Mark++){

if ($DataFromFile001[$Mark] -like "*$SplitStartMark") {Write-Host "Mark" $Mark ';' $DataFromFile001[$Mark]

}}


Class Split{

[int]$Position

[string]$String

}


Write-Host "Start 03 ================="

$SplitStartMarkPosition = @()


for ($Mark = 0; $Mark -lt $DataFromFile001.count; $Mark++){

if ($DataFromFile001[$Mark] -like "*$SplitStartMark") {

Write-Host "Mark" $Mark ';' $DataFromFile001[$Mark]

$NewHost = [Split]::new()

$NewHost.Position = $Mark

$NewHost.String = $DataFromFile001[$Mark]

$SplitStartMarkPosition += $NewHost

Remove-Variable NewHost

}}

Write-Host "Total" $SplitStartMarkPosition.Count


Write-Host "Start 04 ================="

Class AnsibleFacts01{

[string]$IsSUCCESS

[string]$FQDN

[string]$IP

[string]$ansible_kernel_version}



$NewHost1 = [AnsibleFacts01]::new()

$NewHost1.IsSUCCESS = $DataFromFile001[0]

for ($Mark = 0; $Mark -lt $SplitStartMarkPosition[1].Position; $Mark++){


if ($DataFromFile001[$Mark] -like '*ansible_fqdn*') {$NewHost1.FQDN = $DataFromFile001[$Mark]}

if ($DataFromFile001[$Mark] -like '*ansible_all_ipv4_addresses*') {$NewHost1.IP = $DataFromFile001[$Mark+1]}

if ($DataFromFile001[$Mark] -like '*ansible_kernel_version*') {$NewHost1.ansible_kernel_version = $DataFromFile001[$Mark]}

}


$NewHost1 | fl


Write-Host "Start 05 ================="

$NewHost2 = [AnsibleFacts01]::new()

$NewHost2.IsSUCCESS = $DataFromFile001[$SplitStartMarkPosition[1].Position] -replace "[^a-z][^A-Z]" -replace "{",""

for ($Mark = $SplitStartMarkPosition[1].Position; $Mark -lt $SplitStartMarkPosition[2].Position; $Mark++){

if ($DataFromFile001[$Mark] -like '*ansible_fqdn*') {$NewHost2.FQDN =  $DataFromFile001[$Mark] -replace '"ansible_fqdn":',"" -replace " ", "" -replace ",","" -replace '"',""}

if ($DataFromFile001[$Mark] -like '*ansible_all_ipv4_addresses*') {$NewHost2.IP = ($DataFromFile001[$Mark+1] -replace '"',"").TrimStart()}

$kv = ''

if ($DataFromFile001[$Mark] -like '*ansible_kernel_version*') {$NewHost2.ansible_kernel_version = ($DataFromFile001[$Mark] -replace '"ansible_kernel_version":', ""  -replace "," -replace '"',"").TrimStart()}

}


$NewHost2 | fl

Что делать дальше – понятно. Тут и обработка ошибок, и нормальное формирование объекта, и что делать, если хост не отвечает, и сравнение состояния «как есть» и «как надо».

Заключение
На написание 5 заметок ушло где-то 10-15 дней, по часу в день. Не каждый день. Хотя один выходной я потратил, с обеда до ужина.
На запуск такого костыля «чтобы посмотреть, что там в простой инфраструктуре», зная как, и куда смотреть, ушло бы примерно 3 рабочих дня для 1 джуна. Даже если в инфраструктуре вообще ничего нет. Остальные работы, доведение выгрузки до читаемого вида, сбор образцов, итд, заняли бы еще пару дней с кофе.
Поэтому, если где-то нет такой выгрузки, то я не знаю, чем там занимается и отдел ИТ, и отдел имитации безопасности.

Литература
ahuffman / ansible-sudoers
Ansible docs: ansible
Microsoft: Windows PowerShell - Build User-Friendly XML Interfaces with Windows PowerShell
Microsoft:  Add-Member Module: Microsoft.PowerShell.Utility Module
Microsoft:  Everything you wanted to know about PSCustomObject
Microsoft:  about_Regular_Expressions

Показать полностью
8

Ansible для детского сада. Часть 6. Пока что не СУБД для Linux Server Update Services (LSUS)

А за скрюченной рекой
В скрюченном домишке
Жили летом и зимой
Скрюченные мышки.
Корней Чуковский. Скрюченная песня

У меня постоянное ощущение того, что я описываю не велосипед с костылями, а велосипед, который давно изобретен, на котором все катались лет 15 назад, если не 20. Что-то типа «введение в линукс и все вокруг для 10 класса». Что на информатике учат.
Поскольку этот текст пишется 50\50 – половина сделана еще осенью 2024, а половину я переделываю в ходе самообучения, и когда время и желание есть, то не удивляйтесь, что в тексте попадаются странные решения.

Для лиги лени: много примеров, как делать не надо, и как точно не работает

Ansible для детского сада в скольки то частях. Часть 1.Про все сразу
Ansible для детского сада в скольки то частях. Часть 2. Костылируем жалкое подобие WSUS - Linux Server Update Services (LSUS)
Ansible для детского сада. Часть 3. Настраиваем подобие безопасности и все остальное
Подготовка Git

Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
4.1 Теоретически все просто
4.2 Сначала готовим открытую часть сертификата без пароля через Putty в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh
4.3 Детский скрипт, и как не надо делать
4.4 Отлаживаем SSH, снова и опять
4.5 Проблемы с переносом строки и не только
4.6 Проверяем, что получилось
4.7 Почти переходим к второму Ansible плейбуку
4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи
4.9 Возвращаемся к основной задаче

Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли
5.1 Теоретическое обоснование костылей, или «зачем».
5.2 Выбор dataflow
5.3 Собираем Ansible facts без плейбука
5.4 Разбираем полученную выгрузку

Ansible для детского сада. Часть 6. Пока что не СУБД для Linux Server Update Services (LSUS)
6.1 Введение
6.2 Еще раз про готовые решения – Spacewalk, Satellite, Foreman + katello, Rudder
6.3 Переходим к проектированию на минималках
6.4 Начинаем писать код
6.4 Начинаем писать код или Powershell класс в классе. Часть 1
6.5 Продолжаем писать код или Powershell класс в классе. Часть 2

Ansible для детского сада. Часть 7, опять не связанная с Ansible
Ansible для детского сада. Часть 8. Теперь костыльная БД для Linux Server Update Services (LSUS)

Ansible для детского сада. Часть 6. Пока что не СУБД для Linux Server Update Services (LSUS)

Все, что мне было надо, я писал во второй части –

С целевой структурой данных ситуация сложнее. Для своего предпоследнего пет проекта под похожие задачи я просто развернул базу данных (Postgre), и туда клал разное. Нужно ли на первом шаге такое решение? Не знаю, мне не нужно, мне и бинарной таблицы хватит. Но что туда класть? Очевидно, туда должны попасть: FQDN, IP, дистрибутив, версия дистрибутива, ядро сейчас, последние дата и время доступности, аптайм. Должно ли туда попадать предыдущее состояние объекта, и какие-то еще настройки? Не очень важно, всегда можно расширить схему данных, добавить к объекту еще пару свойств.

И вот тут самое время подумать. Что я хочу от системы, какую задачу она решает. И как хранит данные для этого, и как глубоко.
Какие технические данные хранить уже понятно, с чем сравнивать, в общем, тоже.

6.2 Еще раз про готовые решения – Spacewalk, Satellite, Foreman + katello, Rudder
Повторю из второй части

У Microsoft был Baseline Security Analyzer
У Microsoft есть Security Compliance Toolkit (SCT)
У Microsoft есть Azure Update Manager operation(AUM).

В опенсорсе был Spacewalk. Последний релиз - 2.10 / March 18, 2020
У RH был Satellite. Это Foreman + katello+ support. Foreman 3.16 and Katello 4.18
Ivanti Patch for Endpoint Manager ? Ага:

Release Date September 18, 2025  The U.S. Cybersecurity and Infrastructure Security Agency (CISA) has published an analysis of the malware deployed in attacks exploiting vulnerabilities affecting Ivanti Endpoint Manager Mobile (EPMM). The Cybersecurity and Infrastructure Security Agency (CISA) obtained two sets of malware from an organization compromised by cyber threat actors exploiting CVE-2025-4427 and CVE-2025-4428 in Ivanti Endpoint Manager Mobile (Ivanti EPMM). Each set contains loaders for malicious listeners that enable cyber threat actors to run arbitrary code on the compromised server. Malicious Listener for Ivanti Endpoint Mobile Management Systems

Rudder ? Ничего про него не знаю.

Вопрос в сложности развертывания, это совсем не WSUS с его далее – далее - готово – пропишите WSUS в GPO

6.3 Переходим к проектированию на минималках
Функциональные требования: что делает этот софт – понятно, структурирует информацию про то, что надо обновить. Но, кстати, в таком случае нужно еще поле «пометки для объекта, почему не обновлено», и это уже точно нужно вести в CMDB (Configuration Management Database).
Не функциональные требования: как он это хранит.
И дальше UI \ UX.

Для начала, нужен уникальный идентификатор объекта.
В Windows это Machine ID, в Linux /etc/machine-id. Кроме этого, скорее всего, у сервера будет уникальный MAC или, для Cloud init, MAC останется, но со своими особенностями.

Что касается «истории состояния», то можно долго думать, читать про всякие 36 нормальных форм, Кимбалла, Data Vaul и вот это все.

Или можно зарезервировать строки данных:
Состояние00 «состояние сейчас»
Состояние01 «состояние при предыдущем опросе, вне зависимости от даты»
Состояние24 «состояние сутки назад»
Состояние168 «состояние неделю назад»
Состояние720 «состояние месяц назад»
Это позволяет организовать простую логику «было – стало» и перемещать данные по цепочке и до заполнения таблицы или таблиц. Вторая и третья нормальные формы SQL, вот это все.

На этом этапе и начинается разделение между «держим в каждом объекте 5 лишних групп, зато это один простой массив» и «держим данные в связанных таблицах с одним первичным ключом, зато модно стильно SQL».
Или,  можно немного упороться, и сделать объект из 5 объектов, то есть 3 мерную таблицу.
Это, с одной стороны, штатный и понятный (мне) механизм, с другой снимает требование по заведению SQL.
Получаемый объект будет в формате:
Сервер.Состояние00.Параметр01
Сервер.Состояние01. Параметр01
и так далее.
Давайте пробовать.
То есть, с точки зрения «вспомнить как Python работает с модулем psycopg2 и СУБД» полезнее сделать с базой данных, с точки зрения скорости \ удобства сейчас – проще сделать класс и класс из классов. Так что перенесу задачу «по базе данных» на часть 7.
Пока писал текст, понял что задача «хранение данных в базе» все равно требует считывания данных в какой-то объект или группу объектов, как-то с этим всем все равно потом работать.
Переусложнение не нужно.

Логика первичного заполнения

В первый момент времени не заполнено ничего. Поэтому при каждом запуске придется выполнять пре-проверку.
Получить сегодняшнее число, считать Data00 и выбрать Data00.timestamp
Взять Data00.timestamp. Если Null, то заполнить текущим временем и данными. 
Можно прописать везде 01.01.1970 (Unix Epoch) при первом запуске и проверять, что там. Это гораздо проще сравнивать при следующих шагах.

Дальше в чем-то проще.
Взять текущую дату. Сравнить с датой в Data01. Если данным в Data01 больше 24 часов, то переместить их в Data24. Ну или как-то так. Можно наоборот – посмотреть данные в Состояние720 и  Состояние168, сравнить с текущей отметкой времени, переместить.
На этом месте я понял, что было два пути:
1. Более правильный в сложных системах: планировать еще пару дней, нарисовать схему движения данных, итд. Возможно, сделать схему более гибкой.
2. Начать писать код, и поправить все что нужно, по мере необходимости. В том числе, расширить схему объекта, если надо.

6.4 Начинаем писать код или Powershell класс в классе.Часть 1
Почему Powershell, а не питон ? Да потому, что часть из этого кода пишется для людей, которые Powershell читать и использовать еще могут, а питон уже сложно, сложно ничего не понятно.

В таких статьях всегда есть соблазн «просто взять и скопипастить уже готовое» или «взять и сделать через AI». AI я использую, но не очень люблю. Даже не смотря на то, что Copilot очень нравится и коллегам, и мне.
Поэтому будет, как обычно, много примеров «неправильно» или «непонятно».
Всегда есть соблазн написать сразу итоговое «хорошо». На мой взгляд, надо писать и пример «вот так плохо, потому что».

Как я писал раньше, вот так, разумеется, работать будет:

Class AnsibleFacts01{
[string]$IsSUCCESS
[string]$FQDN
[string]$IP
[string]$Ansible_kernel_version
[string]$Comment}

$NewHost1 = [AnsibleFacts01]::new()
$NewHost1.Comment = "123"
$NewHost1

И даже вот так система сработает без ошибок:
Class AnsibleFactsForData{

[AnsibleFacts01]$Data00
[AnsibleFacts01]$Data01}
$NewHost2 = [AnsibleFactsForData]::new()

Но уже на этапе
$NewHost2.Data00.Comment = "123"
система скажет
The property 'Comment' cannot be found on this object.

Почему? Потому. Overloading constructors, вот это все по руководству Petri PowerShell Classes – Your Ultimate Guide

Поэтому что? Поэтому есть три пути. Webcam, дальше вы знаете.

Путь первый. Конструктор и инициализация.

Для начала перепишу первый класс, а точнее скопирую пример из Using other classes with a class in Powershell

class SomeOtherClass {
[string] $Status
[string] $Reason

SomeOtherClass () { }
SomeOtherClass ([string] $Status, [string] $Reason) {
$this.Status = $Status
$this.Reason = $Reason  }}

class SomeClass {
[string] $Server
[string] $File
[SomeOtherClass] $OtherClass
SomeMasterClass () { }
SomeMasterClass ([string] $Server, [string] $File, [SomeOtherClass] $Class) {
$this.Server  = $Server
$this.File  = $File
$this.OtherClass = $Class}}

Но к такому примеру нужна инициализация: (там же)

$testing = [SomeClass]@{
Server = 'Server01'
File  = 'SomeFile'
OtherClass = [SomeOtherClass]@{
Status = 'OK!'
Reason = 'Idk'  }}


В моем примере (выше) это будет выглядеть как:


Class AnsibleFactsForData01{
[AnsibleFacts01]$Data00
[AnsibleFacts01]$Data01}

$NewHost1 = [AnsibleFactsForData01]::new()
$NewHost1.Data00 = [AnsibleFacts01]@{Comment = '456'}

Посмотрю что получилось:
$NewHost1.Data00
Поменяю значение и еще раз посмотрю что получилось:
$NewHost1.Data00.Comment = '6'
$NewHost1.Data00

Вот такая вот история. Хотите, не хотите, а будьте добры инициализировать, а после этого уже как хотите.

Путь второй, тоже не очень

Class AnsibleFactsForData02{
[AnsibleFacts01]$Data00
[AnsibleFacts01]$Data01
# AnsibleFactsForData02 () { } Ввиду тупости, я не понимаю зачем эта строка, поэтому ее закомментировал.
AnsibleFactsForData02 () {
$this.Data00 =  [AnsibleFacts01]::new()}}

$NewHost02 = [AnsibleFactsForData02]::new()
$NewHost02.Data00.Comment = '12'

Проверим что вышло:
$NewHost02.Data00

Выглядит, что все работает.
Теперь проверим:
$NewHost02.Data01.Comment = '34'

И, конечно, так не работает.
Потому что внутри AnsibleFactsForData02 я потрогал только  $this.Data00, и не потрогал $this.Data01

Раз я его не потрогал, то под него не выделилась память, и все такое. Но у меня есть оправдание, когда в школе люди учили  typedef struct, constructor и прочие ~className (Destructors), я прогуливал.

Тут должен быть третий путь, но я его не придумал.
Но в моем примере можно вместо класса с классами внутри использовать простой массив классов, только придется запомнить, что [0] это сейчас, [1] это в прошлый раз, итд.

Осталось записать это в файл.

6.5 Продолжаем писать код или Powershell класс в классе. Часть 2

Упрощаем и отрезаем.  Получаем:

Class Ans_F{ # AnsibleFacts
[string]$IsSUCCESS
[string]$FQDN
[string]$IP
[string]$Ansible_kernel_version
[string]$Comment}

Class A4D { #AnsibleFactsForData
[Ans_F]$D00
[Ans_F]$D01
[Ans_F]$D24 # «состояние сутки назад»
[Ans_F]$D168 # «состояние неделю назад»
[Ans_F]$D720 # «состояние месяц назад»
# AnsibleFactsForData02 () { } не понимаю зачем эта строка, поэтому закомментировал.
A4D () {
$this.D00 =  [Ans_F]::new()
$this.D01 =  [Ans_F]::new()
$this.D24 =  [Ans_F]::new()
$this.D168 =  [Ans_F]::new()
$this.D720 =  [Ans_F]::new() }}


$NewHost02 = [A4D]::new()
$NewHost02.D00.Comment = '12'
$NewHost02.D01.Comment = '34'
$NewHost02.D00

$NewHost03 = [A4D]::new()
$NewHost03.D168.Comment = '56'

$HostList = @()
$HostList += $NewHost02
$HostList += $NewHost03
$ScriptPath = $PSScriptRoot
$OutFileName = 'Ansible2.xml'
$OutFullName = $ScriptPath + "\" + $OutFileName
Export-Clixml -Path $OutFullName -InputObject $HostList

$HostListExport = Import-Clixml -Path $OutFullName
$HostListExport[1].D168.Comment

Есть минус, про Select при выборе формата – можно забыть.

Литература
machine-id(5) — Linux manual page
Cloud init Network configuration
MS about_Classes

stackoverflow Using Classes within an class
Petri PowerShell Classes – Your Ultimate Guide
How and where to properly define classes and enums in your PowerShell modules
Powershell v5 Classes & Concepts

Показать полностью
Отличная работа, все прочитано!