===== Использование ===== Созданный на хосте пользователь должен быть судошным без ввода пароля, либо нужно вводить пароль при выполнении заданий\\ Yaml файл начинается с директивы **"---"** (три тире), обозначает начало очередного файла\\ **Установка** # apt install ansible # yum install epel-release && yum install ansible ==== Общее ==== ==== Исполнения ====
:!: Общее Сука тварь блядь, пидарастический ансибл, тупое хуйло, этому пидару надо точно писать "import_tasks" **SSSSS**uka, или include_tasks(sssss), если не дай бох этому гандону написать без `s`, эта хуила выйбет весь мозг, будет сыпать кучей невнятных ошибок в которых хуй че разберешь, а проблема в одной пидарской букве, спасибо пидары разработчики за такое хуйло\\ По умолчанию ансибл выполняет все команды на хостах, указанных в параметре "**hosts**", со всеми вытекающими, ресурсы/зависимости и т.д.\\ Выполнение можно делегировать на другой хост в т.ч. на локальный, ("**delegate_to: localhost**" и "**local_action**" одно и тоже, но синтаксис разный)\\ При делегировании возможно переменные указывают на делегированный хост, а не на инвентори, хотя тут неясно. Есть еще вариант указания "connection: local", отличий вроде нет\\ Во всех случаях таска выполнится столько раз сколько хостов указано, можно указать "hosts: 127.0.0.1" **тогда выполнится один раз**\\ И кстати нормально, при таком варианте инвентори и все хосты из него доступны, ничего не подменивается\\ Так же есть директива "run_once" для однократного выполнения на первом хосте из доступных\\ --- - hosts: webservers #connection: local tasks: - name: Take out of load balancer pool ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }} delegate_to: 127.0.0.1 - name: Take out of load balancer pool local_action: ansible.builtin.command /usr/bin/take_out_of_pool {{ inventory_hostname }} - name: Send summary mail local_action: module: community.general.mail body: "{{ mail_body }}" run_once: True
==== Инфо о хосте ==== **ansible_facts** - содержит множество данных о целевом хосте\\ Занимает время при каждом выполнении, можно отключить в таске либо в конфиге, "gather_facts: False"\\ ansible 10.11.193.43 -m setup ansible 10.11.193.43 -m setup -a 'filter=facter_*' ansible 10.11.193.43 -m setup -a 'gather_subset=!all,!any,facter' ansible all -m debug -a "var=hostvars[inventory_hostname]" - name: Collect only facts returned by facter ansible.builtin.setup: gather_subset: !all,!facter,!ohai,network {{ ansible_facts['devices']['xvda']['model'] }} {{ hostvars['myHost']['ansible_facts']['os_family'] }} ==== Манипуляции с текстом ==== [[https://docs.ansible.com/ansible/latest/user_guide/complex_data_manipulation.html|Дока]]\\
:!: Вывод/обработка списка Инвентори или group_vars: mylist: - cl_first - cl_second - cl_three - cl_four Простой вывод списка, заменяя префикс --- - name: Test play hosts: 127.0.0.1 tasks: - name: check debug: msg: - "{{ mylist | replace('cl_', 'GSC ') }}" - "{{ list1 | difference(list2) }}" - "{{ list | select('match', '^prefix.*') | reject('match', '^bad1$|^bad2$') | list }}"
==== Работа с группами инвентори ==== # Перечень групп (развернут с хостами) {{ groups }} # Содержимое конкретной группы {{ groups['<имя-группы>'] }} # Перечень без развертывания {{ groups.keys() }} # Тоже без развертывания {{ groups | list }} # Группы в которые входит хост выполнения {{ group.names }} ==== Аргументы/Параметры ==== * **-b [become]** - выполнять с повышенными привилегиями * **[roles]** - список ролей применяемые для плейбука * **-i** - каталог с инвентори * **-a** - аргументы * **-l (--limit)** - группа/хост для выполнения указанного задания ==== Vault ==== При использовании пароля в файле, используется только вывод файла т.е. запуск, можно указывать скрипт\\ # Зашифровать файл ansible-vault encrypt 'path_file' # Расшифровать файл ansible-vault decrypt 'path_file' # Зашифровать строку ansible-vault encrypt_string 'string' Переменная среды: ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass.txt\\ Ключ для запроса в cmd: --ask-vault-pass Ключ для указания в cmd: --vault-password-file # ask password --ask-vault-password, --ask-vault-pass # создать, сохранится зашифрованный ansible-vault create new_file # расшифровать ansible-vault decrypt file.txt --output file.txt_open # открыть для редактирования, при закрытии снова зашифруется ansible-vault edit ansible-vault view
:!: Пример расшифровки в консоли export chiphertext='$ANSIBLE_VAULT;1.1;AES256 63363161346637313765383762356535366637653435313436623435666166326338346163303232 6432313962643561306631303363613334303664666536350a653561613564343236313766646336 66336363396339353734643732343833333135336662613038326435633831613935326434633865 6631626366386234620a386332633965613436386337613138353762623566623461353034653832 62306266396439656533636430643261376331616436656334376430386264656532' printf "%s\n" $chiphertext | ansible-vault decrypt
==== Ограничения запуска ==== Указывая хост (или группу) можно ограничить те места, откуда будет выполняться задача, в блоке "hosts" при этом должна быть указана родительская группа, "all" например, иначе ошибка аля "not found target"\\ ansible-playbook mybook.yml --limit ansible -m ping ==== Работа с файлами ==== Как минимум три модуля:\\ * **lineinfile** - добавить, заменить или удалить одну строку * **replace** - добавить, заменить или удалить несколько строк * **blockinfile** - добавлять несколько строк, окруженных маркерами
:!: Описание ** lineinfile **\\ [[https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html|Дока]]\\ Поиск/замена одной строки\\ - name: Ensure SELinux is set to enforcing mode ansible.builtin.lineinfile: path: /etc/selinux/config regexp: '^SELINUX=' line: SELINUX=enforcing - name: Make sure group wheel is not in the sudoers configuration ansible.builtin.lineinfile: path: /etc/sudoers state: absent regexp: '^%wheel' ** replace **\\ [[https://docs.ansible.com/ansible/latest/collections/ansible/builtin/replace_module.html|Дока]]\\ Поинтереснее, но все же заточен на построчную поиск/замену\\ Указав "любой символ" работает с каждым найденным словом как с отдельным элементом, причем даже пробелы не щадит(\\ Для замены собсна тоже принимает строку\\ - name: "Запись в файл" ansible.builtin.replace: path: /tmp/hosts-test after: "# Start gameserver hosts" before: "# End gameserver hosts" #regexp: '^1.1.(.+)$' #replace: '11.11.11.11' regexp: '.*' replace: '11.11.11.11' ** blockinfile **\\ [[https://docs.ansible.com/ansible/latest/collections/ansible/builtin/blockinfile_module.html|Дока]]\\ Вставляет блок текста, соблюдая строки\\ Можно указать регулярками (либо конец файла) после чего (до чего) вставлять блок, что то одно !!!\\ Работает прежде всего с блоками текста, сам текст указан в **"block"**, обрамление в **"marker"** (директива **"{mark}"** подставляет слова BEGIN/END в метке)\\ С блоками работает нормально, проверяет наличие, если изменений нет, то пропускается, если есть, то обновляет текст, если указать в **block** пустую строку, то блок удалится вместе с метками\\ - name: "Запись в файл hosts" ansible.builtin.blockinfile: #become: yes #become_method: sudo #become_user: root path: /tmp/hosts-test block: | 2.2.2.2 two 3.3.3.3 three 4.4.4.4 four marker: "{mark} Gameservers from ansible inventory"
==== Фильтрация списков словарей ==== * **selectattr rejectattr** - возвращает весь элемент по указанному условию * **map** - вырезает только значения указанных аттрибутов, по одному аттрибуту из всех подходящих элементов\\
:!: Описание ** selectattr rejectattr **\\ Есть нюанс тогда когда указанный для фильтрации ключ [[https://www.0xf8.org/2021/03/filtering-with-ansibles-selectattr-rejectattr-when-the-tested-attribute-can-be-absent/|есть не во всех элементах]]. Указанный по ссылке пример в итоге возвратит два списка, один с установленным аттрибутом, второй без аттрибута.\\ Условия для поиска могут быть разными "equalto match search" и т.д.\\ # Так возвращает элемент (весь элемент словаря) у которого установлен аргумент в указанное значение # "('origin1', 'none')" - для поиска пустого значения "{{ fruit | selectattr('origin1', 'equalto', '21') }}" # "selectattr('origin1', 'undefined')" - Для поиска именно неустановленного аттрибута "{{ fruit | selectattr('origin1', 'undefined') }}" ** map **\\ # Из общего списка словарей вернет только элементы по регулярке, далее покажем только значение только одного аттрибута, всех этих элементов "{{ fruit | selectattr('origin1', 'match', '^..$') | map(attribute='origin1') }}"
==== Block ====
:!:
===== Преобразования типов данных ===== [[https://cn-ansibledoc.readthedocs.io/zh_CN/latest/user_guide/playbooks_filters.html|Дока]]\\ dict2items,items2dict,combine,zip и т.д.\\ ===== Примеры ===== ==== Zabbix ==== Для работы нужен "zabbix-api", плюс ансибл работает со своей версией питона\\ /usr/bin/python3.8 -m pip install zabbix-api ansible-galaxy collection install community.zabbix ==== OverAll ====
:!: Минимальный стартовый пример hosts, можно короче\\ all: hosts: 192.168.0.101 test.yaml --- - hosts: all tasks: - name: Test ping delegate_to: 127.0.0.1 ping: + в конфиге еще путь к инвентори
:!: Перебор хостов циклом Инкремент переменной в цикле \\ --- - hosts: all gather_facts: no run_once: True tasks: - name: Get list group vars: - llist: '' - foo: '' set_fact: foo: "{{ item +' '+ hostvars[item]['ansible_host'] | default('-') }}" llist: "{{ llist + foo }}\n\r " loop: "{{ groups['remote'] }}" - name: show debug: msg: "{{ llist }}" (...) msg: "{{ hostvars[groups['gameservers_all'][0]]['ansible_winrm_host'] }}" (...) (...) vars: - zabbix_gameservers_list: '' set_fact: zabbix_gameservers_list: "{{ zabbix_gameservers_list + hostvars[item]['openvpn_ip'] | default(hostvars[item]['ansible_winrm_host']) | default('# ip not found :(') +' ' + hostvars[item]['inventory_hostname'] }}\n" loop: "{{ groups['gameservers_all'] }}" (...)
:!: Выполнение bash команды и вывод результата --- - hosts: all gather_facts: no run_once: True tasks: - name: Get list group shell: cmd: "ss -ntlua | grep 8086 | wc" register: resultt become: yes - debug: var=resultt.stdout_lines
:!: Работа в консоли Есть несколько вариантов/утилит, в зависимости от задачи\\ Например вывод списка из инвентори команды:\\ **ansible-inventory, ansible-console** - по функционалу почти одинаковые, и почти бесполезные, тупые, конечные (или даже конченные)\\ Можно просто запросом в консоли через debug\\ ansible localhost -m debug -a 'var=groups["my_group"]' ansible localhost -m debug -a 'var=hostvars["myHost"].myVariable' ansible-inventory --graph "my_group" --vars --export # Объявление переменной в консоли ansible-playbook runrole.yml -e "zabbix_status_host='enabled'"
:!: Работа с сервисами --- - name: "Конфигурация systemd для новой службы" systemd: daemon_reload: yes listen: "reload_systemd" - name: "Запуск службы zookeeper" systemd: name: zookeeper state: restarted enabled: true listen: "start_restart_zookeeper" # рекоммендуют запускать при любых изменениях чтобы убедится в применении пар-ов, в случае yes, запустит reload даже если ничего не запускал/останавливал daemon_reload: yes # Ожидание завершения no_block: false # start/stopped — это идемпотентные действия, которые не будут запускать команды без необходимости. После restarted устройство всегда будет подпрыгивать. reloaded всегда перезагружается. state: reloaded # запускает службу в любом случае state: restarted # перезапускает службу state: [stopped, started] Проверка существования и остановка сервиса, несколько попыток с задержкой, пока не остановится, таска упадет если сервис так и не остановится\\ --- - name: "Получаем информацию о сервисах в системе" ansible.builtin.service_facts: - name: "Останавливаем fprofilesac.service если он есть в ОС" systemd: name: 'fprofilesac.service' state: stopped register: fprofilesacInfo until: fprofilesacInfo.state == "stopped" retries: 5 delay: 10 when: "'fprofilesac.service' in services" handlers: запуск/перезапуск/начальная активация службы\\ - name: "Запуск службы FProfilesAutoAcceleration" systemd: name: fprofilesac state: restarted enabled: true daemon_reload: yes listen: "start_restart_fprofilesac" Пример шаблона для unit файла [Unit] Description=FProfilesAutoAcceleration service After=tank.mount After=mnt-update_server_200.mount After=network-online.target #Requires=network-online.target tank.mount mnt-update_server_200.mount [Service] ExecStart=java -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port=10045 \ -Djava.rmi.server.hostname={{ openvpn_ip | default(ansible_host) }} \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false \ -jar {{ fac_appname }}.jar \ -Xmx7G WorkingDirectory={{ fac_path }} Restart=always TimeoutSec=15 User={{ fac_user }} [Install] Alias=fac.service
:!: Работа в ОС - name: "Создание пользователя" user: name: fprofilesac create_home: false system: true shell: /bin/false - name: "Создание директорий" file: path: "{{ item }}" state: directory owner: fprofilesac group: fprofilesac mode: '0755' loop: - "{{ fprofilesac_path }}" - "{{ fprofilesac_path }}/logs" - "{{ fprofilesac_path }}/backups" - name: "Создание ссылки симлинка" file: src: "/src/directory" dest: "dest/symlink-name" state: link
# become для винды become: yes become_method: runas become_user: "{{ admin_user }}"
:!: Запуск процессов Windows Есть проблемы с синхронным/ассинхронным выполнением на хостах, бывает что ансиблом запускаешь процесс, все норм он запускается но завершается вместе с плейбуком т.к. плейбук по умолчанию работает в синхронном режиме, надо запускать в ассинхронном\\ - name: "test" win_shell: | cd {{ my_path }} ./myScript.ps1 async: 15 ignore_errors: yes register: start_app failed_when: start_app.started != 1 # Еще примеры запуска скриптов/процессов - name: "Старт приложения" win_shell: | cd {{ my_path }} ./myScript.ps1 - name: "Старт приложения" win_shell: | cd {{ my_path }} (Start-Process java -ArgumentList "-Xmx4096m -Dapplication.env=PROD Main" -passthru).ID > myScript.pid # Рестарт Stop-Process -Id $(cat myScript.pid) ./myScript.ps1 Переменная с результатом содержит примерно такой вывод, при условии что консоль продолжила работать\\ (и started и finished == 1)\\ Если консоль сразу же корректно завершилась то вывод не отличается, тобишь здесь можно отследить проблему именно при физическом запуске доп процесса, типа если pShell не сможет его запустить, а вот если приложение упадет после запуска, это уже надо другими способами проверять, хотя бы ПИД как минимум\\ ok: [host_mes_mds_prod] => { "msg": { "ansible_async_watchdog_pid": 1516, "ansible_job_id": "41177545019.4340", "changed": true, "cmd": "cd C:\\test_mds\\\n./startMDS2.ps1", "delta": "0:00:00.437520", "end": "2023-12-04 12:59:59.234763", "failed": false, "failed_when_result": false, "finished": 1, "rc": 0, "results_file": "C:\\Users\\ansible\\.ansible_async\\41177545019.4340", "start": "2023-12-04 12:59:58.797243", "started": 1, "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": [] } }
:!: Динамические группы инвентори Смысл в том что можно в рантайме сформировать группу хостов, на основе каких то данных в нем, например факты или хостовые переменные\\ Динамические группы формируются на все попадаемые значения, указанной переменной, содержатся только в памяти и к сожалению похоже нельзя вывести весь список, что усложняет работу с ними\\ Так же использовать можно только в текущем плейбуке, "не отходя от кассы" что называется\\ Например: # Выполняем на всех хостах, группируем по знаяениям в хостовой переменной - hosts: all gather_facts: no tasks: - name: Set 'appgrp_mgaimports' group_by: key: "{{ variable_in_host }}" # Далее запускаем плейбуки с определенными параметрами только для нужной группы - hosts: value_1 gather_facts: false roles: - my_role_for_1 - hosts: value_2 gather_facts: false roles: - my_role_for_2
=== Command & shell === **command** - работает чуть быстрее но запускается не через оболочку ("bin/sh"), поэтому в нем не доступны переменные окружения и прочие плюшки шелла, типа конвейеров, перенаправления вывода и проч\\