Система управления автоматической конфигурации серверов. Контроль и отслеживание изменений в системе: версий установленных пакетов, файлов конфигурации, настроек ОС.
Пособие по Ansible
Документация
Список модулей
Task | Базовый блок. Обычно модуль с параметрами, выполняющий определённое действие. |
---|---|
Role | Типа функции - объединяет задачи, файлы, шаблоны, переменные и обработчики в единое целое. |
Play | Сущность, связывающая задачи и роли с целевыми серверами. |
Playbook | Файл yaml для запуска, где перечислены все нужные плеи. |
Модули используются в задачах конфигурации и запускаются на целевых серверах.
Плагины упрощают работу с Ansible на контрольной ноде — сервере, где запускается сам Ansible.
# копирование файла, используя модуль copy ansible localhost -m copy -a "src=file.txt dest=/tmp/file.txt"
Важное отличие от простого копирования через консоль или скрипт - идемпотентность, т. е., свойство функции или операции при повторном применении к объекту давать тот же результат, что и при первом. В данном случае, при втором запуске файл копироваться не будет, потому что ansible вычисляет контрольные суммы файлов и понимает, что ничего не изменилось. Можно, конечно, сделать проверки и в скрипте, но это трудоёмкий процесс, требующий изобретения разных подходов для разных задач.
Роли в Ansible - это список задач.
Плейбук - это список плеев, где перечисляются роли и к каким серверам они применяются.
Базовый элемент.
# Cписок из 5 задач - name: Модуль maven_artifact скачивает приложение из Nexus maven_artifact: dest: "/opt/project/backend/lib/file.jar" repository_url: "https://nexus-srv/repository/project" group_id: "com.group.id" artifact_id: "backend" version: "0.1.0" - name: Добавление сервисного пользователя user: name: "serviceuser" create_home: no shell: /sbin/nologin - name: Шаблонизация конфигурации - управление настройками приложения с помощью переменных template: src: project-backend.service.j2 dest: /etc/systemd/system/project-backend.service - name: Перечитать конфигурацию systemd systemd: daemon_reload: yes - name: Запуск сервиса service: name: project-backend state: running
name
отображается в отчёте. После него идёт название модуля. Справка по модулю - ansible-doc <имя модуля>
.
Проверка, всё ли выполнилось так, как нужно.
fail
- инициировать сбой
assert
- убедиться, что условие выполняется (в некотором смысле, fail наоборот)
- hosts: 127.0.0.1 gather_facts: no connection: local vars: fail_via_fail: false fail_via_assert: false fail_via_complex_assert: true tasks: - name: Fail if conditions warrant a failure fail: msg: "Task failed" when: fail_via_fail - name: Fail if an assertion isn't validated assert: that: "fail_via_assert != true" - name: Assertions can have certain conditions assert: that: - "fail_via_fail != true" - "fail_via_assert != true" - "fail_via_complex_assert != true"
Вывод информации.
tasks: # Вывод переменной foo, свойства rc - debug: var=foo.rc # Лучше использовать такой синтаксис, т. к. если в свойстве будет дефис, то может быть ошибка. - debug: var=foo['rc'] # Вывод сообщения - debug: msg="Hello {{ foo['rc'] }}"
Это дополнительные задачи, обработчики, вызываемые по запросу. Вызываются из задачи с помощью notify: <имя хэндлера>
, если только эта задача реально выполняется, т. е. что-то изменяется.
Хэндлеры выполняются в конце плейбука после всех задач, если нет спец. настроек.
- name: Install Apache hosts: staging_servers become: yes vars: source: ~/for_site/index.html destination: /var/www/html tasks: - name: Install package: name: apache2 state: latest - name: Start & enable service: name: apache2 state: started enabled: yes - name: Copy index.html copy: src: "{{ source }}" dest: "{{ destination }}" mode: 0774 owner: www-data group: www-data notify: restart apache handlers: - name: restart apache service: name: apache2 state: restarted
Если нужно выполнить хэндлер сразу после какой-то задачи, не дожидаясь выполнения всех остальных задач, то сразу после неё ставится спецзадача
- name: Kick handlers immediately meta: flush_handlers
Т. к. по умолчанию обработчики выполняются в конце, есть шанс, что какая-то из задач, вызывающая хэндлер, выполнится, а затем какая-то из последующих задач завершится сбоем и обработчики уже не сработают, т. к. сбоем завершится весь плейбук. Чтобы форсировать выполнение хэндлеров в таком случае, нужно запускать команду с ключом –force-handlers
.
ansible-playbook -i hosts playbook.yml --force-handlers
Вызывать можно несколько обработчиков из одной задачи:
notify: - restart apache - restart memcached
А можно вызывать обработчик из другого обработчика, т. е., вызвать restart memcached из restart apache.
Набор задач, вызываемый как единый модуль или функция одной строкой с параметрами. Это каталог с упорядоченными по назначению YAML-файлами.
Файловая структура роли:
defaults/main.yml # значения переменных по умолчанию для роли. files/ # каталог с файлами, не требующими шаблонизации. handlers/main.yml # обработчики, запускаются только при получении соответствующих уведомлений (notify) от задач. meta/main.yml # метаданные с описанием авторов роли, зависимостей и версии. tasks/main.yml # задачи, запускаемые ролью. templates/ # каталог с jinja2-шаблонами. tests/ vars/main.yml # переменные для роли.
Генерация файловой структуры роли: ansible-galaxy init <role name>
.
Другая организация плейбука, где можно указать, откуда брать задачи/хэндлеры и т. д. Можно не создавать роль, а обойтись этим, если плейбук не очень большой. Если он большой, лучше использовать роли.
Здесь идёт ссылка на файл, куда вынесены соответствующие куски плейбука.
handlers: - import_tasks: handlers/apache.yml - import_tasks: handlers/app.yml tasks: - import_tasks: tasks/apache.yml - import_tasks: tasks/app.yml
Import отличается от include тем, что import сразу формирует файл плейбука при парсинге во время запуска с подстановкой переменных, а include выполняет эти файлы, когда доходит до них. Т. е., если в задаче используются какие-то переменные, значение которых формируется по ходу выполнения плейбука, то нужно использовать Include. Если это просто для выноса кусков кода для расхламления плейбука, то тогда Import.
После слов import
и include
можно писать что угодно: import_aaa
или include_greatThings
Импортировать/включать можно и целый плейбук. Для этого include нужно разместить на верхнем уровне.
name: playbook1 hosts: all become:true tasks: - import: tasks/apache.yml - include: playbook2.yml
Связь задач/ролей с целевыми серверами.
--- - name: Запуск backend сервиса project # Шаблон целевых хостов это группа хостов с именем backend hosts: backend # Список ansible-ролей для backend-серверов roles: - project-backend - name: Запуск frontend сервиса project # Шаблон целевых хостов это группа хостов с именем frontend hosts: frontend # Список ansible-ролей для frontend-серверов roles: - project-frontend
Запуск плейбука: ansible-playbook playbook.yaml
. Будет выполняться один плей за другим, запуская задачи ролей на указанных серверах.
Задачи можно помечать определённым словом, и тогда можно будет запускать плейбук только с выбранными задачами.
tasks: - name: ... shell: ... tags: - api - echo
Запустит только задачи с тэгом api.
ansible-playbook playbook.yml --tags=api
Группы хостов берутся из inventory. Это в простом случае может быть текстовый файл со списком IP-адресов или имён серверов, а может быть и в формате yaml.
# Названия чувствительны к регистру - [staging_servers] и [staging_Servers] будут разными группами. # Все сервера из всех групп входят в группу all # можно писать сервера вообще без группы, они будут входить в группу ungrouped 192.168.1.1 192.168.1.2 server.example.com server2.example.com ansible_host=192.168.1.3 [staging_db] 192.168.1.51 192.168.1.52 [staging_web] 192.168.1.61 192.168.1.62 [staging_app] 192.168.1.71 192.168.1.72 # Группа staging_all, объединяющая группы staging_db, staging_db и staging_app [staging_all:children] staging_db staging_db staging_app [staging_servers] # чтобы не прописывать одинаковые значения ansible_user, ansible_ssh_private_key и т. д., можно прописать переменные # См. [staging_servers:vars] ниже #k2 ansible_host=192.168.1.22 ansible_user=user ansible_ssh_private_key=/home/user/.ssh/id_ed25519 #k3 ansible_host=192.168.1.23 ansible_user=user ansible_ssh_private_key=/home/user/.ssh/id_ed25519 k2 ansible_host=192.168.1.22 k3 ansible_host=192.168.1.23 # Vars лучше не прописывать в файле inventory, а держать отдельно, но так тоже можно [staging_servers:vars] ansible_user=user ansible_ssh_private_key=/home/user/.ssh/id_ed25519 [windows_servers] windows2012 ansible_host=192.168.1.10 windows2016 ansible_host=192.168.1.11 [windows_servers:vars] ansible_user = myadmin ansible_port = 5986 ansible_connection = winrm ansible_winrm_server_cert_validation = ignore
# Показать список хостов с принадлежащими им переменными ansible-inventory --list # Типа tree для файлов ansible-inventory --graph # проверка связи (inventory - hosts.txt, все хосты, модуль ping) ansible -i hosts.txt all -m ping
Проблема в том, что если на сервера раньше не заходили, запросит кучу подтверждений ssh fingerprint. Чтобы этого избежать, рисуем ansible.cfg, где заодно указываем inventory-файл по умолчанию.
[defaults] host_key_checking = false inventory = ./hosts.txt
# проверка связи c учётом ansible.cfg ansible all -m ping
Формат yaml:
all: # Обязательный параметр (все сервера) children: backend: # (группа серверов backend) hosts: 192.168.3.4: dev-backend.example.com: frontend: hosts: 192.168.3.5: dev-frontend.example.com: dev: hosts: dev-backend.example.com: dev-frontend.example.com: prod: hosts: 192.168.3.4: 192.168.3.5:
После этого можно запускать плейбук с ограничением по группе, например, ansible-playbook playbook.yaml --limit dev
В файле inventory можно прописывать переменные, но это не рекомендуется:
all: children: backend: hosts: dev-backend.example.com: # Переменная только для этого хоста ansible_user: ansible vars: # Переменные для группы backend backend_version: 3.1.1 vars: # Переменные для всех хостов в inventory ansible_connection: ssh
Лучше создавать файлы с переменными в каталоге group_vars
рядом с playbook.yaml
, например, файл group_vars/all.yaml
будет выглядеть так:
ansible_connection: ssh
Для часто меняющейся инфраструктуры используется динамический inventory. В Ansible есть плагин динамической инвентаризации, который подключается к облачной платформе или API системы виртуализации и получает список серверов.
Используются для управления поведением задач подобно параметрам функции в программировании.
Переменные могут быть определены в плейбуках, в inventory, внутри ролей или передаваться в командной строке запуска плейбука (т. н. extra vars).
В именах переменных могут использоваться только буквы, цифры (не первый символ) и подчёркивания.
# Extra vars имеют наивысший приоритет ansible-playbook playbook.yaml --extra-vars "my_var=value"
Переменные, указанные в файлах group_vars/*.yaml
, в свою очередь, имеют приоритет над переменными, заданными в Ansible-ролях.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#ansible-variable-precedence
https://docs.ansible.com/ansible/latest/reference_appendices/general_precedence.html#general-precedence-rules
Добавить переменную в ~/.bash_profile
.
Если строки с regexp:
нет, то она создастся. Если есть, то она изменится.
Если под regexp:
попадает несколько строк, то изменится последняя.
- name: Var playbook hosts: k3 tasks: - name: Add environment variable to the remote user's shell lineinfile: dest: "~/.bash_profile" regexp: '^ENV_VAR=' line: 'ENV_VAR=value' # Вывод переменной - name: Get the ENV_VAR value shell: 'source ~/.bash_profile && echo $ENV_VAR' # Если не указать bash, то может запуститься sh с ошибкой "stderr": "/bin/sh: 1: source: not found" args: executable: /bin/bash register: envvar # Либо в составе сообщения, либо просто вывести переменную - debug: msg="The value is {{ envvar.stdout }}" - debug: var=envvar.stdout
Общесистемные переменные нужно загонять в файл /etc/environment
. Понадобится become: true
.
Положим, есть плейбук со скачиванием файла и настройкой прокси-сервера.
- name: Var playbook hosts: k3 tasks: - name: Download a file get_url: url: http://ipv4.download.thinkbroadband.com/5MB.zip dest: /tmp environment: http_proxy: http://proxy.example.com https_proxy: https://proxy.example.com
Можно вынести переменные прокси на верхний уровень, тогда в задаче можно на них сослаться.
vars: proxy_vars: http_proxy: http://proxy.example.com https_proxy: https://proxy.example.com tasks: - name: ... environment: proxy_vars
А можно сделать так, что переменные будут действовать на все задачи:
environment: http_proxy: http://proxy.example.com https_proxy: https://proxy.example.com tasks: - name: ... - name: ...
Переменные из файла:
vars: key: value vars_files: - vars/main.yml
Задать переменную ansible_super_secret
из значения переменной окружения SUPER_SECRET
:
ansible_super_secret: "{{ lookup('env', 'SUPER_SECRET') }}"
Это переменные, которые формируются автоматически на основе сбора данных из системы. Это нужно, потому что в разных системах могут быть разные пакетные менеджеры, разная иерархия каталогов файловой системы и т. д.
При запуске плея, при первом подключении к хостам и до запуска указанных задач, Ansible запускает модуль setup, который выполняет анализ окружения целевого хоста и формирует набор переменных в словаре ansible_facts.
# принудительный запуск на локальной машине ansible localhost -m setup
Так как сбор фактов - операция ресурсоёмкая, применяются следующие техники для ускорения работы:
- hosts: development gather_facts: no tasks: - name: waiting for connection wait_for_connect
- hosts: development gather_facts: no tasks: - name: wait for connection wait_for_connect - name: selected facts ansible.builtin.setup: filter: - 'ansible_distribution' - 'ansible_machine_id' - 'ansible_*_mb'
ansible.cfg
. [inventory] cache = True cache_plugin = jsonfile cache_connection = ~/.cache/ansible cache_timeout = 3600
Это движок для подстановки переменных или для динамического формирования конфигурационных файлов. Ссылка на переменную идёт в любом файле yaml в двойных фигурных скобках.
- name: Install package {{ package_name }} package: name: "{{ package_name }}"
Чтобы создавать файлы на основе шаблонов, используется модуль template.
- name: Create app backend service unit template: src: app-backend.service.j2 dest: /etc/systemd/system/app-backend.service
Если переменные бэкенда описаны в group_vars/backend.yml
:
backend_maven_version: 3.4.1 backend_service_user: app_user backend_report_directory: /opt/app/reports backend_lib_directory: /opt/app/lib/
То можно генерировать systemd-unit на основе шаблона app-backend.service.j2
:
[Unit] Description=app [Service] User={{ backend_service_user }} Restart=always Environment=REPORT_PATH={{ backend_report_directory }} ExecStart=/usr/bin/java -Xrs -jar {{ backend_lib_directory }}/sausage-store-{{ backend_maven_version }}.jar [Install] WantedBy=multi-user.target
Используются для нескольких ОС, где разные пути к конфигам и один и тот же пакет называется по-разному, например apache2 в Debian и httpd в CentOS. Создаётся несколько файлов с переменными:
apache_package: httpd apache_service: httpd apache_config_dir: /etc/httpd/conf.d
apache_package: apache2 apache_service: apache2 apache_config_dir: /etc/apache2/sites-enabled
В плейбуке рисуется раздел с предварительным заданием (на уровне tasks), который читает первый совпадающий файл с переменными:
pre_tasks: # - debug: var=ansible_os_family - name: Load variable file include_vars: "{{ item }}" with_first_found: - "vars/apache_{{ ansible_os_family }}.yml" - "vars/apache_default.yml"
Вместо ansible_os_family
можно использовать ansible_distribution
.
Базовый способ настройки Ansible. В ansible.cfg определены директивы конфигурации для всего набора утилит (ansible, ansible-playbook, ansible-galaxy, ansible-vault и т. д.). В этом файле указывается расположение ролей, конфигурация inventory и имя пользователя для подключения к хостам. Пример:
[defaults] roles_path = roles inventory = inventory remote_user = ansible vault_password_file = .vault host_key_checking = False [privilege_escalation] become = true ; повышать привилегии (sudo)
Ansible поддерживает следующие способы передачи параметров конфигурации (по убыванию приоритета):
ansible-config
покажет список всех параметров конфигурации, их стандартные значения и пояснения, откуда взялось то или иное значение.
Ansible ищет конфигурационные файлы в следующем порядке:
./ansible.cfg
(файл в текущем каталоге)~/.ansible.cfg
(файл в домашнем каталоге)/etc/ansible/ansible.cfg
Используется только первый найденный файл, остальные игнорируются.
https://docs.ansible.com/ansible/devel/reference_appendices/config.html
Шифрованное хранилище для секретных данных - паролей, токенов и т. п. Командой ansible-vault
их можно зашифровать, а при запуске Ansible передать спец. пароль, который расшифрует данные в процессе работы:
ansible-vault encrypt_string "VerySecretPassword!@#$%" --name secret_pass
Вывод команды указывается в group_vars/<my_group>.yml
:
secret_pass: !vault | $ANSIBLE_VAULT;1.1;AES256 39836982658142836487543449583409549856387698734580384098152098347874095687623654 ... 8457
Передача пароля для расшифровки происходит в интерактивной сессии при запуске ansible-playbook с параметром --ask-vault-pass
:
ansible-playbook playbook.yaml --ask-vault-pass
При запуске Ansible в CI-системе этот способ не подойдёт и потребуется механизм передачи пароля без участия пользователя.
Для этого нужно сформировать файл с паролем на предыдущем шаге CI и указать его расположение в ansible.cfg
:
vault_password_file = .vault
Файл .vault
необходимо добавить в .gitignore
!
Пример структуры.
. ├── README.md ├── ansible.cfg ├── roles │ ├── backend │ └── frontend ├── group_vars │ ├── all.yaml │ ├── backend.yaml │ └── frontend.yaml ├── inventory │ ├── static.yaml │ └── cloud.yaml └── playbook.yaml
Коллекция - совокупность необходимых ролей, плейбуков, плагинов для inventory, собственных модулей. Коллекции можно хранить в git и устанавливать их оттуда или публиковать на специальном сервисе Ansible Galaxy.
Примерный вид рабочего процесса для автоматизации управления конфигурацией:
requirements.yaml
ansible-galaxy
и формирование inventoryansible-playbook
с указанием плейбукаЭтот рабочий процесс может быть запущен на CI системе или в специальной системе, построенной поверх Ansible — AWX. У AWX есть GUI, упрощающий некоторые задачи по работе с Ansible, например, сделать кнопку для техподдержки для выполнения рутинных операций, которые требуются как реакция на инцидент или запрос от пользователя.
Ansible Galaxy - общий ресурс для хранения коллекций и ролей.
По умолчанию роли оттуда ставятся в глобальный каталог типа /etc/ansible/roles
. Если нужно установить локально в текущий плейбук, нужно в ansible.cfg
прописать
roles_path = ./roles
Рядом положить файл requirements.yml
с перечислением ролей-зависимостей и, по желанию, их версий.
--- roles: - name: elliotweiser.osx-command-line-tools version: 2.3.0 - name: geerlingguy.mac version: 4.0.0
После этого запустить установку ролей из Galaxy, указав файл зависимостей. Скачанные роли окажутся в подкаталоге roles, как указано в файле конфигурации.
ansible-galaxy install -r requirements.yml
Плейбук:
--- - hosts: localhost connection: local vars: homebrew_installed_packages: - pv roles: - elliotweiser.osx-command-line-tools - role: geerlingguy.mac become: true
По степени увеличения сложности:
yamllint
ansible-playbook –syntax-check
- встроенная проверка синтаксиса. Ловит не всё, например, include:
не может, т. к. для этого нужно запустить плейбук.ansible-lint
ansible-playbook –check
(against prod)Проверка YAML на корректность. Даже если плейбук запускается и работает, то шероховатости в его написании могут впоследствии привести к проблемам.
# Установка sudo apt install yamllint # или pip3 install yamllint yamllint playbook_delegate.yml playbook_delegate.yml 1:1 error too many blank lines (1 > 0) (empty-lines) 2:1 warning missing document start "---" (document-start) 4:11 warning truthy value should be one of [false, true] (truthy) 11:1 warning comment not indented like content (comments-indentation) 13:81 error line too long (156 > 80 characters) (line-length) 18:13 warning truthy value should be one of [false, true] (truthy) 19:81 error line too long (155 > 80 characters) (line-length) 23:1 error too many blank lines (3 > 0) (empty-lines)
Проверка на соответствие best practices.
# Установка sudo apt install ansible-lint # или pip3 install ansible-lint ansible-lint playbook2.yml WARNING Listing 4 violation(s) that are fatal yaml: truthy value should be one of [false, true] (truthy) playbook2.yml:3 package-latest: Package installs should not use latest playbook2.yml:11 Task/Handler: Install yaml: wrong indentation: expected 4 but found 2 (indentation) playbook2.yml:11 yaml: truthy value should be one of [false, true] (truthy) playbook2.yml:19 You can skip specific rules or tags by adding them to your configuration file: # .ansible-lint warn_list: # or 'skip_list' to silence them completely - package-latest # Package installs should not use latest - yaml # Violations reported by yamllint Finished with 4 failure(s), 0 warning(s) on 1 files.
Существует модуль synchronize, где можно настроить делегирование и тем самым запустить копирование непосредственно с одного хоста на другой.
- name: Synchronization of src on delegate host to dest on the current inventory host. ansible.posix.synchronize: src: /first/absolute/path # Путь на делегируемом хосте-источнике delegate.host dest: /second/absolute/path # Путь на текущем обрабатываемом хосте delegate_to: delegate.host
Но есть проблема аутентификации: каким образом обеспечить доступ с делегируемого хоста на конечные? Есть вариант SSH Agent Forwarding, но это вроде как небезопасно. Поэтому в общем случае копируем сначала на менеджера (достаточно одного раза - run_once: true
), а потом с него на конечные сервера.
--- - name: Sync hosts: k3 gather_facts: no tasks: - name: Copy WAR to worker from source command: rsync k2:/home/user/file.war /home/user/file.war run_once: true delegate_to: localhost - name: Copy WAR to destination from worker synchronize: src: /home/user/file.war dest: /home/user/file.war
- name: "Get service status" shell: systemctl --user is-active {{ service_name }}.service register: service_status ignore_errors: true - name: "Get process ID" shell: pgrep -f "{{ catalina_base }}" register: process_id ignore_errors: true - name: "Stop process by script" script: "{{ deploy_dir }}/stop.sh" args: creates: "~/.config/systemd/user/{{ service_name }}.service" when: service_status.stdout != 'active' and process_id.stdout != "" - name: "Kill process" shell: kill -9 "{{ process_id.stdout }}" when: service_status.stdout != 'active' and process_id.stdout != "" - name: "Stop service" systemd: scope: user name: "{{ service_name }}" state: stopped when: service_status.stdout == 'active' - name: "Create folders" file: path: "{{ item }}" state: directory with_items: - "~/.config/systemd/user" - "{{ catalina_base }}/conf" - "{{ catalina_base }}/webapps" - "{{ pool_settings_dir }}" - name: "Enable systemd lingering" shell: loginctl enable-linger $USER - name: "Copy WAR file to worker from source ({{ war_source_server }})" command: rsync "{{ deploy_user }}@{{ war_source_server }}:{{ catalina_base }}/webapps/app.war" /tmp/app.war delegate_to: localhost run_once: true when: copy_war|bool == true - name: "Copy WAR to destination" syncronize: src: /tmp/app.war dest: "{{ catalina_base }}/webapps/app.war" when: copy_war|bool == true - name: "Copy conf files" copy: src: "{{ item }}" dest: "{{ catalina_base }}/conf" owner: "{{ deploy_user }}" mode: 0660 with_fileglob: - "conf/*" - "conf/{{ my_env }}/*" when: copy_conf|bool == true - name: "Copy scripts" copy: src: "{{ item }}" dest: "{{ deploy_dir }}" owner: "{{ deploy_user }}" mode: 0770 with_fileglob: - "scripts/*" - name: "Copy pool-settings.xml" template: src: "pool-settings.xml.{{ my_env }}.j2" dest: "{{ pool_settings_dir }}/pool-settings.xml" mode: 0660 - name: "Copy unit file" template: src: "{{ service_name }}.service.j2" dest: "~/.config/systemd/user/{{ service_name }}.service" - name: "Start service" systemd: scope: user daemon_reload: true name: "{{ service_name }}" state: started enabled: true - name: "Remove passwords from pool-settings.xml" replace: path: "{{ pool_settings_dir }}/pool-settings.xml" regexp: '(password\">).*(</entry>)' replace: '\1\2'
[WARNING]: Collection ansible.posix does not support Ansible version 2.17.0
ansible-galaxy collection install ansible.posix Starting galaxy collection install process [WARNING]: Collection junipernetworks.junos does not support Ansible version 2.17.0 [WARNING]: Collection frr.frr does not support Ansible version 2.17.0 [WARNING]: Collection ibm.qradar does not support Ansible version 2.17.0 [WARNING]: Collection cisco.asa does not support Ansible version 2.17.0 [WARNING]: Collection ansible.posix does not support Ansible version 2.17.0 Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`. ansible-galaxy collection install ansible.posix --force