Автоматизуємо і прискорюємо процес налаштування хмарних серверів з 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
завдання:

- 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 }}"

Процеси на віддалених машинах.


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


Ми бачимо, що виконання команди перенаправляється для локальної машини.


Таким чином, ви можете запустити будь-модуль Ansible з local_action.

Працюємо з умовами

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

- name: Install package apache2
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
завдання:

- 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 package httpd
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
завдання: 
- name: Install package nginx
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
завдання:
- 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
завдання:
- 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. Словник — простий список, тому для звернення використовуємо 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, коли: "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 }
завдання:
- 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.

Успішної роботи!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.