В предыдущей статье мы разобрались в том, что такое инфраструктура как код и познакомились с Ansible. Теперь поработаем с ним вплотную — развернём LEMP (Linux, Nginx, MySQL, PHP mfp) стек на Ubuntu 18.
Мы будем работать с Ansible из под Ubuntu 20, которая крутится на WSL2. О том, что такое WSL2 и как им пользоваться можно узнать из этой статьи.
Структура проекта
Любая работа над проектом начинается с постановки задачи и проработки структуры проекта. Задача: одновременная установка LEMP-стека на несколько хостов (удаленных серверов).
Структура проекта:
.
├── files
│ ├── info.php.j2
│ └── nginx.conf.j2
├── hosts.yml
├── playbook.yml
├── tree.txt
└── vars
└── default.yml
Помимо уже хорошо знакомого нам yaml-формата в проекте есть и пока незнакомый нам j2-формат. Это формат шаблонизатора Jinja2. Мы его применим для подстановки динамических значений в конфигурационный Nginx-файл.
Механика взаимодействия элементов проекта следующая: Ansible подключается к хостам по IP-адресам, указанным в hosts.yml, выполняет задачи, прописанные в playbook.yml и загружает файлы из папки files на хосты. Помимо этого файлы связаны внутренней логикой, которая будет раскрыта далее.
С задачами и структурой проекта определились, можно устанавливать Ansible.
Установка Ansible
Ansible не входит в стандартные репозитории Linux, поэтому мы добавим альтернативный источник командой: sudo apt-add-repository ppa:ansible/ansible.
Команда вернёт вывод примерно такого вида:
После нажатия на Enter — будет произведена инициализация нового ppa. Теперь можно устанавливать Ansible привычным образом: sudo apt install ansible. Проверим установку Ansible командой: ansible --version. Результат работы команды:
Отчёт состоит из перечня параметров Ansible, среди которых конфигурационные файлы и пути их расположения. Для нас важным здесь является конфигурационный файл ansible.cfg. Вкратце рассмотрим его свойства.
ansible.cfg — конфигурация Ansible
Конфигурационный файл ansible.conf управляет такими параметрами поведения ансибла как:
- проверка рукопожатия при подключении по ssh;
- указание расположения не дефолтного hosts-файла;
- количество одновременных подключений к хостам;
и многое другое.
Ansible.cfg расположен по адресу: /etc/ancible/ansible.conf и имеет декларативный ini-формат. Это удобно, так как количество возможных ошибок при его заполнении сильно сокращается.
По умолчанию ansible.cfg содержит только закомментированный текст. Мы же укажем в нём буквально один параметр, который облегчит нам работу с Ansible:
[defaults]
host_key_checking = False
Строка [defaults] — указывает на название секции, к которой относятся все последующие настройки до указания новой секции. Не дефолтный ansible.cfg обязательно должен иметь секцию defaults.
host_key_checking — это параметр, который отвечает за проверку ключей при подключении по SSH, установив ему атрибут False, мы отключили проверку — это полезно, когда нужно подключиться к большому количеству устройств первый раз.
Больше ничего в ansible.cfg мы менять не будем. Нам достаточно и этих параметров. С первым компонентом Ansible — разобрались. Можно переходить к разбору следующего компонента — инвентаризационного файла hosts.
hosts — конфигурация подключения к серверам
Файл hosts содержит данные для подключения к удаленным серверам. По умолчанию дефолтный hosts-файл располагается по следующему пути: /etc/ansible/hosts — там же, где и ansible.cfg. Кстати, файл содержит отличную документацию по тому как его заполнять.
Менять оригинальный hosts-файл мы не будем, мы создадим новый файл в соответствии со структурой проекта и расположим его корне нашего проекта.
.
├── files
│ ├── info.php.j2
│ └── nginx.conf.j2
├── hosts.yml
├── playbook.yml
├── tree.txt
└── vars
└── default.yml
По умолчанию hosts-файл не имеет расширения, однако Ansible интерпретирует его как .ini-файл. Поэтому записи hosts и hosts.ini будут равнозначными. Но мы будем писать hosts-файл в формате yaml. Это нам позволит автоматизировать процесс раскатки LEMP-стека на несколько машин.
Вот как выглядит наш hosts.yml внутри:
all:
hosts:
server-1:
ansible_host: 188.227.72.2
ansible_connection: ssh
ansible_user: root
ansible_password: g5YLBOCx
server-2:
ansible_host: 188.227.75.26
ansible_connection: ssh
ansible_user: root
ansible_password: c6Nbp0dk
Кратко разберём, что за что отвечает:
- all — это первый уровень, название группы хостов;
- hosts — второй уровень, секция списка хостов;
- server-1 и server-2 — третий уровень, названия хостов;
- ansible_host — ip-адрес хоста;
- ansible_connection — тип подключения;
- ansible_user — пользователь для подключения по ssh;
- ansible_password — пароль для подключения по ssh.
Теперь протестируем наш hosts-файл — запустим модуль ping на всех хостах, указанных в файле hosts.yml: ansible all -i hosts.yml -m ping.
Вот, что вернула нам команда:
В отчёте мы видим:
- название хостов (server-1 и server-2) и статус задачи (SUCCESS);
- интерпретатор Python, который был задействован (discovered_interpreter_python) для выполнения модуля;
- изменилось ли состояние сервера(changed);
- результата выполнение модуля ping — ответ pong.
Всё работает корректно. Двигаемся дальше, на очереди центральный элемент Ansible — Playbook.
Playbook — сценарий работы Ansible
Playbook — это сценарий работы Ansible на хостах, написанный в формате YAML. Мы подробно разобрали работу с этим форматом в этой статье. Для корректной работы Playbook достаточно задать одну переменную — hosts: all:
---
- hosts: all #Название группы хостов в hosts-файле.
...
Это значит, что из hosts-файла, который будет передан через ключ i в Ansible будут взяты все хосты. Базовый вид команды для запуска плейбуков выглядит так: ansible-playbook playbook.yml -i hosts.ini. Команда вернет следующий результат:
По умолчанию Ansible всегда делает проверку фактов о хостах. Поэтому, запустив пустой Playbook мы увидим отчет о выполнении задач по сбору фактов на хостах: server-1 и server-2 со статусом «ok».
Помимо задач категории «ok» (выполненных) есть и другие задачи, такие как:
- changed — задачи, которые что-то изменили в системе;
- unreachable — задачи не достигшие хоста;
- failed — невыполненные задачи;
- skipped — пропущенные задачи;
- rescued — восстановленные задачи;
- ignored — проигнорированные задачи.
Обычно команда вида ansible-playbook playbook.yml -i hosts.ini, используется для проверки соединения с хостами. Наш тест работает исправно, можно добавлять блоки задач. Первый блок: преднастройка пакетного менеджера и установка пакетов LEMP-стека:
---
- hosts: all
become: true
vars_files:
- vars/default.yml
tasks:
- name: Преднастройка пакетного менеджера aptitude
apt: name={{ item }} update_cache=yes state=latest
force_apt_get=yes
loop: [ 'aptitude' ]
- name: Установка LEMP-стека
apt: name={{ item }} update_cache=yes state=latest
loop: [ 'nginx', 'mysql-server', 'python3-pymysql', 'php-fpm', 'php-mysql' ]
...
Разберем вкратце, что здесь записано:
- hosts — директива, указывающая для каких хостов из файла hosts.yml будут применены все последующие задачи;
- become — директива со значением true указывает на то, что все задачи должны выполняться от рута;
- vars_files — директива, указывающая на файл с переменными ({{ имя переменной }}), которые используются в плейбуке.
- tasks — основной раздел с задачами.
Обратите внимание, задачи имеют форму списка и вложены в словарь tasks. Правила создания задачи:
- задача должна обязательно начинаться с имени — name. Имя должно отражать логику выполнения задачи;
- задачи выполняется на удаленных хостах модулями, поэтому должен быть указан модуль.
- у модуля в зависимости от его параметров должны быть указаны обязательные аргументы.
Модулей для Ansible огромное количество. Узнать какой модуль за что отвечает и какими параметрами обладает можно из официальной документации Ansible.
Взглянем на финальный playbook целиком↓
---
- hosts: all
become: true
vars_files:
- vars/default.yml
tasks:
- name: Преднастройка пакетного менеджера aptitude
apt: name={{ item }} update_cache=yes state=latest
force_apt_get=yes
loop: [ 'aptitude' ]
- name: Установка LEMP-стека
apt: name={{ item }} update_cache=yes state=latest
loop: [ 'nginx', 'mysql-server', 'python3-pymysql', 'php-fpm', 'php-mysql' ]
# Настройка Nginx
- name: Скопировать и переименовать Nginx конфиг
template:
src: "files/nginx.conf.j2"
dest: "/etc/nginx/sites-available/{{ http_conf }}"
- name: Создать симлинк
file:
src: "/etc/nginx/sites-available/{{ http_conf }}"
dest: "/etc/nginx/sites-enabled/{{ http_conf }}"
state: link
notify: Reload Nginx
- name: Удалить дефолтный сайт
file:
path: "/etc/nginx/sites-enabled/default"
state: absent
notify: Reload Nginx
# Настройка MySQL
- name: Установка пользователя для подключения
mysql_user:
name: root
password: "{{ mysql_root_password }}"
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Удаление всех анонимных аккаунтов
mysql_user:
name: ''
host_all: yes
state: absent
login_user: root
login_password: "{{ mysql_root_password }}"
- name: Удаление тестовой базы данных
mysql_db:
name: test
state: absent
login_user: root
login_password: "{{ mysql_root_password }}"
# Настройка UFW
- name: "UFW - открытие {{ http_port }} HTTP порта"
ufw:
rule: allow
port: "{{ http_port }}"
proto: tcp
# Настройка PHP info страницы
- name: Настройка PHP info страницы
template:
src: "files/info.php.j2"
dest: "/var/www/html/info.php"
handlers:
- name: Перезагрузка Nginx
service:
name: nginx
state: reloaded
- name: Рестарт Nginx
service:
name: nginx
state: restarted
В плейбуке помимо повторяющихся из модуля в модуль деклараций встречаются ещё конструкции заключенные в двойные фигурные скобки: {{ имя }}. В зависимости от условий это может быть переменная, значение которой подтягивается из файла, указанного в vars_files или переменная, значение которой подставляется из конструкции loop.
Например, вот задача по установки всех элементов LEMP-стека:
- name: Установка LEMP-стека
apt: name={{ item }} update_cache=yes state=latest
loop: [ 'nginx', 'mysql-server', 'python3-pymysql', 'php-fpm', 'php-mysql' ]
В этой записи вместо name={{ item }} будет подставлено каждое значение из списка loop. Так по очереди будут установлены все компоненты LEMP-стека.
Вот другой пример:
- name: Скопировать и переименовать Nginx конфиг
template:
src: "files/nginx.conf.j2"
dest: "/etc/nginx/sites-available/{{ http_conf }}"
Здесь есть переменная {{ http_conf }}, её значение будет взято из файла, указанного в директиве vars_files. По основным механикам работы внутри Playbook — всё, осталось привести общую схему взаимодействия всех элементов проекта между собой.
Схема взаимодействия элементов проекта между собой:
Теперь, когда у нас все компоненты проекта взаимосвязаны и работают — можно запускать Ansible: ansible-playbook playbook.yml -i hosts.ini. После успешного выполнения всех задач, перейдём в браузер и обратимся к тестовой странице PHP: http://185.158.152.31/info.php. Если всё работает исправно, будет показана страница php-info:
Итак, у нас получился полноценный рабочий масштабируем проект по установки LEMP-стека на несколько хостов. Настало время собрать всё воедино.
Объединяем знания
Ansible — это мощный и расширяемый Open source инструмент, который позволяет управлять конфигурациями удаленных серверов и сохранять их состояние. Ansible использует безагентную push-модель, это значит, что для работы на хостах ему нужен только Python на Linux и открытый SSH-порт.
Принцип работы Ansible таков: DevOps-инженер или администратор указывает задачи (таски, от английского tasks) для Ansible в специальном файле сценариев — playbook, а сведения об хостах, на которых эти задачи должны быть исполнены указываются в инвентаризационном файле hosts.
Таски в плейбуке записываются, следуя логике установки и внесения изменений в систему. Так, чтобы установить LEMP-стек на хост, сначала нужно обновить менеджер пакетов, потом установить Nginx, php-fpm, mySQL/PostgreSQL, затем настроить доступы к БД и перезагрузить Nginx.
Пожалуй, главными свойствами Ansible являются идемпотентность, расширяемость и следование философии единого источника. Конечно, Ansible не единственная система работы с инфраструктурой как кодом с подобным подходом. Ближайший конкурент ансиблу — это Terraform.
В следующих публикациях: подружим 1cloud API и Ansible, автоматизируем процесс создания виртуальных серверов в обход Панели, познакомимся с Terraform, попробуем его на боевую и сравним с Ansible. Для большего погружения в будущий материал, рекомендуем ознакомиться с этими публикациями:
Введение в IaC и знакомство с Ansible
Рассказываем, что такое инфраструктура как код и знакомимся с концепциями Ansible.
YAML для начинающих
Разбираемся в YAML и учимся писать на нём конфигурационные файлы.
Linux внутри Windows 10
Краткий мануал для тех, кто хочет использовать фишки обоих ОС одновременно.