Инструменты пользователя

Инструменты сайта


service:ansible

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
service:ansible [20.03.2025 13:51] – [Выборка из словаря по значениям из списка] viacheslavservice:ansible [15.04.2025 12:21] (текущий) – [Выборка из словаря по значениям из списка] viacheslav
Строка 1: Строка 1:
 +====== 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.
 +
 +<code bash>
 +# копирование файла, используя модуль copy
 +ansible localhost -m copy -a "src=file.txt dest=/tmp/file.txt"
 +</code>
 +Важное отличие от простого копирования через консоль или скрипт - //идемпотентность//, т. е., свойство функции или операции при повторном применении к объекту давать тот же результат, что и при первом. В данном случае, при втором запуске файл копироваться не будет, потому что ansible вычисляет контрольные суммы файлов и понимает, что ничего не изменилось. Можно, конечно, сделать проверки и в скрипте, но это трудоёмкий процесс, требующий изобретения разных подходов для разных задач.
 +
 +//Роли// в Ansible - это список //задач//.\\ 
 +//Плейбук// - это список //плеев//, где перечисляются роли и к каким серверам они применяются.
 +
 +===== ad-hoc команды =====
 +<code bash>
 +# k3 - имя целевого хоста, -m - модуль, -a - аргументы
 +# Создать каталог (рекурсивно, аналог mkdir -p)
 +ansible k3 -m file -a "path=/home/vasya/.config/systemd/user state=directory"
 +# проверка доступа к хосту
 +ansible k3 -m ping
 +</code>
 +
 +===== Задача =====
 +Базовый элемент.
 +<code yaml>
 +# 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
 +</code>
 +''name'' отображается в отчёте. После него идёт название модуля. Справка по модулю - ''ansible-doc <имя модуля>''.
 +
 +==== Управление ошибками / кодами возврата, условия ====
 +
 +https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html
 +
 +==== fail / assert ====
 +Проверка, всё ли выполнилось так, как нужно.\\
 +''fail'' - инициировать сбой\\
 +''assert'' - убедиться, что условие выполняется (в некотором смысле, fail наоборот)
 +
 +<code yaml>
 +- 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"
 +</code>
 +==== Debug ====
 +Вывод информации.
 +<code yaml>
 +tasks:
 +# Вывод переменной foo, свойства rc
 +  - debug: var=foo.rc
 +# Лучше использовать такой синтаксис, т. к. если в свойстве будет дефис, то может быть ошибка.
 +  - debug: var=foo['rc']
 +
 +# Вывод сообщения
 +  - debug: msg="Hello {{ foo['rc'] }}"
 +</code>
 +
 +===== Handlers =====
 +Это дополнительные задачи, обработчики, вызываемые по запросу. Вызываются из задачи с помощью ''notify: <имя хэндлера>'', если только эта задача реально выполняется, т. е. что-то изменяется.\\
 +Хэндлеры выполняются в конце плейбука после всех задач, если нет спец. настроек.
 +
 +<file yaml playbook_apache.yml>
 +- 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
 +</file>
 +
 +Если нужно выполнить хэндлер сразу после какой-то задачи, не дожидаясь выполнения всех остальных задач, то сразу после неё ставится спецзадача
 +<code yaml>
 +- name: Kick handlers immediately
 +  meta: flush_handlers
 +</code>
 +
 +Т. к. по умолчанию обработчики выполняются в конце, есть шанс, что какая-то из задач, вызывающая хэндлер, выполнится, а затем какая-то из последующих задач завершится сбоем и обработчики уже не сработают, т. к. сбоем завершится весь плейбук. Чтобы форсировать выполнение хэндлеров в таком случае, нужно запускать команду с ключом ''--force-handlers''.
 +<code yaml>
 +ansible-playbook -i hosts playbook.yml --force-handlers
 +</code>
 +
 +Вызывать можно несколько обработчиков из одной задачи:
 +<code yaml>
 +    notify:
 +      - restart apache
 +      - restart memcached
 +</code>
 +А можно вызывать обработчик из другого обработчика, т. е., вызвать restart memcached из restart apache.
 +===== Роль =====
 +Набор задач, вызываемый как единый модуль или функция одной строкой с параметрами. Это каталог с упорядоченными по назначению YAML-файлами.
 +
 +Файловая структура роли:
 +<code bash>
 +defaults/main.yml # значения переменных по умолчанию для роли.
 +files/ # каталог с файлами, не требующими шаблонизации.
 +handlers/main.yml # обработчики, запускаются только при получении соответствующих уведомлений (notify) от задач.
 +meta/main.yml # метаданные с описанием авторов роли, зависимостей и версии.
 +tasks/main.yml # задачи, запускаемые ролью.
 +templates/ # каталог с jinja2-шаблонами.
 +tests/
 +vars/main.yml # переменные для роли.
 +</code>
 +Генерация файловой структуры роли: ''ansible-galaxy init <role name>''.
 +
 +==== Include / Import ====
 +Другая организация плейбука, где можно указать, откуда брать задачи/хэндлеры и т. д. Можно не создавать роль, а обойтись этим, если плейбук не очень большой. Если он большой, лучше использовать роли.\\
 +Здесь идёт ссылка на файл, куда вынесены соответствующие куски плейбука.
 +<code yaml>
 +handlers:
 +  - import_tasks: handlers/apache.yml
 +  - import_tasks: handlers/app.yml
 +tasks:
 +  - import_tasks: tasks/apache.yml
 +  - import_tasks: tasks/app.yml
 +</code>
 +
 +Import отличается от include тем, что import сразу формирует файл плейбука при парсинге во время запуска с подстановкой переменных, а include выполняет эти файлы, когда доходит до них. Т. е., если в задаче используются какие-то переменные, значение которых формируется по ходу выполнения плейбука, то нужно использовать Include. Если это просто для выноса кусков кода для расхламления плейбука, то тогда Import.
 +
 +:!: После слов ''import'' и ''include'' можно писать что угодно: ''import_aaa'' или ''include_greatThings''
 +
 +Импортировать/включать можно и целый плейбук. Для этого include нужно разместить на верхнем уровне.
 +<code yaml>
 +name: playbook1
 +hosts: all
 +become:true
 +
 +tasks:
 +  - import: tasks/apache.yml
 +
 +- include: playbook2.yml
 +</code>
 +
 +===== Плейбук =====
 +Связь задач/ролей с целевыми серверами.
 +<file yaml playbook.yaml>
 +---
 +- name: Запуск backend сервиса project
 +  # Шаблон целевых хостов это группа хостов с именем backend
 +  hosts: backend
 +  # Список ansible-ролей для backend-серверов
 +  roles:
 +    - project-backend
 +
 +- name: Запуск frontend сервиса project
 +  # Шаблон целевых хостов это группа хостов с именем frontend
 +  hosts: frontend
 +  # Список ansible-ролей для frontend-серверов
 +  roles:
 +    - project-frontend
 +</file>
 +Запуск плейбука: ''ansible-playbook playbook.yaml''. Будет выполняться один плей за другим, запуская задачи ролей на указанных серверах.
 +
 +==== Тэги/метки ====
 +Задачи можно помечать определённым словом, и тогда можно будет запускать плейбук только с выбранными задачами.
 +<code yaml>
 +  tasks:
 +  - name: ...
 +    shell: ...
 +    tags:
 +      - api
 +      - echo
 +</code>
 +Запустит только задачи с тэгом api.
 +<code bash>
 +ansible-playbook playbook.yml --tags=api
 +</code>
 +===== Inventory =====
 +Группы хостов берутся из **inventory**. Это в простом случае может быть текстовый файл со списком IP-адресов или имён серверов, а может быть и в формате yaml.\\
 +<code ini>
 +# Названия чувствительны к регистру - [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
 +</code>
 +
 +<code bash>
 +# Показать список хостов с принадлежащими им переменными
 +ansible-inventory --list
 +# Типа tree для файлов
 +ansible-inventory --graph
 +
 +# проверка связи (inventory - hosts.txt, все хосты, модуль ping)
 +ansible -i hosts.txt all -m ping
 +</code>
 +
 +Проблема в том, что если на сервера раньше не заходили, запросит кучу подтверждений ssh fingerprint.
 +Чтобы этого избежать, рисуем ansible.cfg, где заодно указываем inventory-файл по умолчанию.
 +<file ini ansible.cfg>
 +[defaults]
 +host_key_checking = false
 +inventory = ./hosts.txt
 +</file>
 +<code bash>
 +# проверка связи c учётом ansible.cfg
 +ansible all -m ping
 +</code>
 +
 +Формат yaml:
 +<code 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:  
 +</code>
 +После этого можно запускать плейбук с ограничением по группе, например, ''%%ansible-playbook playbook.yaml --limit dev%%''
 +
 +В файле inventory можно прописывать переменные, но это не рекомендуется:
 +<code yaml>
 +all:
 +  children:
 +    backend:
 +      hosts:
 +        dev-backend.example.com:
 +          # Переменная только для этого хоста
 +          ansible_user: ansible
 +      vars: # Переменные для группы backend
 +        backend_version: 3.1.1
 +  vars: # Переменные для всех хостов в inventory
 +    ansible_connection: ssh
 +</code>
 +Лучше создавать файлы с переменными в каталоге ''group_vars'' рядом с ''playbook.yaml'', например, файл ''group_vars/all.yaml'' будет выглядеть так:
 +<code yaml>
 +ansible_connection: ssh
 +</code>
 +
 +Для часто меняющейся инфраструктуры используется динамический inventory. В Ansible есть плагин динамической инвентаризации, который подключается к облачной платформе или API системы виртуализации и получает список серверов.
 +
 +===== Переменные =====
 +Используются для управления поведением задач подобно параметрам функции в программировании.
 +
 +Переменные могут быть определены в плейбуках, в inventory, внутри ролей или передаваться в командной строке запуска плейбука (т. н. extra vars).\\
 +:!: В именах переменных могут использоваться только буквы, цифры (не первый символ) и подчёркивания.
 +<code bash>
 +# Extra vars имеют наивысший приоритет
 +ansible-playbook playbook.yaml --extra-vars "my_var=value"
 +</code>
 +Переменные, указанные в файлах ''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:'' попадает несколько строк, то изменится последняя.
 +<code yaml>
 +- 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
 +</code>
 +:!: Общесистемные переменные нужно загонять в файл ''/etc/environment''. Понадобится ''become: true''.
 +
 +==== Местоположение переменных ====
 +Положим, есть плейбук со скачиванием файла и настройкой прокси-сервера.
 +<code yaml>
 +- 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
 +</code>
 +Можно вынести переменные прокси на верхний уровень, тогда в задаче можно на них сослаться.
 +<code yaml>
 +  vars:
 +    proxy_vars:
 +      http_proxy: http://proxy.example.com
 +      https_proxy: https://proxy.example.com
 +    
 +  tasks:
 +  - name: ...
 +    environment: proxy_vars
 +</code>
 +
 +А можно сделать так, что переменные будут действовать на все задачи:
 +<code yaml>
 +  environment:
 +    http_proxy: http://proxy.example.com
 +    https_proxy: https://proxy.example.com
 +    
 +  tasks:
 +  - name: ...
 +  - name: ...
 +</code>
 +
 +Переменные из файла:
 +<code yaml>
 +  vars:
 +    key: value
 +  vars_files:
 +    - vars/main.yml
 +</code>
 +
 +Задать переменную ''ansible_super_secret'' из значения переменной окружения ''SUPER_SECRET'':
 +<code yaml>
 +ansible_super_secret: "{{ lookup('env', 'SUPER_SECRET') }}"
 +</code>
 +==== Факты ====
 +Это переменные, которые формируются автоматически на основе сбора данных из системы. Это нужно, потому что в разных системах могут быть разные пакетные менеджеры, разная иерархия каталогов файловой системы и т. д.
 +
 +При запуске плея, при первом подключении к хостам и до запуска указанных задач, Ansible запускает модуль setup, который выполняет анализ окружения целевого хоста и формирует набор переменных в словаре ansible_facts.
 +<code bash>
 +# принудительный запуск на локальной машине
 +ansible localhost -m setup
 +</code>
 +
 +Так как сбор фактов - операция ресурсоёмкая, применяются следующие техники для ускорения работы:
 +  - Отключение - применяется в облаках, когда VM автоматически создана, но ОС внутри ещё не запустилась. Чтобы Ansible не выдал ошибку, даётся указание ждать подключения, а сбор данных выключить. <code yaml>- hosts: development
 +  gather_facts: no
 +  tasks:
 +    - name: waiting for connection
 +      wait_for_connect</code>
 +  - Фильтрация - собирать только необходимую информацию. <code yaml>- 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'</code>
 +  - Кэширование - настраивается в файле ''ansible.cfg''. <code>[inventory]
 +cache = True
 +cache_plugin = jsonfile
 +cache_connection = ~/.cache/ansible
 +cache_timeout = 3600</code>
 +
 +==== Шаблоны Jinja2 ====
 +Это движок для подстановки переменных или для динамического формирования конфигурационных файлов. Ссылка на переменную идёт в любом файле yaml в двойных фигурных скобках.
 +<code yaml>
 +- name: Install package {{ package_name }}
 +  package:
 +    name: "{{ package_name }}"
 +</code>
 +Чтобы создавать файлы на основе шаблонов, используется модуль template.
 +<code yaml>
 +- name: Create app backend service unit 
 +  template:
 +    src: app-backend.service.j2
 +    dest: /etc/systemd/system/app-backend.service
 +</code>
 +
 +Если переменные бэкенда описаны в ''group_vars/backend.yml'':
 +<code yaml>
 +backend_maven_version: 3.4.1
 +backend_service_user: app_user
 +backend_report_directory: /opt/app/reports
 +backend_lib_directory: /opt/app/lib/ 
 +</code>
 +
 +То можно генерировать systemd-unit на основе шаблона ''app-backend.service.j2'':
 +<code>
 +[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 
 +</code>
 +
 +=== Переменная в кавычках ===
 +Чтобы после обработки шаблона переменная была окружена кавычками, надо делать так:
 +<code yaml>
 +datasource:
 +  url: "{{ datasource_url | quote }}"
 +</code>
 +Т. е., ставить кавычки сами по себе и дополнительно применять команду. Одни кавычки отбросятся, вторые останутся.
 +
 +=== Копирование нескольких шаблонов одной задачей ===
 +<code yaml>
 +- name: Copy templates
 +  template:
 +    src: "{{ item }}"
 +    dest: /tmp/{{ item | basename | regex_replace('\\.j2$', '') }}
 +  with_fileglob:
 +    - ../templates/*.j2
 +</code>
 +:!: Note ''with_fileglob'' always operates from ''files/'', you can get to templates with ''../templates/mytemplate/*''
 +
 +https://serverfault.com/questions/578544/deploying-a-folder-of-template-files-using-ansible
 +
 +==== Динамические переменные ====
 +Используются для нескольких ОС, где разные пути к конфигам и один и тот же пакет называется по-разному, например apache2 в Debian и httpd в CentOS. Создаётся несколько файлов с переменными:
 +<WRAP group>
 +<WRAP half column>
 +<file yaml apache_RedHat.yml>
 +apache_package: httpd
 +apache_service: httpd
 +apache_config_dir: /etc/httpd/conf.d
 +</file>
 +</WRAP>
 +
 +<WRAP half column>
 +<file yaml apache_default.yml>
 +apache_package: apache2
 +apache_service: apache2
 +apache_config_dir: /etc/apache2/sites-enabled
 +</file>
 +</WRAP>
 +</WRAP>
 +
 +В плейбуке рисуется раздел с предварительным заданием (на уровне tasks), который читает первый совпадающий файл с переменными:
 +<code yaml>
 +  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"
 +</code>
 +Вместо ''ansible_os_family'' можно использовать ''ansible_distribution''.
 +===== ansible.cfg =====
 +Базовый способ настройки Ansible. В ansible.cfg определены директивы конфигурации для всего набора утилит (ansible, ansible-playbook, ansible-galaxy, ansible-vault и т. д.). В этом файле указывается расположение ролей, конфигурация inventory и имя пользователя для подключения к хостам. Пример:
 +<code>
 +[defaults]
 +roles_path = roles
 +inventory = inventory
 +
 +remote_user = ansible
 +
 +vault_password_file = .vault
 +host_key_checking = False
 +[privilege_escalation]
 +become = true ; повышать привилегии (sudo)
 +</code>
 +
 +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 передать спец. пароль, который расшифрует данные в процессе работы:
 +<code bash>
 +ansible-vault encrypt_string "VerySecretPassword!@#$%" --name secret_pass
 +</code>
 +Вывод команды указывается в ''group_vars/<my_group>.yml'':
 +<code yaml>
 +secret_pass: !vault |
 +  $ANSIBLE_VAULT;1.1;AES256
 +  39836982658142836487543449583409549856387698734580384098152098347874095687623654
 +  ...
 +  8457
 +</code>
 +Передача пароля для расшифровки происходит в интерактивной сессии при запуске ansible-playbook с параметром ''%%--ask-vault-pass %%'':
 +<code bash>
 +ansible-playbook playbook.yaml --ask-vault-pass
 +</code>
 +<WRAP round important 80%>
 +При запуске Ansible в CI-системе этот способ не подойдёт и потребуется механизм передачи пароля без участия пользователя.\\
 +Для этого нужно сформировать файл с паролем на предыдущем шаге CI и указать его расположение в ''ansible.cfg'':
 +<code>
 +vault_password_file = .vault
 +</code>
 +Файл ''.vault'' необходимо добавить в ''.gitignore''!
 +</WRAP>
 +
 +===== Структура проекта Ansible, коллекции =====
 +Пример структуры.
 +<code>
 +.
 +├── README.md
 +├── ansible.cfg
 +├── roles
 +│   ├── backend
 +│   └── frontend
 +├── group_vars
 +│   ├── all.yaml
 +│   ├── backend.yaml
 +│   └── frontend.yaml
 +├── inventory
 +│   ├── static.yaml
 +│   └── cloud.yaml
 +└── playbook.yaml
 +</code>
 +
 +[[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'' прописать
 +<code yaml>
 +roles_path = ./roles
 +</code>
 +Рядом положить файл ''requirements.yml'' с перечислением ролей-зависимостей и, по желанию, их версий.
 +<code yaml>
 +---
 +roles:
 +  - name: elliotweiser.osx-command-line-tools
 +    version: 2.3.0
 +  - name: geerlingguy.mac
 +    version: 4.0.0
 +</code>
 +После этого запустить установку ролей из Galaxy, указав файл зависимостей. Скачанные роли окажутся в подкаталоге roles, как указано в файле конфигурации.
 +<code bash>
 +ansible-galaxy install -r requirements.yml
 +</code>
 +Плейбук:
 +<code yaml>
 +---
 +- hosts: localhost
 +  connection: local
 +  
 +  vars:
 +    homebrew_installed_packages:
 +      - pv
 +  roles:
 +    - elliotweiser.osx-command-line-tools
 +    - role: geerlingguy.mac
 +      become: true
 +</code>
 +
 +===== Проверка/тестирование =====
 +По степени увеличения сложности:
 +  - ''yamllint''
 +  - ''ansible-playbook --syntax-check'' - встроенная проверка синтаксиса. Ловит не всё, например, ''include:'' не может, т. к. для этого нужно запустить плейбук.
 +  - ''ansible-lint''
 +  - molecule test (integration)
 +  - ''ansible-playbook --check'' (against prod)
 +  - parallel infrastructure
 +
 +==== yamllint ====
 +Проверка YAML на корректность. Даже если плейбук запускается и работает, то шероховатости в его написании могут впоследствии привести к проблемам.
 +
 +<code bash>
 +# Установка
 +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)
 +
 +</code>
 +
 +==== ansible-lint ====
 +Проверка на соответствие best practices.
 +
 +<code bash>
 +# Установка
 +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.
 +
 +</code>
 +
 +====== Конкретные задачи ======
 +===== Копирование с одного удалённого сервера на другой =====
 +Существует модуль [[https://docs.ansible.com/ansible/latest/collections/ansible/posix/synchronize_module.html|synchronize]], где можно настроить делегирование и тем самым запустить копирование непосредственно с одного хоста на другой.
 +<code yaml>
 +- 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
 +</code>
 +Но есть проблема аутентификации: каким образом обеспечить доступ с делегируемого хоста на конечные? Есть вариант [[https://stackoverflow.com/questions/24124140/ssh-agent-forwarding-with-ansible|SSH Agent Forwarding]], но это вроде как небезопасно. Поэтому в общем случае копируем сначала на менеджера (достаточно одного раза - ''run_once: true''), а потом с него на конечные сервера.
 +<code yaml>
 +---
 +- 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
 +</code>
 +
 +
 +===== Останов, копирование .war-файла с эталонного сервера и конфигов, запуск, удаление паролей =====
 +
 +<code yaml>
 +- 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'
 +
 +</code>
 +
 +===== Добавить секцию в шаблон конфига по условию =====
 +Например, если сервер входит в ту или иную группу. В самом шаблоне:
 +<code bash>
 +{% if inventory_hostname in groups['t01'] or inventory_hostname in groups['t03'] %}
 +Секция, которую надо вставить для хостов в группах t01 и t03
 +{% else %}
 +Секция, которую надо вставить для прочих хостов
 +{% endif %}
 +</code>
 +Если используются переменные из //фактов// (в примере ''inventory_hostname'' из [[https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html|спец. переменных]]), то в плейбуке надо не запрещать сбор этих фактов, т. е., ''gather_facts: false'' не применять.
 +
 +Дополнительно про инвентори: [[https://habr.com/ru/articles/509938/|Основы Ansible, без которых ваши плейбуки — комок слипшихся макарон, часть 2]]\\
 +Template Designer Documentation: https://jinja.palletsprojects.com/en/stable/templates/
 +
 +===== Выборка из словаря по значениям из списка =====
 +В зависимости от набора работающих служб получать набор строк для формирования API-запроса. Если есть работающие службы, которые отсутствуют в словаре, они будут пропущены по условию.
 +
 +<file yaml services_status.yml>
 +- hosts: k3
 +  roles:
 +    - services_status
 +  vars:
 +    services:
 +      project-service1: main
 +      project-service2: thingy
 +      project-service3: bonanza
 +</file>
 +
 +<file yaml roles/services_status/tasks/main.yml>
 +- name: Gather running services
 +  shell:
 +    cmd: systemctl --user --type service --state running --plain --quiet |grep ^project- |sed -E 's#\.service.*##'
 +  register: running_services
 +
 +# В curl body - это --data '{"dataSources": ["main","thingy","bonanza"]}'
 +- name: Update API
 +  uri:
 +    url: https://localhost/admin/api/update_metadata
 +    user: admin
 +    password: {{ pass }}
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    body_format: json
 +    body:
 +      dataSources:
 +        - "{{ services[item] }}"
 +  with_items:
 +    - "{{ running_services['stdout_lines'] }}"
 +  when: item in services | list
 +</file>
 +
 +Работают 1-я и 3-я службы, 2-я не работает. Значения получаются main и bonanza соответственно.
 +<code bash>
 +ansible-playbook services_status.yml 
 +
 +TASK [services_status : debug] **********************************************************************************************************
 +ok: [k3] => (item=project-service1) => {
 +    "msg": "Values are: main"
 +}
 +ok: [k3] => (item=project-service3) => {
 +    "msg": "Values are: bonanza"
 +}
 +</code>
 +
 +Выбор списка значений из словаря
 +<code yaml>
 +- name: "Update metadata"
 +  uri:
 +    url: https://localhost/service/json/admin/api/refresh_metadata
 +    user: admin
 +    password: admin
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    body_format: json
 +    timeout: 600
 +    body:
 +      dataSources:
 +        {{ services | dict2items | selectattr('key', 'in', running_services['stdout_lines']) | map(attribute='value') | list }}
 +</code>
 +
 +===== Учёт кода возврата в в ответе Nexus в формате json =====
 +
 +<file yaml staging.yml>
 +- hosts: 127.0.0.1
 +  connection: local
 +  roles:
 +    - staging
 +  vars:
 +    name: ""
 +    version: ""
 +    repo_test: "project_maven_test"
 +    repo_uat: "project_maven_uat"
 +    repo_prod: "project_maven_prod"
 +    nexus_user_1: "user1"
 +    nexus_user_2: "user2"
 +    nexus_password_1: ""
 +    nexus_password_2: ""
 +    timeout: 300
 +</file>
 +
 +<file yaml roles/staging/tasks/main.yml>
 +- name: "Create tag"
 +  uri:
 +    url: https://nexus-cd.example.com/service/rest/v1/script/getVersion/run
 +    user: "{{ nexus_user_1 }}"
 +    password: "{{ nexus_password_1 }}"
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    timeout: "{{ timeout }}"
 +    return_content: true
 +    body_format: json
 +    body:
 +      version: "{{ version }}"
 +  register: response_ctag
 +
 +- debug:
 +    var: response_ctag.json.result
 +
 +- pause:
 +    seconds: 10
 +
 +- name: "Set tag"
 +  uri:
 +    url: https://nexus-cd.example.com/service/rest/v1/script/setVersion/run
 +    user: "{{ nexus_user_1 }}"
 +    password: "{{ nexus_password_1 }}"
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    timeout: "{{ timeout }}"
 +    return_content: true
 +    body_format: json
 +    body:
 +      tagName: "ver-{{ version }}"
 +      repository: "{{ repo_test }}"
 +      name: "{{ name }}"
 +      version: "{{ version }}"
 +  register: response_stag
 +
 +- debug:
 +    var: response_stag.json.result
 +
 +- pause:
 +    seconds: 10
 +  when: (response_stag.json.result |from_json).status == 200
 +
 +- name: "Staging to UAT"
 +  uri:
 +    url: https://nexus-cd.example.com/service/rest/v1/script/staging/run
 +    user: "{{ nexus_user_2 }}"
 +    password: "{{ nexus_password_2 }}"
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    timeout: "{{ timeout }}"
 +    return_content: true
 +    body_format: json
 +    body:
 +      repoName: "{{ repo_uat }}"
 +      version: "{{ version }}"
 +  register: response_uat
 +  when: (response_stag.json.result |from_json).status == 200
 +
 +- debug:
 +    var: response_uat.json.result
 +  when: response_uat.json.result is defined
 +
 +- pause:
 +    seconds: 10
 +  when: response_uat.json.result |from_json).status == 200
 +
 +- name: "Staging to PROD"
 +  uri:
 +    url: https://nexus-cd.example.com/service/rest/v1/script/staging/run
 +    user: "{{ nexus_user_2 }}"
 +    password: "{{ nexus_password_2 }}"
 +    force_basic_auth: true
 +    method: POST
 +    validate_certs: false
 +    timeout: "{{ timeout }}"
 +    return_content: true
 +    body_format: json
 +    body:
 +      repoName: "{{ repo_prod }}"
 +      version: "{{ version }}"
 +  register: response_prod
 +  when: (response_uat.json.result |from_json).status == 200
 +
 +- debug:
 +    var: response_prod.json.result
 +  when: response_prod.json.result is defined
 +</file>
 +
 +====== Ошибки, проблемы ======
 +
 +===== Collection ansible.posix does not support Ansible version =====
 +<color #ff7f27>[WARNING]: Collection ansible.posix does not support Ansible version 2.17.0</color>
 +
 +<code bash>
 +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
 +</code>
 +
 +===== The processing instruction target matching "[xX][mM][lL]" is not allowed =====
 +Ошибка после запуска jar, где ''log4j2.xml'' настроен как конфигурация логов. Проблема в том, что первой строкой стоял комментарий, а там всегда должен быть XML declaration. Вот так будет работать корректно:
 +<code xml>
 +<?xml version="1.0" encoding="UTF-8" ?>
 +<!-- ######################
 +# Здесь какой-то текст
 +####################### -->
 +</code>
 +====== Литература ======
 +[[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 на Русском]]\\
 +[[https://www.youtube.com/playlist?list=PLT98CRl2KxKEUHie1m24-wkyHpEsa4Y70|Getting started with Ansible (Learn Linux TV)]]\\
 +[[https://ezhvsalate.ru/posts/ansible-peredaem-json-v-tele-zaprosa-ispolzuia-modul-uri|Ansible: передаем json в теле запроса, используя модуль uri]]\\
 +
  

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki