2022-06-15
6774

Синтаксис playbook'ов Ansible

Описание

Playbook — это сценарий, описывающий действия, которые нужно выполнить на управляемых хостах.

    Особенности:
  • Пишется на YAML;
  • Действия выполняются последовательно в том порядке, в котором написаны;
  • Идемпотентность - проверяется, достигнуто ли желаемое конечное состояние, и если это состояние достигнуто работа завершается без выполнения каких-либо действий, так что повторение задачи не изменяет конечное состояние;
  • Начинается с трех тире (---), обозначающих начало документа;
  • Комментарии обозначаются решеткой #;
  • Отступ - 2 пробела.
Запуск сценария осуществляется командой
ansible-playbook main.yaml

Структура файла playbook

---                     #Начинается всегда с трех тире
- name: test-playbook   #Имя playbook (любое)
  hosts: servers        #Группа хостов, на которых будет происходить выполнение (своя группа, all, ungroupt, localhost)
  become: true          #Запускать все задачи с правами суперпользователя (true или false, по умолчанию - false)
  gather_facts: true    #Осуществлять или нет сбор фактов (данных об управляемых хостах) (true или false, по умолчанию - true)
  vars:                 #Список переменных для этого сценария
    owner: root
    custom_var: 42
  vars_files:           #Файлы с переменными
    - vars.yaml

  tasks:                          #Список задач
    - name: block examples tasks  #Объединенный блок нескольких задач, для которого можно задать условие, теги и.т.д.
      block: 
        - name: Установка apache в centos
          yum:
            name: httpd
            update_cache: true
            state: present

        - name: Запуск apache в centos
          service:
            name: httpd
            state: started
            enabled: true
      tags: tag1, tag2                      #Теги
      when: ansible_os_family == "RedHat"   #Условие выполнения

    - name: Создание файла              #Имя задачи
      file:                             #Модуль
        dest: /var/www/html/index.html  #Аргументы
        state: touch
        owner: "{{ owner }}"            #Использование переменной, всегда выделяется кавычками
        group: "{{ owner }}"
        mode: 0644
      tags: tag1, tag2
      notify: Restart httpd             #Вызывается handlers только если произошли изменения

  handlers:
    - name: Restart httpd
      service:
        name: httpd
        state: restarted
    Основными группами простого сценария являются:
  • hosts — управляемые узлы или группы узлов, к которым нужно применить изменения, можно указать список;
  • tasks — задачи описывающие состояние, в которое необходимо привести управляемый узел, могут быть выделены в роли;
  • roles — объединение содержимого, такого как файлы, шаблоны, задачи и т.д. в одну роль для ее последующего вызова
  • gather_facts — собирать или нет информацию об узлах перед выполнением задач, по умолчанию — да;
  • vars — переменные, которые будут использованы при выполнении сценария;
  • block — объединение нескольких задач в блок для которого можно указать условие и теги
  • vars_files — список файлов с переменными
  • pre_tasks — список задач, выполняемых перед другими задачами
  • post_tasks — список задач, выполняемых после других задач
  • handlers — список задач, выполняемых после вызова из другой задачи

Задачи в блоке handlers называются обработчиками и выполняются в тех случаях, если в результате выполнения вызывающей их задачи произошли изменения. Обработчики выполняются только после завершения задач и только один раз. Обработчики выполняются в порядке следования в разделе handlers. В основном обработчики используются для перезапуска служб после изменения конфигурационных файлов.

    Основными параметрами простого сценария являются:
  • connection — можно указать метод соединения с узлами: pure ssh, paramiko, fireball, chroot, jail, local, accelerate (применимо также для выполнения отдельного модуля);
  • sudo — после установления соединения выполнять задачу с привилегиями другого пользователя, по умолчанию другой пользователь — root;
  • sudo_user — в сочетании с предыдущим параметром можно указать с привилегиями какого именно пользователя будет выполнена задача;
  • vars_prompt — перед выполнением playbook'а Ansible в интерактивном режиме может уточнить указанные в этом разделе параметры;
  • remote_user — имя пользователя для авторизации на удалённом узле;
  • become — выполнить от имени привилегированного пользователя;
  • become_user — имя привилегированного пользователя;
  • when — условие выполнения;
  • tags — теги, могут быть указаны при запуске playbook'а для выполнения конкретных задач;
  • ignore_errors — игнорировать ошибки выполнения задачи и продолжать дальше.

Debug и register

    Модуль debug имеет два возможных аргумента
  • var — выводит значение переменной;
  • msg — выводит текст, а также можно выводить значение переменной, в том числе встроенных в ansible, например owner. Все встроенные переменные можно посмотреть командой ansible group -m setup.
Пример debug
---
- name: test-playbook
  hosts: localhost
  vars:
    msg1: 123
    msg2: 456
    path: /home/123
  tasks:
    - debug:  
        var: path

    - debug:  
        var: ansible_os_family #выводит семейство ос
    
    - debug:  
        msg: "string {{ owner }}"

    #Объединить переменные в новую переменную и вывести
    - set_fact: full_message="{{ msg1 }} {{ msg2 }} {{ owner }}"

    - debug:  
        msg: full_message

Модуль register сохраняет выходные данные команд, он регистрирует в отдельный словарь с информацией о задаче, которую можно использовать дальше, всякая задача, выполняемая при запуске, может сохранить результаты в переменной. Для вывода информации в консоль можно использовать модуль debug.

Пример register
---
- name: test-playbook
  hosts: localhost
  tasks:
    - shell: uptime
      register: results
    
    - debug:  
        var: results 

    - debug:  
        var: results.stdout #выведет только вывод команды uptime

Условия

Для условия используется слово when

Пример when (true или false)
---
- name: test-playbook
  hosts: localhost
  vars:
    var1: true
    var2: false
  tasks:
    - name: Установка apache в centos
      yum:
        name: httpd
        update_cache: true
        state: present
      when: var1
      
    - name: Установка nginx в centos
      yum:
        name: httpd
        update_cache: true
        state: present
      when: var2
Будет выполнена только первая задача.
Пример when (сравнение переменной со значением)
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Установка apache в centos
      yum:
        name: httpd
        update_cache: true
        state: present
      when: ansible_os_family == "RedHat"   #ansible_os_family - встроенная переменная

    - name: Установка apache в ubuntu
      apt:
        name: apache2
        update_cache: true
        state: present
      when: ansible_os_family == "Debian"   #или when: ansible_os_family != "RedHat"
      
    - name: Запуск apache в centos
      service:
        name: httpd
        state: started
        enabled: true
      when: ansible_os_family == "RedHat"

    - name: Запуск apache в ubuntu
      service:
        name: apache2
        state: started
        enabled: true
      when: ansible_os_family == "Debian"   #или (when: ansible_os_family != "RedHat")
Можно сделать блоком предыдущий пример с использованием block
---
- name: test-playbook
  hosts: servers
  tasks:
    - block:
        - name: Установка apache в centos
          yum:
            name: httpd
            update_cache: true
            state: present

        - name: Запуск apache в centos
          service:
            name: httpd
            state: started
            enabled: true
      when: ansible_os_family == "RedHat"

    - block:
        - name: Установка apache в ubuntu
          apt:
            name: apache2
            update_cache: true
            state: present

        - name: Запуск apache в ubuntu
          service:
            name: apache2
            state: started
            enabled: true
      when: ansible_os_family == "Debian"

Шаблоны Jinja2

Шаблоны позволяют задавать переменные в файлах, которые будут скопированы на хосты. При копировании переменная заменяется значением.

Например есть файл template1.j2 со следующим содержимым
Hostname: {{ ansible_hostname }}        #встроенные переменные
OS family: {{ ansible_os_faminy }}
IP: {{ ansible_default_ipv4.address }}
my_var: {{ my_var}}                       #своя переменная
Тогда playbook будет следующим
---
- name: test-playbook
  hosts: servers
  vars:                               #своя переменная
    my_var: example1
  tasks:
    - name: Копирование файла с подстановкой переменных
      template:
        src: /home/test1/template1.j2
        dest: /home/test2/file1.txt   #из шаблона получается файл с именем file1.txt с подставленными переменными
        mode: 0644

Циклы

    Есть несколько вариантов цикла
  • loop - цикл по количеству элементов списка;
  • with_fileglob - цикл использующий файлы, которые соответствуют регулярному выражению;
  • until - цикл с условием выхода.

Цикл имеет зарезервированную переменную item, значение которой меняется каждую итерацию цикла.

Пример loop
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Вывод 4 строк с цифрами
      debug:  
        msg: "string {{ item }}"  #item - это зарезервированное для цикла слово
        loop:
          - "1"
          - "2"
          - "3"
          - "4"
        loop_control:
          pause: 3                #пауза в 3 секунды между итерациями цикла, можно не указывать

    - name: Копирование нескольких файлов
      copy:
        src: "/home/test1/{{ my_files }}"
        dest: /home/test2
        mode: 0644
      loop:
        - "file1.txt"
        - "file5.txt"
        - "file6.txt"
      loop_control:
        loop_var: my_files  #замена стандартной переменной item на свою
Пример with_fileglob
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Копирование файлов по регулярному выражению
      copy:
        src: "/home/test1/{{ item }}"
        dest: /home/test2
        mode: 0644
      with_fileglob: "*.txt"

    - name: Копирование файлов по регулярному выражению
      copy:
        src: "{{ item }}"
        dest: /home/test2
        mode: 0644
      with_fileglob: "/home/test1/*.txt"
Эти две задачи выполнят одно и тоже.
Пример until
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Второй вариант
      shell: echo -n "123-" >> file1.txt && cat file1.txt     #запись строки в файл и вывод в консоль
      register: output                                        #вывод предыдущей команды записывается в переменную output
      delay: 2                                                #задержка 2 секунды между итерациями цикла, необязательно
      retries: 10                                             #максимальное количество итераций цикла, необязательно, по умолчанию 3
      until: output.stdout.find("123-123-123-") == false      #условие выхода из цикла

    - name: Вывод output
      debug:  
        var: output.stdout

Import и include

Для разделения playbook'а используются import и include. Они позволяют в отдельном файле написать задачи на определенную тему, затем импортировать их в основной файл.

При использовании import - ansible берет текст файла перед запуском и проверяет на ошибки.

При использовании include содержимое файла берется только в момент прохода по этой команде, поэтому если используются переменные взятые из фактов нужно использовать include.

Например файл task_create.yaml содержит
---
- name: create file
  file:
    dest: /home/file.txt
    state: touch
    owner: root
    group: root
    mode: 0644
А основной файл
---
  - name: test-playbook
    hosts: servers
    tasks:
      include: task_create.yaml   #вместо include можно использовать import
        myvar: "string"           #можно передавать переменные в подключаемый файл

Роли

Роли позволяют автоматически загружать связанные переменные, файлы, задачи, обработчики и другие артефакты Ansible на основе известной файловой структуры.

Роль Ansible имеет определенную структуру каталогов с восемью основными стандартными каталогами. Эту структуру можно сгенерировать командой
ansible-galaxy init 
или создать каталоги самостоятельно

Части playbook'а переносятся в соответствующие каталоги, затем в главной файле вызывается роль.

    Список каталогов роли и их назначение
  • tasks — задачи;
  • handlers — задачи handlers;
  • templates — шаблоны j2;
  • files — файлы для копирования;
  • vars — переменные, связанные с этой ролью;
  • defaults — переменные по умолчанию, имеют самый низкий приоритет;
  • meta — метаданные для роли, включая зависимости от роли;
  • library — пользовательские модули;
  • module_utils — пользовательские module_utils;
  • lookup_plugins — дополнительные плагины.
Основной файл
---
- name: test-playbook
  hosts: servers
  roles: 
    - role_name_1
    - role_name_2

Внешние переменные

Переменные, переданные в playbook через extra-var имеют наивысший приоритет. Передача значения переменной в playbook осуществляется с помощью ключа -e или --extra-var, например -e "message1=Hello".

Например, чтобы указывать на какой группе хостов запускать playbook, можно написать следующее
---
- name: test-playbook
  hosts: "{{ hosts_run }}"
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: "{{ owner }}"
        group: "{{ owner }}"
        mode: 0644
И при запуске указывать значение переменной
ansible-playbook main.yaml --extra-var "hosts_run=servers owner=root"

Теги

Ansible позволяет применять теги для выполнения определенных сценариев или задач к которым они привязаны. Для указания тегов при запуске playbook'а необходимо использовать ключ tags, за которым следует список нужных тегов.

  • ansible-playbook main.yaml --tags=test запуск сценария с передачей тега
  • ansible-playbook main.yaml --skip-tags=test пропустить задачи с определенным тегом
  • ansible-playbook main.yaml --list-tags проверить доступные теги
    Ansible имеет специальные теги:
  • always - задача, помеченная этим тегом, всегда будет выполняться, даже если она не соответствует списку тегов, переданных в опции --tags. Единственное исключение ― когда он явно пропускается с помощью опции --skip-tags=always.
  • never - задача, помеченная этим тегом, не выполняется, за исключением случая, когда выполняется набор сценариев с опцией --tags, в которой указано значение never или один из тегов, связанных с задачей.
  • tagged - запускает любую задачу с явным тегом
  • untagged - запускает любую задачу не имеющую явного тега
  • all - включает все задачи в сценарии независимо от того, есть у них теги или нет. Это поведение Ansible по умолчанию.

Выполнение задачи только на одном сервере

Нужно написать у задачи delegate_to: server1 тогда задача выполнится только на указанном сервере.

Пример
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644
      delegate_to: server1
С помощью этого можно сделать так, чтобы сервера направили некоторые свои данные на другой сервер, например
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: text
      shell: echo "Server {{ inventory_hostname }} - node name {{ ansible_nodename }}" >> /home/log.txt
      delegate_to: localhost
При выполнении на мастере создаться файл в который будут записаны значения переменных с других серверов.
Если нужно запустить задачу только на одном сервере, но не важно на каком, нужно поставить флаг run_once: true, например
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644
      run_once: true

Перехват и контроль ошибок

При запуске playbook, если на одном из серверов какая-либо задаче не выполнилась, то все последующие задачи на этом сервере не будут выполнены, чтобы этого не произошло нужно добавить флаг ignore_errors: yes, тогда если в этой задаче произойдет ошибка последующие все равно будут выполняться.

Пример
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644
      ignore_errors: yes
Можно контролировать ошибки, то есть даже если задача выполнилась, можно, в зависимости от результата вывести ошибку
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644
      register: results
      failed_when: "'text' is results.stdout"
В этом случае будет ошибка выполнения задачи, если в выводе будет слово text
Можно по коду возврата
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644
      register: results
      failed_when: results.rc == 0
Чтобы при ошибке выполнения на одном из серверов прекратилось выполнение на всех, нужно добавить any_errors_fatal: true перед блоком задач
---
- name: test-playbook
  hosts: servers
  any_errors_fatal: true
  tasks:
    - name: Создание файла
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644

Шифрование файлов

ansible-vault позволяет создавать и редактировать зашифрованный файл, который playbook будет автоматический распознавать и расшифровывать с помощью пароля.

  • ansible-vault create secrets.yaml создание файла для сохранения секретных данных, будет запрошен пароль
  • ansible-vault view secrets.yaml посмотреть содержимое зашифрованного файла, будет запрошен пароль
  • ansible-vault edit secrets.yaml редактировать содержимое зашифрованного файла, будет запрошен пароль
  • ansible-vault encrypt secrets.yaml шифрование уже созданного файла с секретными данными
  • ansible-vault decrypt secrets.yaml расшифровка файла с секретными данными
  • ansible-vault rekey secrets.yaml сменить пароль файла с секретными данными
  • ansible-playbook main.yaml --ask-vault-pass при запуске playbook'а выводить запрос пароля для расшифровки файла
  • ansible-playbook main.yaml --vault-password-file pass_file.txt при запуске playbook'а взятие пароля из файла

К файлу, зашифрованному таким образом можно обращаться в секции vars_files. Если в playbook'е есть обращение к зашифрованному файлу, то при запуске playbook'а нужно указывать ключ --ask-vault-pass для запроса пароля, при этом пароль может сохраниться в логах ansible, чтобы это не произошло, в playbook'е перед блоком задач нужно указать no_log: true

Кроме того можно не шифровать файл, а получить зашифрованную строку командой
ansible-vault encrypt_string
После выполнения этой команды нужно будет ввести пароль и саму зашифровываемую строку, в результате на экране отобразиться зашифрованная строка, которую можно вставить в playbook. При запуске playbook'а нужно будет добавлять ключ --ask-vault-pass для запроса пароля расшифровки.
Пример
---
- name: test-playbook
  hosts: servers
  tasks:
    - name: create file
      file:
        dest: /home/file.txt
        state: touch
        owner: root
        group: root
        mode: 0644

    - name: install git 
      yum:
        name: git
        update_cache: true
        state: present

    - name: install vim and nano 
      yum:
        name: 
          - vim
          - nano
        update_cache: true
        state: present