Ansible, bash и я: три мушкетёра в мире автоматизации управления компьютерами на Linux

от автора

Дорогие читатели!

Добро пожаловать в увлекательное путешествие по миру автоматизации в Linux! Хочу предупредить вас, что формат этой статьи будет необычен — я решил представить материал в виде захватывающего рассказа.

Немного об авторе и его прошлом

В землях Linux, где каждый день приносит новые задачи и вызовы, я — ваш проводник в мире автоматизации. Меня зовут Александр Габидуллин, и я рад приветствовать вас в своём повествовании о приключениях трёх мушкетёров: Ansible, Bash и меня.

Про ansible и bash поговорим чуть дальше, а сейчас обо мне!

Я занимаю должность инженера в отделе архитектуры и интеграции в одном из больших вендоров отечественного ПО. До этого я начинал как обычный рыцарь в мире linux, и помогал windows администраторам сделать первые шаги на встречу к linux системам и автоматизировать их рутинный труд.

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

Присоединяйтесь ко мне в этом путешествии, и давайте вместе отправимся навстречу новым открытиям и достижениям!

«На пути к переменам: проблема, которая стала отправной точкой»

Когда-то в далеком 2022 году, работая в государственном учреждении на должности инженер информационной безопасности, я получил письмо счастья, а точнее копию письма с указом президента РФ №250.

Так как я был безопасником, меня касалась малая часть работ по переводу защищенных систем на отечественное ПО и большей проблемой были закупки и финансирование. Однако коллеги из отдела эксплуатации техники и информационных систем попросили помощи, так как все администраторы были windows пользователями, и с отечественным Linux, им было очень тяжело, а потом к ним присоединились еще и администраторы отвечающие на студенческие рабочие места.

P.S. да-да, я начинал свои первые шаги с большого государственного университета.

После отрицания и гнева, как всем известно наступает этап принятия и я начал планировать импортозамещение во всей организации с технической стороны.

Необходимо было в кратчайшие сроки перевести более 1000 рабочих мест сотрудников и студентов на отечественное ПО и без ручного труда.

В поисках пути к автоматизации: первые шаги и вызовы

Изначально я не любил программирование, но любил скрипты на bash. И учитывая инфраструктуру с которой приходилось работать, когда АРМ может включаться и выключаться несколько раз за день, а переустановку с *Окон* на Linux производят в ручную администраторы, то было принято решение подготовить небольшой набор скриптов, которые запускались после накатывания ОС.

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

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

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

От хаоса к порядку: первый проект «Помогатор»

Я понимая, что не могу постоянно бегать и помогать в запуске скриптов, начинаю создавать проект под название Помогатор. Он получает базовый графический интерфейс на zenity, я собираю его в удобный deb пакет и кладу его на внутренний сервер. Таким образов администратор в любой момент может установить мой пакет двойным кликом и введя свой пароль. А далее его встретит графический интерфейс и подскажет ему возможности и требования.

Под капотом пока остаются bash скрипты, но все переменные необходимые для выполнения уже запрашиваются в интерактивном режиме, и бывшие администраторы windows понимают, что он них хотят и в какой поле необходимо ввести.

Функционал начинает набирать обороты, и начинают добавляться различные кастомные сборки пакетов и их установка.

Скрытый текст

Исходники лежат в общем доступе, кто хочет изучить или возможно взять за основу милости прошу https://gitflic.ru/project/gabidullin-aleks/pomogator

Что я хотел решить данным проектом?

  1. Автоматизация рутинных задач на месте;

    Теперь я не хожу к каждому АРМ, а любой админ нажав пару кнопок в приложение способен настроить его под нужды конечного сотрудника.

  2. Удобства установки кастомных сборок deb пакетов без флешки и подключения сетевых папок;

    Мною был поднят сервер и туда выложены все deb пакеты, которые могли понадобиться сотруднику, и которых не было в репозитории или они были собраны в ручную. А моя утилита просто переходила по ссылке, скачивала нужный пакет и устанавливала автоматически и если требовался какой-либо pre/post инстальный сценарий, то он уже был прописан в скрипте под капотом.

  3. Я могу постоянно дописывать функционал и обновлять пакет Помогатора, но тут скрывалась и проблема: а как мне обновить пакет, на уже установленных АРМ?

И я решая дописать в Помогатор проверку версии установленной уже в системе, и последнюю версию выложенную на сервере.

Делаю отдельную вкладку на nginx и по данному пути обновляю версию сборки, а локально установленный пакет перед каждым запуском обращаются по url и сверяет версию свою и на сервере. Если версия отличается, то появляется кнопка с требованием обновить пакет и запросом пароля администратора. И вроде все, проблемы решены, но остается одно НО и очень существенное: администратору все ровно необходимо идти и вводить пароль для установки обновленного пакета. И тут без систем автоматизации уже не обойтись.

«На пути к совершенству: развитие проекта и внедрение новых решений».

Начался поиск систем автоматизации, которые работают из коробки и не требуют большого погружения в стилистику написания. Получился всем известный список: Puppet, Chef, Salt и Ansible. Не хотелось использовать агентские службы, так как пришлось бы все ровно настраивать агента на всех АРМ.

Как видно из названия статьи, выбор пал на ansible.

Почему я выбрал именно его:

  1. Понятный для меня формат yaml;

  2. Нет агентских служб;

  3. Можно запускать с использованием пароля от локальной УЗ с необходимыми права;

  4. Идемпотентен ( при прямых руках и как оказалось не всегда они прямые);

  5. Нет необходимости настройки отдельный мастер-нод.

Теперь имея выбранную систему необходимо было улучшить проект, но понимая, что я буду использовать другой подход и технологии, я решаю приступить к новому проекту, который будет уже open-source и будет направлен на помощь всем администраторам, занимающимся импортозамещением. Проект получает свое название AstraWizard и как можно понять из название связан он с ОС Astra Linux, но сейчас не об этом.

Скрытый текст

Код проекта можно посмотреть https://gitflic.ru/project/gabidullin-aleks/astrawizard

Логотип проекта

Логотип проекта

Я понимая, что проект будет помогать в различных задачах и на разных уровнях, начинаю использовать все best-practice, которые получил на первом проекте.

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

Так я формирую базу для приложения:

#-------------------------------------Основные функции------------------------------------#  run_event() {         local command_to_run="$1"     eval "$command_to_run" }  rend_menu() {     local choices=("$@")       selected_item_menu=$(zenity --list --title="Меню выбора" --column="Выберите" "${choices[@]}"  --height=400 --width=500) }  run_menu(){      local menu_items=("$@")            while true; do         rend_menu "${menu_items[@]}"         if [ $? -eq 0 ]; then             if [ -n "${event_menu["$selected_item_menu"]}" ]; then              run_event "${event_menu["$selected_item_menu"]}"             elif  [ "$selected_item_menu" == "$exit_menu" ]; then                 selected_item_menu=""                 return                     fi         else             exit 1         fi      done }  run_app() {     pre-install     disclamer     wizard_info     wizard_version     make_inventory      run_menu "${items_menu_all[@]}" }  #--------------------Запуск программы--------------------------- run_app

Таким образом я могу делать множество вложенных меню и просто придумывать имена для кнопок, которые будут отрисованы в zenity.

Теперь необходимо решить, а как я буду запускать написанные мною playbook’и по нажатию кнопки? Не самое лучшее, но точно удобное для меня решение получись таким:

#выполнить playbook ansible с доп. параметрами playbook(){     (     ansible-playbook "$dir_playbook/preparation.yml" --user=$user_name --private-key=$dir_wizard/id_rsa --extra-vars "ansible_become_pass=$password_hosts" -i $choose_inventory_type --limit="$group_hosts"     #выполнить плейбук     ansible-playbook "$dir_playbook/$selected_item_menu.yml" --user=$user_name --private-key=$dir_wizard/id_rsa --extra-vars "ansible_become_pass=$password_hosts restart_system='$restart_system' path_shell_script='$path_shell_script' path_file_src='$path_file_src' path_file_dst='$path_file_dst' path_from='$path_from' path_to='$path_to' remove_packet='$remove_packet' install_packet='$install_packet' deb_packet_url='$deb_packet_url' deb_packet_localhost='$deb_packet_localhost' text='$text' check_packet='$check_packet' old='$old' new='$new' file_to_path='$file_to_path' command_shell='$command_shell' stop_system='$stop_system' start_system='$start_system' enable_system='$enable_system' disable_system='$disable_system' local_repo='$local_repo' list_packets_freeipa='$list_packets_freeipa'  status_system='$status_system' choose_system='$choose_system' path_to_iso='$path_to_iso' install_freeipa_replica_cmd='$install_freeipa_replica_cmd' list_packets_freeipa_replica='$list_packets_freeipa_replica' name_dir_repo='$name_dir_repo' install_freeipa_server_cmd='$install_freeipa_server_cmd' install_freeipa_client_cmd='$install_freeipa_client_cmd' domain_msad='$domain_msad' ip_msad='$ip_msad' admin_msad='$admin_msad' password_for_msad='$password_for_msad' ip_dc_ipa='$ip_dc_ipa' search_name='$search_name' wallpaper='$wallpaper' usb_cmd='$usb_cmd' install_zabbix_agent='$install_zabbix_agent'" -i $choose_inventory_type --limit="$group_hosts" > "$dir_log"     exit_code=$?         # Проверка кода завершения и отображение соответствующего сообщения             if [ $exit_code -eq 0 ]; then                 zenity --info --title="Успех" --text="Групповая политика успешно применилась.Журнал хранится в $dir_log" --height=100 --width=400             else                 zenity --error --title="Ошибка" --text="Ошибка при применении политики.Журнал хранится в $dir_log" --height=100 --width=400              fi         ) | zenity --progress --pulsate --auto-close --title="Применение политики" --text="Выполняется $selected_item_menu" --height=100 --width=300 --auto-kill }

Объясняю: моя кнопка имеет русское название, допустим — Ввести клиента в домен Freeipa.
Так как я не хочу описывать каждую кнопку, то я использую тот же принцип, что и при создании меню. То есть у меня описано, что при выборе данной кнопки, запускается функция для сбора информации о будущем клиенте, а далее через —extra-vars передает данные значения в Playbook, который называется тоже на русском как и кнопка.

#Работа с freeipa event_menu["$item_menu_gpo_freeipa_server"]="freeipa_server; check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_freeipa_client"]="freeipa_client; check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_freeipa_replica"]="freeipa_replica; check_vars_for_playbook; playbook &"  #ввод в домен freeipa freeipa_client(){     #заполняем необходимые формы"         form_data=$(zenity --forms --title="Введите данные" --text="Введите данные:" \         --add-entry="Введите имя домена. Пример: domain.test" \         --add-entry="Введите ip адрес контроллера домена. Пример: 10.10.10.23" \         --add-password="Введите пароль администратора домена" --height=300 --width=400)     search_name=$(echo "$form_data" | awk -F '|' '{print $1}')     ip_dc_ipa=$(echo "$form_data" | awk -F '|' '{print $2}')     pass_ipa_admin=$(echo "$form_data" | awk -F '|' '{print $3}')     install_freeipa_client_cmd="astra-freeipa-client -d $search_name -p $pass_ipa_admin -y --par \"--hostname \"{{ inventory_hostname }}\".$search_name --force --enable-dns-updates\" " }

Если выполнения playbook’а происходит без необходимости передачи значения для переменных, то можно выполнять его на прямую. Таким образом я могу добавлять либо сбор переменных и далее запуск одноименного с кнопкой playbook’а, либо запуск playbook’а сразу.

#установка удаленного помощника event_menu["$item_menu_gpo_remote_desktop"]="check_vars_for_playbook; playbook &"  #Сменить репозитории# event_menu["$item_menu_gpo_repo_stable"]="check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_repo_1_7_6frozen"]="check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_repo_1_7_7frozen"]="check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_repo_1_8_1frozen"]="check_vars_for_playbook; playbook &" event_menu["$item_menu_gpo_repo_1_8_2frozen"]="check_vars_for_playbook; playbook &"

Важной проблемой, которую я решаю до сих пор, является безопасность подключения с использованием пароля. Я использую при подключение ssh-ключи, однако если на АРМ еще не выполнялся ни один Play, и ключа ssh нет у нас и у конечного хоста в доверенных, то необходимо ими обменяться, а тут уже нужен пароль для подключения.

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

#парсил файл с хостами и передают туда открытый ключ ssh_key_send(){         form_data=$(zenity --forms --title="Введите данные" --text="Введите данные:" \         --add-entry="Введите имя администратора для управляемого хоста" \         --add-password="Введите пароль администратора" \         --add-combo="Выберете список для работы с компьютерами" \         --combo-values="Статический|LDAP|Сетевой Сканер" )         # Разбиение строки с данными на отдельные переменные         user_name=$(echo "$form_data" | awk -F '|' '{print $1}')         password_hosts=$(echo "$form_data" | awk -F '|' '{print $2}')         type_inventory=$(echo "$form_data" | awk -F '|' '{print $3}')         if [ "$type_inventory" = "LDAP" ]; then         choose_inventory_type="$freeipa_inventory_host"         fi         if [ "$type_inventory" = "Сетевой Сканер" ]; then         choose_inventory_type="$network_inventory_host"         fi         if [ "$type_inventory" = "Статический" ]; then         choose_inventory_type="$inventory_host"         fi         ##--new version---##         # Чтение строк из файла в массив lines     mapfile -t lines < "$choose_inventory_type"      # Создание пустого списка для хранения хостов     hosts=()     (     # Цикл по каждой строке     for line in "${lines[@]}"; do         # Проверка, если строка начинается с #         if [[ $line =~ ^\#.*$ ]]; then             continue  # Пропускаем текущую итерацию цикла, если строка начинается с #         fi                if [[ $line =~ ^\[.*$ ]]; then             continue  # Пропускаем текущую итерацию цикла, если строка начинается с [         fi         # Проверка, содержит ли строка "ansible_ssh_host="         if [[ $line == *ansible_ssh_host=* ]]; then             # Взять IP-адрес из строки             ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')         else             # Использовать всю строку как имя хоста             ip=$line         fi          # Проверка, найден ли IP-адрес         if [[ -n $ip ]]; then             # Добавление хоста в список             hosts+=("$ip")         fi     done     # Вывод переменной hosts для проверки     # printf '%s\n' "${hosts[@]}"      # Проход по каждому хосту и выполнение необходимых операций     #ssh-keygen -R "$pc" если необходимо удалить хост     for pc in "${hosts[@]}"; do         if ! ssh-keygen -F "$pc" >/dev/null; then              sshpass -p "$password_hosts" ssh-copy-id -f -o "StrictHostKeyChecking no" -i $dir_wizard/id_rsa.pub $user_name@$pc         fi     done     # Проверка кода завершения и отображение соответствующего сообщения     ) | zenity --progress --pulsate --auto-close --title="Подготовка управляемых ПК" --text="Передача ключей для управления компьютером" --height=100 --width=400 --auto-kill     set_hosts }

Сейчас проект развивается только моими силами, и в свободное от моей основной работы время.

Есть много интересных задач, который я смог решить с помощью данного проекта, например:

  1. Возможность формировать inventory файл на основе: Freeipa/ALDPro, динамически на основе сканирования включенных хостов, ну и статический заполненный в ручную;

  2. Можно используя GUI выбирать группы, на которые необходимо применить ту или иную политику;

  3. Есть возможность выводить всем активным пользователям сообщение на их экран, не зависимо заблокирован он или нет;

  4. Ведение журналов по использованию каждой политики

  5. и другие.

Завершение рассказа, но не нашего пути…

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


ссылка на оригинал статьи https://habr.com/ru/articles/922854/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *