Автоматизация развёртывания Linux-серверов с Ansible: пишем первые плейбуки

Проблема

Ручная настройка даже десятка 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

Этот плейбук выполняет типовые действия, которые администратор проделывает с каждым новым сервером. Модули aptuserauthorized_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) не проверяют текущее состояниеСтарайтесь использовать специализированные модули (lineinfiletemplatefilecopy), а не shell/command, где это возможно.
Не отрабатывает хендлер reload nginxВ предыдущей задаче не произошло измененийХендлеры выполняются только если задача-триггер вернула статус changed. Проверьте, что копируемый файл действительно отличается от существующего.

Ansible playbook это не просто скрипт, а полноценная документация инфраструктуры в коде. Написав два базовых плейбука, вы уже можете за минуты привести «голый» сервер к полностью рабочему состоянию, а при необходимости развернуть веб-стек на десятках машин одной командой. В будущем эти плейбуки легко расширить: добавить установку MariaDB, настройку мониторинга, интеграцию с Vault для хранения секретов. Ansible снижает порог входа в Infrastructure as Code, а привычка писать плейбуки для каждой повторяющейся операции — один из признаков зрелого администратора.

Menu