Автоматизируем и ускоряем процесс настройки облачных серверов с Ansible. Часть 5: local_action, условия, циклы и роли

    В первой части мы начали изучение Ansible, популярного инструмента для автоматизации настройки и развертывания ИТ-инфраструктуры. Ansible был успешно установлен в InfoboxCloud, описаны принципы работы, базовая настройка. В завершении статьи мы показали как быстро установить nginx на несколько серверов.

    Во второй части мы разобрались в выводе playbook, научились отлаживать и повторно использовать скрипты Ansible.

    В третьей части мы узнали как написать единый Ansible playbook для разных ОС (например с rpm и deb), как обслуживать сотни хостов и не писать их все в inventory и как сгруппировать сервера по регионам InfoboxCloud. Было изучено использование переменных Ansible и файла inventory.

    В четвертой части мы научились использовать модули Ansible для настройки сервера: разобрались, как запускать самые обычные скрипты на удаленных серверах в InfoboxCloud, использовать шаблонизацию для файлов конфигурации, подставляя необходимые переменные, и как использовать системы управления версиями для получения кода на сервер.



    В этой части мы рассмотрим, как запускать задачу локально в рамках playbook для удаленных серверов, как использовать условия для выполнения конкретных задач только в определенной ситуации, как использовать циклы для значительного сокращения количества задач в playbook. В завершении мы разберем, как организовывать playbook в роли.

    Запускаем задачи локально с помощью local_action


    Иногда задачи надо запускать на локальной машине в рамках исполнения playbook для удаленных серверов. Например, можно на Ansible–сервере прописать ключи доступа по API к облаку и отдавать команды утилите командной строки для создания новых серверов облака. Часто может требоваться отправлять запросы в REST API через модуль uri Ansible. Возможность что-то делать прямо на Ansible–сервере для отдельной задачи в playbook, где в качестве hosts прописаны удаленные сервера, есть.

    Допустим, вы хотите запустить shell–модуль на сервере, откуда вы запускаете Ansible. Для этого пригодится опция local_action, которая запустит модуль локально.
    ---
    - hosts: experiments
      remote_user: root
      tasks:
    
      - name: check running processes on remote system
        shell: ps
        register: remote_processes
    
      - name: remote running processes
        debug: msg="{{ remote_processes.stdout }}"
    
      - name: check running processes on local system
        local_action: shell ps
        register: local_processes
    
      - name: local running processes
        debug: msg="{{ local_processes.stdout }}"
    

    Процессы на удаленных машинах.


    Процессы на локальной машине.


    Mы видим, что исполнение команды перенаправляется для локальной машины.


    Таким образом, вы можете запустить любой модуль Ansible с local_action.

    Работаем с условиями


    Ansible исполняет все задачи последовательно. Тем не менее, для сложного playbook с десятками задач, вам может потребоваться в зависимости от ситуации запускать только часть задач. Ранее мы уже рассматривали ситуацию, когда с помощью переменных мы корректно устанавливали Apache на rpm и deb дистрибутивы. Подобным образом можно указывать условия для выполнения задач с помощью when:
    ---
    - hosts: experiments
      remote_user: root
      tasks:
      - name: Install httpd package
        yum: name=httpd state=latest
        sudo: yes
        when: ansible_os_family == "RedHat"
    
      - name: Install apache2 package
        apt: name=apache2 state=latest
        sudo: yes
        when: ansible_os_family == "Debian"
    

    Если ОС семейства RedHat – будет установлен пакет httpd через yum, а если семейства Debian – apache2 через apt. ansible_os_family – переменная Ansible, получаемая на стадии gather_facts.

    В playbook выше мы использовали sudo: yes, подразумевая, что у пользователя есть права sudo. Давайте проверим, так ли это:
    ---
    - hosts: experiments
      remote_user: root
      tasks:
    
      - name: Testing user sudo privilege
        command: /usr/bin/sudo -v
        register: sudo_response
        ignore_errors: yes
    
      - name: Stop if Users doesn`t have sudo privilege
        fail: msg="User doesn`t have sudo privilege"
        when: sudo_response.rc == 1
    



    В примере выше мы запустили команду на сервере /usr/bin/sudo -v и сохранили ее вывод в переменную через register. В переменной был захвачен вывод stdout и stderr (rc, return code). Во второй задаче мы проверили содержание return code переменной и если ошибка произошла — должны завершить исполнение playbook с выводом сообщения.

    Для сравнения в условиях в Ansible можно использовать == (равно), != (не равно), > (больше), < (меньше), >= (больше равно), <= (меньше равно).

    Если вам нужно проверить, есть ли в переменной символ или строка, используйте операторы in и not.
    - name: Querying rpm list for httpd package
      shell: rpm -qa | grep httpd
      register: httpd_rpm
    
    - name: Check if httpd rpm is installed on the remote host
      debug: msg="httpd is installed on the remote host"
      when: "'httpd-2.2.27–1.2.x86_64' in httpd_rpm.stdout"
    
    – name: Check if httpd rpm is not installed on the remote host
      debug: msg="httpd is not installed on the remote host"
      when: not 'httpd-2.2.27.1.2.x86_64' in httpd_rpm.stdout
    

    Можно задавать несколько условий, используя операторы and (и) и or (или).
    – name: Check if httpd rpm is installed on the remote host
      debug: msg="httpd is installed on the remote host"
      when: "'httpd-2.2.27–1.2.x86_64' in httpd_rpm.stdout and 'httpd-tools-2.2.27–1.2.x86–64' in httpd_rpm.stdout"
    

    Также можно проверить логическое значение переменной. Давайте сделаем бекап, если в переменной backup установлено true:
    – name: Rsync 
      shell: /usr/bin/rsync -ra /home /backup/{{ inventory_hostname}}
      sudo: yes
      when: backup
    

    Ansible позволяет в условии использовать информацию о том, была ли уже определена переменная. Для этого используйте when: var is not define (где var — имя переменной, is not define – еще не была определена, is defined – уже была определена).

    Работаем с циклами


    Бывает, что необходимо установить сразу несколько пакетов на сервер. Но написание многих задач для этого может превратиться в настоящий ночной кошмар. Проблему решит использование циклов.

    Стандартные циклы

    Используя стандартные циклы вы можете передать список пакетов для установки и Ansible запустит задачу для всех указанных пакетов.
    ---
    - hosts: experiments
      remote_user: root
      tasks: 
      – name: Install nginx package
        yum: name={{ item }} state=latest
        with_items:
        – nginx
        – htop
        sudo: yes
    

    В примере выше мы использовали конструкцию «with_items:» для задания переменных и использовали переменную по умолчанию item. На каждой итерации item принимает следующее значение, указанное в with_items.



    Задача запускается один раз, но apt вызывается для всех указанных пакетов. Можно так же использовать with_items как словарь вместо строк:
    with_items:
    – {name: 'httpd', state: 'latest'}
    – {name: 'htop', state: 'absent'}
    

    Вложенные циклы

    Вложенные циклы полезны, когда вы хотите выполнить несколько операций над одним и тем же ресурсом. Например, если вы хотите предоставить доступ ко множеству баз данных для пользователей MySQL.
    –––
    – hosts: experiments
      remote_user: root
      tasks:
      – name: give users access to multiple databases
      mysql_user: name={{ item[0] }} priv={{ item[1]}}.*:ALL append_privs=yes password=pass login_user=root login_password=root
      with_nested:
      – ['alexey', 'alexander']
      – ['clientdb', 'providerdb']
    

    В приведенном примере мы используем модуль mysql_user для установки прав на базы данных и используем вложенные циклы с двумя списками: список пользователей и список баз данных. Ansible запустит модуль mysql_user для пользователя alexey, даст права на все указанные во втором списке базы данных, затем запустит для пользователя alexander и так же даст права.

    Циклы по подэлементам


    В предыдущем примере мы назначили все указанные базы данных всем указанным пользователям. Но что делать, если для каждого пользователя нужно назначить свой специфический набор баз данных? Для этого нам пригодятся циклы по подэлементам.
    ---
    - hosts: experiments
      remote_user: root
      vars:
        users:
        – name: alexey
          database:
          – clientdb
          – providerdb
        – name: alexander
          database:
          – providerdb
      tasks:
      – name: give users access to multiple databases
        mysql_user: name={{ item.0.name }} priv={{ item.1 }}.*:ALL append_privs=yes password=pass login_user=root login_password=root
        with_subelements:
        – users
        - database
    

    Мы создали словари, которые состоят из имен пользователей и имен баз данных. Вместо добавления данных пользователей в playbook можно вынести их в отдельный файл переменных и включить в playbook. Ansible пройдется по словарю используя переменную item. Ansible назначает численные значения ключам, представленным конструкцией with_subelements, начиная с 0. В словаре 0 имя — пара «ключ-значения», поэтому для обращения по имени пользователя мы используем item.0.name. Dictionary — простой список, поэтому для обращения используем item.1.

    Работаем с ролями


    При проектировании архитектуры обычно оперируют ролями серверов: веб-сервер, сервер баз данных, балансировщик нагрузки и так далее. Каждая роль включает в себя определенный набор софта для установки и настройки. С ростом вашей системы постепенно будут выделяться компоненты, которые можно повторно использовать. Роли в Ansible предоставляют удобный способ организации ваших playbook. На основе предопределенной файловой структуры будут загружаться компоненты роли. Фактически роли — просто магия вокруг include (импортов), облегчающая подготовку playbook.

    Типичная структура playbook с ролями:
    ---
    - hosts: webservers
      roles:
         - common
         - web
         – db
    

    Файловая структура ролей будет выглядеть так:
    site.yml
    webservers.yml
    roles/
       common/
         files/
         templates/
         tasks/
         handlers/
         vars/
         defaults/
         meta/
       web/
         files/
         templates/
         tasks/
         handlers/
         vars/
         defaults/
         meta/
       db/
         files/
         templates/
         tasks/
         handlers/
         vars/
         defaults/
         meta/
    

    Если какой-то директории в роли нет — она будет проигнорирована и playbook будет исполняться. Совсем не обязательно у вас должны быть все элементы и директории playbook.

    Правила, используемые для каждой роли:
    • Если roles/x/tasks/main.yml существует, задачи будут добавлены в процесс исполнения playbook.
    • Если roles/x/handlers/main.yml существует, обработчики событий будут добавлены в процесс исполнения playbook.
    • Если roles/x/vars/main.yml существует, переменные будут добавлены в процесс исполнения playbook.
    • Если roles/x/meta/mail.yml существует, любые роли-зависимости будут добавлены в список ролей. (В meta можно указывать список ролей, которые должны быть применены до конкретной роли, чтобы она применилась корректно).
    • Любая задача копирования может ссылаться на файл в roles/x/files без указания абсолютного или относительного пути.
    • Любая скриптовая задача может ссылаться на скрипты в roles/x/files без указания абсолютного или относительного пути.
    • Любая задача шаблонизации может ссылаться на roles/x/templates без указания абсолютного или относительного пути.
    • Любые импортируемые задачи могут ссылаться на файлы задач в директории roles/x/tasks без указания абсолютного или относительного пути.

    В конфигурационном файле Аnsible можно задать roles_path (директорию с ролями). Это может пригодиться, если у вас playbook лежат в одном репозитории, а сами роли в другом. Можно задавать сразу несколько путей к ролям через двоеточие:
    roles_path = /opt/mysite/roles:/opt/othersite/roles
    

    В роли можно передавать переменные или использовать условия:
    ---
    - hosts: experiments
      roles:
       – common
       – {role: web, dir: '/var/www', port: 80}
       – {role: repository, when: "ansible_os_family =='RedHat'"}
    

    Ранее в статьях мы не рассматривали тэги. С их помощью можно запускать помеченную часть playbook.
    С задачами использование тэгов выглядит так:
    tasks:
        - apt: name={{ item }} state=installed
          with_items:
             - httpd
             - htop
          tags:
             - packages
    
        - template: src=templates/src.j2 dest=/var/www/.htaccess
          tags:
             - configuration
    

    Можно запустить часть playbook так: ansible-playbook example.yml --tags «configuration,packages» или пропустить исполнение части так: ansible-playbook example.yml --skip-tags «notification».

    Так вот тэги также можно использовать и при указании ролей:
    ---
    - hosts: experiments
      roles:
        - { role: web, tags: ["apache", "simple"] }
    

    Можно указать, какие задачи должны выполниться до роли и после:
    ---
    - hosts: experiments
      pre_tasks:
        - shell: echo 'hello, habr'
      roles:
        - { role: web }
      tasks:
        - shell: echo 'still busy'
      post_tasks:
        - shell: echo 'goodbye, habr'
    

    Зависимости ролей

    Зависимости ролей позволяют автоматически исполнить зависимые роли при запуске конкретных ролей, у которых зависимости есть. Зависимости хранятся в roles/x/meta/main.yml. Вместе с зависимыми ролями могут быть переданы параметры. Путь к ролям может быть указан как в сокращенном виде, так и в полном. Также может быть использован репозиторий системы управления версиями.
    ---
    dependencies:
      - { role: common, some_parameter: 3 }
      - { role: '/path/to/common/roles/foo', x: 1 }
      - { role: 'git+http://git.example.com/repos/role-foo,v1.1,foo' }
    

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

    Ansible Galaxy


    Ansible Galaxy — репозиторий ролей Ansible. С этого ресурса можно использовать уже готовые роли Ansible или добавлять свои.

    Заключение


    В написании статьи очень помогла книга "Learning Ansible" и конечно официальная документация.

    Все эксперименты с Ansible удобно проводить в InfoboxCloud, так как имеется возможность для каждого виртуального сервера установить именно то количество ресурсов, которое необходимо для задачи (CPU/Ram/диск независимо друг от друга) или использовать автомасштабирование, а не выбирать VM из готовых шаблонов. Когда эксперименты не проводятся — можно просто выключить VM и оплачивать только стоимость диска.

    Если вы обнаружили ошибку в статье, автор ее с удовольствием исправит. Пожалуйста напишите в ЛС или на почту о ней. Туда же можно задавать вопросы по Ansible для освещения в последующих статьях. Если вы не можете писать комментарии на Хабре, можно оставить их в Сообществе InfoboxCloud.

    Успешной работы!
    • +9
    • 23.8k
    • 2
    Infobox
    34.56
    Company
    Share post

    Comments 2

      +1
      local_action уже давно deprecated. Надо использовать конструкцию с delegate_to: 127.0.0.1
        0
        откуда информация про «deprecated»?

        There is also a shorthand syntax that you can use on a per-task basis: ‘local_action’.


        http://docs.ansible.com/ansible/playbooks_delegation.html

      Only users with full accounts can post comments. Log in, please.