===== Использование =====
Созданный на хосте пользователь должен быть судошным без ввода пароля, либо нужно вводить пароль при выполнении заданий\\
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"), поэтому в нем не доступны переменные окружения и прочие плюшки шелла, типа конвейеров, перенаправления вывода и проч\\
:!: Результат команды в переменную
- name: "Получаем имя из конфига"
ansible.builtin.shell: "yq '.profile' {{ workdir }}{{ profile_config }}"
register: try_name
- name: "msg"
debug:
msg: "{{ try_name }}"
ok: [localhost] => {
"msg": {
"changed": true,
"cmd": "yq '.profile' /home/path/my-file.yml",
"delta": "0:00:00.121520",
"end": "2024-11-20 12:35:49.242621",
"failed": false,
"failed_when_result": false,
"rc": 0,
"start": "2024-11-20 12:35:49.121101",
"stderr": "",
"stderr_lines": [],
"stdout": "my-super-name",
"stdout_lines": [
"my-super-name"
]
}
}
:!: