Проблема
Ручная настройка даже десятка Linux-серверов быстро превращается в рутину: установить одни и те же пакеты, создать одинаковых пользователей, разложить SSH-ключи, поправить конфигурационные файлы, открыть нужные порты. С ростом количества машин ошибки, опечатки и расхождения в конфигурациях становятся неизбежными. При передаче дел другому администратору приходится вручную описывать все шаги, а повторное развёртывание стенда занимает часы. Ansible playbook это решение, превращающее «ручную работу» в воспроизводимый, самодокументируемый код. Системный администратор описывает желаемое состояние сервера в YAML-файле, запускает одну команду и получает идентично настроенные машины за минуты. При этом не требуется устанавливать агентов на целевые хосты, достаточно SSH-доступа и Python.
Решение
Будем использовать Ansible систему управления конфигурациями с открытым исходным кодом, работающую по протоколу SSH. В отличие от конкурентов (Puppet, Chef), Ansible не требует отдельного сервера или агентов, а плейбуки пишутся на простом YAML, что снижает порог входа. Напишем два плейбука: один для начальной настройки Ubuntu-сервера (установка базовых пакетов, создание администратора, настройка брандмауэра), второй — для развёртывания веб-стека (Nginx + PHP). Все примеры будут протестированы на свежей установке Ubuntu 24.04 LTS. Официальная документация по Ansible доступна на docs.ansible.com, синтаксис модулей описан в Ansible Module Index, а лучшие практики структуры ролей — в Best Practices Guide.
Пошаговая инструкция
Шаг 1. Установка Ansible на управляющий узел
Управляющий узел (control node) может быть любым компьютером с Python. В Linux установка выполняется из репозитория:
bash
sudo apt update && sudo apt install ansible -y # Debian/Ubuntu
# sudo dnf install ansible -y # RHEL/Rocky Linux
Проверьте версию:
bash
ansible --version
Шаг 2. Подготовка инвентарного файла
Инвентарный файл описывает список целевых хостов. Создайте файл inventory.ini:
[webservers]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11
[admins]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Группа [webservers] пригодится для веб-плейбука, [admins] — для базовой настройки. В [all:vars] задаём глобальные переменные: пользователь для подключения и путь к SSH-ключу. Подробнее об инвентаре: Ansible Inventory Guide.
Шаг 3. Пишем первый плейбук: базовая настройка сервера
Создайте файл base-setup.yml:
yaml
---
- name: Базовая настройка серверов
hosts: admins
become: yes
vars:
admin_user: operator
admin_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
tasks:
- name: Установить базовые пакеты
apt:
name:
- htop
- vim
- curl
- wget
- git
- ufw
- unattended-upgrades
state: present
update_cache: yes
- name: Создать административного пользователя
user:
name: "{{ admin_user }}"
state: present
groups: sudo
shell: /bin/bash
create_home: yes
- name: Добавить SSH-ключ администратора
authorized_key:
user: "{{ admin_user }}"
state: present
key: "{{ admin_public_key }}"
- name: Отключить вход root по SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
state: present
notify: restart sshd
- name: Настроить брандмауэр UFW
ufw:
rule: "{{ item.rule }}"
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop:
- { rule: allow, port: 22, proto: tcp }
- { rule: allow, port: 80, proto: tcp }
- { rule: allow, port: 443, proto: tcp }
- { rule: deny, port: '', proto: '' }
when: item.port | default('') != '' or item.rule == 'deny'
- name: Включить UFW
ufw:
state: enabled
policy: deny
direction: incoming
- name: Настроить автоматические обновления безопасности
copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
handlers:
- name: restart sshd
service:
name: sshd
state: restarted
Этот плейбук выполняет типовые действия, которые администратор проделывает с каждым новым сервером. Модули apt, user, authorized_key и ufw — стандартные. Конструкция notify: restart sshd вызывает хендлер (перезагрузку SSH) только при изменении конфигурации.
Шаг 4. Второй плейбук: развёртывание веб-стека Nginx + PHP
Файл webserver.yml:
yaml
---
- name: Развёртывание веб-сервера Nginx + PHP
hosts: webservers
become: yes
tasks:
- name: Установить Nginx и PHP-FPM
apt:
name:
- nginx
- php-fpm
- php-cli
- php-mysql
state: present
update_cache: yes
- name: Создать директорию сайта
file:
path: /var/www/example.com
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Разместить index.php
copy:
dest: /var/www/example.com/index.php
content: |
<?php phpinfo(); ?>
owner: www-data
group: www-data
mode: '0644'
- name: Скопировать конфигурацию виртуального хоста Nginx
template:
src: nginx-vhost.conf.j2
dest: /etc/nginx/sites-available/example.com
owner: root
group: root
mode: '0644'
notify: reload nginx
- name: Активировать виртуальный хост
file:
src: /etc/nginx/sites-available/example.com
dest: /etc/nginx/sites-enabled/example.com
state: link
- name: Удалить стандартный сайт
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: reload nginx
- name: Открыть порт 80 в брандмауэре
ufw:
rule: allow
port: '80'
proto: tcp
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
Здесь впервые используется модуль template. Он позволяет генерировать конфигурационные файлы на основе Jinja2-шаблонов. Создайте файл шаблона nginx-vhost.conf.j2 рядом с плейбуком:
jinja2
server {
listen 80;
server_name {{ ansible_host }};
root /var/www/example.com;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}
}
Переменная {{ ansible_host }} будет автоматически подставлена из фактов целевого хоста (IP или hostname). Чтобы собирать факты, в плейбуке неявно используется gather_facts: true, что включено по умолчанию.
Шаг 5. Запуск плейбуков
Перед первым запуском проверьте синтаксис:
bash
ansible-playbook -i inventory.ini base-setup.yml --syntax-check
Запустите плейбук:
bash
ansible-playbook -i inventory.ini base-setup.yml
Вы увидите цветной вывод: зеленый изменений не было, желтый выполнена операция, красный ошибка.
Для веб-плейбука аналогично:
bash
ansible-playbook -i inventory.ini webserver.yml
После отработки откройте в браузере http://192.168.1.10 (или IP вашего сервера), чтобы увидеть вывод phpinfo().
Шаг 6. Использование ролей для масштабирования
Когда плейбуки разрастутся, вы неизбежно захотите переиспользовать задачи. Структура роли создаётся командой:
bash
mkdir -p roles/common/tasks
echo "---" > roles/common/tasks/main.yml
Повторяющиеся задачи (установка пакетов, настройка SSH) выносятся в роль, а в плейбуке достаточно указать:
yaml
- name: Применение общей роли
hosts: all
become: yes
roles:
- common
Подробности в Ansible Roles Documentation.
Устранение распространённых проблем
| Симптом | Вероятная причина | Решение |
|---|---|---|
| «Failed to connect to the host via ssh» | Неверный ключ, пользователь или IP | Проверьте SSH вручную: ssh -i ~/.ssh/id_ed25519 root@192.168.1.10. Убедитесь, что в inventory.ini правильные данные. |
«Permission denied (publickey)» при использовании become: yes | Пользователь не входит в группу sudo или отсутствует право NOPASSWD | Добавьте пользователя в sudoers с NOPASSWD: echo "operator ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/operator. |
| Ошибка «template not found» | Шаблон лежит не рядом с плейбуком или неверный путь | Убедитесь, что файл .j2 лежит в той же директории или в templates/ роли. |
Плейбук падает на apt с ошибкой блокировки | Другой процесс обновления уже запущен | Выполните sudo killall apt apt-get на целевом хосте или добавьте задержку перед запуском плейбука. |
| Задачи выполняются, но сервер настраивается неверно | Не учтена идемпотентность: некоторые модули (например, shell) не проверяют текущее состояние | Старайтесь использовать специализированные модули (lineinfile, template, file, copy), а не shell/command, где это возможно. |
Не отрабатывает хендлер reload nginx | В предыдущей задаче не произошло изменений | Хендлеры выполняются только если задача-триггер вернула статус changed. Проверьте, что копируемый файл действительно отличается от существующего. |
Ansible playbook это не просто скрипт, а полноценная документация инфраструктуры в коде. Написав два базовых плейбука, вы уже можете за минуты привести «голый» сервер к полностью рабочему состоянию, а при необходимости развернуть веб-стек на десятках машин одной командой. В будущем эти плейбуки легко расширить: добавить установку MariaDB, настройку мониторинга, интеграцию с Vault для хранения секретов. Ansible снижает порог входа в Infrastructure as Code, а привычка писать плейбуки для каждой повторяющейся операции — один из признаков зрелого администратора.







