Мы перевели статью, где подробно рассматривается использование модуля Ansible Shell и различные способы выполнения удалённых команд на узлах в рамках работы по автоматизации. В статье рассматриваются различные опции и модули для выполнения удалённых команд, а также их различия и то, когда следует использовать каждый из них. Статья для тех, кто изучает Ansible.

Что представляет собой модуль Ansible shell

Модуль Ansible shell используется для выполнения shell-команд на удалённых целевых машинах. Модуль shell принимает имя команды, за которым следует список аргументов, ограниченных пробелами.

Модуль shell выполняется не прямо на целевой машине, а в среде shell (/bin/sh) на ней. Это позволяет использовать специфические для оболочки возможности и функции, такие как pipe |, перенаправление <, >, >> и т.д.

Если вы работаете с узлами Windows, используйте модуль ansible.windows.win_shell.

Примеры использования модуля Ansibe shell

Рассмотрим несколько примеров использования модуля shell в действии.

Пример 1: Модуль Ansible shell для выполнения одной команды

- name: Execute shell command
  ansible.builtin.shell: tail -n 10 /var/log/syslog > tail_syslog.txt

В данном примере мы используем модуль shell для получения последних десяти строк из файла /var/log/syslog и передачи вывода в файл tail_syslog.txt. Модуль shell здесь действительно необходим, поскольку оператор > является функцией shell.

Пример 2: Выполнение команды с помощью модуля shell, если файл не существует

- name: This command will only run when file_to_check.txt doesn't exist
  ansible.builtin.shell: tail -n 10 /var/log/syslog > tail_syslog.txt
  args:
    creates: file_to_check.txt

В приведённом примере команда будет выполнена только в том случае, если файл file_to_check.txt не существует. Это достигается с помощью параметра creates и полезно в тех случаях, когда необходимо проверить, выполнялись ли уже команды, создающие определённый артефакт.

Пример 4: Показать использование диска

- name: Check disk usage and grep for /dev/sda1
  ansible.builtin.shell: df -h | grep /dev/sda1
  register: disk_usage

- name: Display output
  ansible.builtin.debug:
    var: disk_usage.stdout_lines

В приведённом выше примере мы используем команду df -h для отображения использования диска и направляем её для фильтрации результатов только для /dev/sda1. Это пример команды, которая не может быть корректно запущена с помощью модуля command.

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

Пример 5: Выполнение команды в определенном каталоге

- name: Compile software in a specific directory
  ansible.builtin.shell: make install
  args:
    chdir: /path/to/source/code

Это ещё один пример использования ключевого слова args вместе с chdir для выполнения shell-команды в определённом каталоге.

В данном случае команда make install будет выполнена в каталоге path/to/source/code.

Пример 6: Выполнение нескольких команд

- name: Update system packages and clean up
  ansible.builtin.shell: |
    apt-get update &&
    apt-get upgrade -y &&
    apt-get clean

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

Пример 7: Проверка запущенности процесса

- name: Check if a process is running
  ansible.builtin.shell: ps aux | grep 'nginx' | grep -v grep
  register: process_status
  failed_when: process_status.rc != 0

- name: Display process status
  ansible.builtin.debug:
    var: process_status.stdout_lines

В данном примере мы используем ps aux для получения списка всех процессов, а затем оператор pipe | для поиска интересующего нас процесса nginx.

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

Пример 8: Выбор оболочки, используемой для выполнения команды

- name: Change shell to bash
  ansible.builtin.shell: cat /var/log/*log > logs_snaphot.txt
  args:
    executable: /bin/bash

В данном примере мы используем параметр executable для указания оболочки, используемой для выполнения команды. Это может быть полезно в тех случаях, когда стандартный /bin/sh не поддерживает функцию, которую мы хотим использовать.

Пример 9: Использование Templated Variable в команде

- name: Run the command using a templated variable to avoid injection
  ansible.builtin.shell: cat {{ logs_snapshot_file|quote }}
  register: logs

В приведённом выше примере мы используем в команде templated variable.

При использовании переменных Ansible в командах обязательно используйте {{ var | quote }} вместо {{ var }} для добавления кавычек вокруг значения переменной, что позволяет избежать попадания другого кода и гарантирует, что содержимое переменной будет рассматриваться как один аргумент, а не как команда, которую оболочка должна интерпретировать.

Альтернативные варианты удалённого запуска команд с помощью Ansible

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

Более того, специализированные модули лучше обрабатывают ошибки.

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

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

Мы уже познакомились с модулем shell, а далее рассмотрим модули command, expect, script и raw, предназначенные для этой цели.

Модуль Ansible command 

Модуль command в Ansible выполняет команды на всех выбранных хостах. Это один из самых простых модулей; он принимает имя команды, за которым следует список аргументов, ограниченных пробелами. Если вы работаете с  Windows, используйте вместо него модуль ansible.windows.win_command.

Важно отметить, что при использовании модуля command команды не обрабатываются через local shell. Это означает, что такие переменные, как $HOSTNAME, не будут работать, а специфические для оболочки функции, такие как команды конвейеризации и операторы перенаправления (<, >, >>, | и т.д.), будут интерпретироваться некорректно.

Такое поведение делает модуль command более безопасным и предсказуемым, чем модуль shell, который мы рассмотрим далее. Используя модуль command, вы можете быть уверены, что команда будет выполнена именно так, как вы её написали, без каких-либо неожиданных побочных эффектов от обработки shell.

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

 - name: Display list of files in /var/log
   ansible.builtin.command: ls /var/log
   register: log_files
 - name: Output list of log files
   ansible.builtin.debug:
     var: log_files.stdout_lines

В приведённом примере выполняемой командой является ls /var/log. Вывод команды регистрируется в переменной log_files, которая затем отображается с помощью модуля debug в следующей задаче.

Приведем пример вывода двух вышеуказанных задач:

Приведём еще один пример использования модуля command для проверки того, установлен ли пакет в целевой системе:

- name: Check if NGINX is installed
  ansible.builtin.command: which nginx
  register: nginx_installed
  changed_when: false
  failed_when: false

- name: Display a message if NGINX is not installed
  ansible.builtin.debug:
    msg: "NGINX is not installed on this system."
  when: nginx_installed.rc != 0

Здесь мы проверяем, установлен ли NGINX, выполнив команду which nginx и проверив код возврата (rc).

Строка changed_when: false гарантирует, что Ansible не будет сообщать об изменениях при каждом выполнении плейбука Ansible, а строка failed_when: false заставляет команду выполняться успешно и не блокировать продолжение работы плейбука, даже если пакет не найден.

Приведём пример выполнения двух вышеуказанных задач:

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

- name: Run multiple commands
  ansible.builtin.command: "{{ item }}"
  with_items:
    - ls /var/log
    - touch /tmp/tmp.txt
    - ps aux

Модуль Ansible expect

Модуль expect в Ansible выполняет команды и отвечает на запросы. Он часто используется для автоматизации взаимодействия с приложениями, требующими ответов на подсказки.

При использовании модуля expect необходимо указать команду, которая будет выполняться, и ответы. Ответы представляют собой сопоставление ожидаемых string/regex и string, на которую нужно ответить.

Обратите внимание, что команды не обрабатываются через shell.

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

- name: Install software
  ansible.builtin.expect:
    command: /path/to/softare/install.sh
    responses:
      Continue\?: "yes"
      Please enter the installation directory: "/path/to/installation/directory"
      Enable automatic updates\?: "no"

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

Поскольку модуль expect был разработан для простых случаев использования, для более сложных и продвинутых случаев следует использовать модуль shell или scripts.

Модуль Ansible script

Модуль script выполняет локальный скрипт на удалённых узлах после его передачи. Этот модуль принимает имя скрипта, за которым следует список аргументов, разделённых пробелами. Заданный сценарий будет обработан через среду shell на удалённом узле.

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

Вот базовый пример использования модуля script:

- name: Run a script on a remote node
  ansible.builtin.script: /path/to/local/script.sh --flag some_value

Приведём более сложный пример использования ключевого слова args для управления окружением сценария.

- name: Run a script with custom environment variables
  ansible.builtin.script: /path/to/local/script.sh
  args:
    executable: /bin/bash
    chdir: /tmp/
    creates: /tmp/example.txt

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

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

Модуль Ansible raw

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

Этот модуль не требует наличия Python на удалённой системе, что делает его полезным в ситуациях, когда Python не установлен или когда вы имеете дело с устройствами, не поддерживающими Python.

Этот модуль также поддерживается для Windows.

Вот пример использования модуля raw для установки Python с помощью yum:

- name: Install Python
  ansible.builtin.raw: yum install -y python3

Стоит отметить, что модуль raw менее безопасен, идемпотентен и предсказуем, чем большинство других модулей Ansible, и не поддерживает такие расширенные возможности Ansible, как подстановка переменных, циклы, условные сигналы и т.д.

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

В каких случаях использовать модуль shell, а в каких — модуль command

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

В отличие от command-модуля, shell-модуль выполняется не непосредственно на цели, а в среде shell на цели. Это позволяет использовать специфические для оболочки возможности и функции, такие как pipe |, перенаправление <, >, >> и другие. Если ваши команды содержат какие-либо специфические для оболочки функции, такие как конвейеризация команд, подстановка переменных, перенаправление вывода в файлы, то необходимо использовать модуль shell.

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

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

При написании плейбуков лучше всего использовать модуль command, если только использование функций shell не требуется в явном виде.

Ознакомьтесь с Ansible best practices.

Ключевые моменты

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

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


Если вы узучаете Ansible, приходите на курс «Ansible: Infrastructure as Code»Вы сможете систематизировать знания и получить практику на наших стендах.

Новый поток стартует 20 ноября. Посмотреть программу и записаться на курс можно на нашем сайте. Ждём на курсе!

Комментарии (1)


  1. Tamerlan666
    17.11.2023 14:00
    +1

    грязные SSH-команды

    :)