====== Ansible ====== Система управления автоматической конфигурации серверов. Контроль и отслеживание изменений в системе: версий установленных пакетов, файлов конфигурации, настроек ОС. [[https://habr.com/ru/post/305400/|Пособие по Ansible]]\\ [[https://docs.ansible.com/ansible/latest/index.html|Документация]]\\ [[https://docs.ansible.com/ansible/2.9/modules/list_of_all_modules.html|Список модулей]]\\ ^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 <имя модуля>''. ==== Управление ошибками / кодами возврата, условия ==== https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html ==== fail / assert ==== Проверка, всё ли выполнилось так, как нужно.\\ ''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" ==== Debug ==== Вывод информации. tasks: # Вывод переменной foo, свойства rc - debug: var=foo.rc # Лучше использовать такой синтаксис, т. к. если в свойстве будет дефис, то может быть ошибка. - debug: var=foo['rc'] # Вывод сообщения - debug: msg="Hello {{ foo['rc'] }}" ===== Handlers ===== Это дополнительные задачи, обработчики, вызываемые по запросу. Вызываются из задачи с помощью ''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 ''. ==== Include / Import ==== Другая организация плейбука, где можно указать, откуда брать задачи/хэндлеры и т. д. Можно не создавать роль, а обойтись этим, если плейбук не очень большой. Если он большой, лучше использовать роли.\\ Здесь идёт ссылка на файл, куда вынесены соответствующие куски плейбука. 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 ===== Группы хостов берутся из **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 Так как сбор фактов - операция ресурсоёмкая, применяются следующие техники для ускорения работы: - Отключение - применяется в облаках, когда VM автоматически создана, но ОС внутри ещё не запустилась. Чтобы Ansible не выдал ошибку, даётся указание ждать подключения, а сбор данных выключить. - 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 ==== Шаблоны Jinja2 ==== Это движок для подстановки переменных или для динамического формирования конфигурационных файлов. Ссылка на переменную идёт в любом файле 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.cfg ===== Базовый способ настройки 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_CONFIG - ''./ansible.cfg'' (файл в текущем каталоге) - ''~/.ansible.cfg'' (файл в домашнем каталоге) - ''/etc/ansible/ansible.cfg'' Используется только первый найденный файл, остальные игнорируются. https://docs.ansible.com/ansible/devel/reference_appendices/config.html ===== Vault ===== Шифрованное хранилище для секретных данных - паролей, токенов и т. п. Командой ''ansible-vault'' их можно зашифровать, а при запуске Ansible передать спец. пароль, который расшифрует данные в процессе работы: ansible-vault encrypt_string "VerySecretPassword!@#$%" --name secret_pass Вывод команды указывается в ''group_vars/.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''! ===== Структура проекта Ansible, коллекции ===== Пример структуры. . ├── README.md ├── ansible.cfg ├── roles │ ├── backend │ └── frontend ├── group_vars │ ├── all.yaml │ ├── backend.yaml │ └── frontend.yaml ├── inventory │ ├── static.yaml │ └── cloud.yaml └── playbook.yaml [[https://docs.ansible.com/ansible/latest/user_guide/collections_using.html|Коллекция]] - совокупность необходимых ролей, плейбуков, плагинов для inventory, собственных модулей. Коллекции можно хранить в git и устанавливать их оттуда или публиковать на специальном сервисе [[https://galaxy.ansible.com/|Ansible Galaxy]]. Примерный вид рабочего процесса для автоматизации управления конфигурацией: - Описание зависимостей (список используемых коллекций) в особом файле, например, ''requirements.yaml'' - Установка зависимостей утилитой ''ansible-galaxy'' и формирование inventory - Запуск команды ''ansible-playbook'' с указанием плейбука Этот рабочий процесс может быть запущен на CI системе или в специальной системе, построенной поверх Ansible — AWX. У AWX есть GUI, упрощающий некоторые задачи по работе с Ansible, например, сделать кнопку для техподдержки для выполнения рутинных операций, которые требуются как реакция на инцидент или запрос от пользователя. ===== Ansible Galaxy ===== [[https://galaxy.ansible.com/|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'' - molecule test (integration) - ''ansible-playbook --check'' (against prod) - parallel infrastructure ==== yamllint ==== Проверка 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) ==== ansible-lint ==== Проверка на соответствие 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. ====== Литература ====== [[https://www.redhat.com/sysadmin/ansible-automate-updates-home|How I used Ansible to automate updates at home]] (RedHat)\\ [[https://www.youtube.com/playlist?list=PL2_OBreMn7FqZkvMYt6ATmgC0KAGGJNAN|Ansible 101 with Jeff Geerling]]\\ [[https://www.youtube.com/playlist?list=PLg5SS_4L6LYufspdPupdynbMQTBnZd31N|ADV-IT - Уроки Ansible на Русском]]