opened image

Ansible: автоматизируем начальную настройку сервера с playbook

 

Вы арендовали новый VPS. Теперь нужно: обновить пакеты, создать пользователя с sudo, отключить вход по паролю для root, установить файрвол, Fail2ban и nginx. Руками это занимает 20-30 минут на один сервер. А если серверов пять? Или двадцать?

 

Ansible решает эту задачу иначе: вы описываете желаемое состояние сервера в YAML-файле (playbook), и Ansible приводит сервер к этому состоянию через SSH. Никаких агентов, никаких дополнительных сервисов на серверах. Нужен только Python, который уже есть почти в каждом Linux-дистрибутиве.

 

 

Как работает Ansible

 

Ansible запускается на вашей управляющей машине (ноутбук, рабочая станция или отдельный control node). Он подключается к серверам по SSH и выполняет задачи последовательно.

 

Три основных понятия:

  • Inventory - список серверов, которыми управляет Ansible. Хосты объединяются в группы.
  • Playbook - YAML-файл с описанием задач. Каждая задача использует модуль Ansible: apt для установки пакетов, user для управления пользователями, copy для копирования файлов и так далее.
  • Module - отдельная единица функциональности. В Ansible более 3000 встроенных модулей.

 

Схема выполнения: вы запускаете ansible-playbook playbook.yml → Ansible читает inventory → подключается по SSH к каждому хосту → выполняет задачи → сообщает о результатах.

 

Ansible идемпотентен: если задача уже выполнена (пакет установлен, пользователь создан), Ansible пропустит её без изменений. Можно запускать тот же playbook несколько раз без побочных эффектов.

 

 

Установка Ansible

 

На управляющей машине с Ubuntu/Debian:

apt update
apt install -y software-properties-common
add-apt-repository --yes --update ppa:ansible/ansible
apt install -y ansible
ansible --version

 

На macOS через Homebrew:

brew install ansible

 

Через pip (работает на любой платформе с Python 3):

pip3 install ansible

 

Проверьте установку:

ansible --version

 

Вывод покажет версию и путь к конфигурационному файлу.

 

 

Inventory-файл

 

Создайте рабочую директорию и inventory-файл:

mkdir ~/ansible-server-setup
cd ~/ansible-server-setup
nano inventory.ini
[web]
web1 ansible_host=203.0.113.10 ansible_user=root
web2 ansible_host=203.0.113.11 ansible_user=root

[db]
db1 ansible_host=203.0.113.20 ansible_user=root

[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3

 

Здесь определены две группы: web (два веб-сервера) и db (один сервер базы данных). В секции [all:vars] указаны общие переменные для всех хостов.

 

Проверьте связь со всеми хостами:

ansible all -i inventory.ini -m ping

 

Каждый доступный хост ответит pong. Если какой-то хост недоступен, Ansible сообщит об ошибке подключения.

 

 

Полный playbook для начальной настройки сервера

 

Создайте файл setup.yml:

---
- name: Initial server setup
  hosts: all
  become: true
  vars:
    new_user: deploy
    ssh_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@laptop"
    ssh_port: 22

  tasks:

    # Обновление системы
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Upgrade all packages
      apt:
        upgrade: dist
        autoremove: yes

    # Установка базовых пакетов
    - name: Install base packages
      apt:
        name:
          - curl
          - wget
          - vim
          - git
          - htop
          - ufw
          - fail2ban
          - nginx
          - unattended-upgrades
        state: present

    # Создание пользователя sudo
    - name: Create deploy user
      user:
        name: "{{ new_user }}"
        shell: /bin/bash
        groups: sudo
        append: yes
        create_home: yes
        state: present

    - name: Add SSH key for deploy user
      authorized_key:
        user: "{{ new_user }}"
        key: "{{ ssh_public_key }}"
        state: present

    - name: Allow sudo without password for deploy user
      lineinfile:
        path: /etc/sudoers
        state: present
        regexp: "^{{ new_user }}"
        line: "{{ new_user }} ALL=(ALL) NOPASSWD: ALL"
        validate: visudo -cf %s

    # Настройка SSH
    - name: Disable root SSH login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PermitRootLogin"
        line: "PermitRootLogin no"
      notify: restart ssh

    - name: Disable password authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PasswordAuthentication"
        line: "PasswordAuthentication no"
      notify: restart ssh

    # Настройка UFW
    - name: Set UFW default incoming to deny
      ufw:
        direction: incoming
        policy: deny

    - name: Set UFW default outgoing to allow
      ufw:
        direction: outgoing
        policy: allow

    - name: Allow SSH
      ufw:
        rule: allow
        port: "{{ ssh_port }}"
        proto: tcp

    - name: Allow HTTP
      ufw:
        rule: allow
        port: 80
        proto: tcp

    - name: Allow HTTPS
      ufw:
        rule: allow
        port: 443
        proto: tcp

    - name: Enable UFW
      ufw:
        state: enabled

    # Настройка Fail2ban
    - name: Copy fail2ban jail config
      copy:
        dest: /etc/fail2ban/jail.local
        content: |
          [DEFAULT]
          bantime = 3600
          findtime = 600
          maxretry = 5

          [sshd]
          enabled = true
          port = {{ ssh_port }}
          logpath = %(sshd_log)s
          backend = %(sshd_backend)s
      notify: restart fail2ban

    # Включение автообновлений безопасности
    - name: Enable unattended upgrades
      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";

    # Nginx
    - name: Start and enable nginx
      systemd:
        name: nginx
        state: started
        enabled: yes

  handlers:
    - name: restart ssh
      systemd:
        name: sshd
        state: restarted

    - name: restart fail2ban
      systemd:
        name: fail2ban
        state: restarted

 

 

Запуск playbook

 

Перед запуском проверьте синтаксис:

ansible-playbook -i inventory.ini setup.yml --syntax-check

 

Запустите в режиме --check (dry run). Ansible покажет, что будет изменено, но ничего не применит:

ansible-playbook -i inventory.ini setup.yml --check --diff

 

Флаг --diff показывает конкретные изменения в файлах: что было и что станет после выполнения задачи.

 

Применение изменений:

ansible-playbook -i inventory.ini setup.yml

 

Для запуска только на одном хосте:

ansible-playbook -i inventory.ini setup.yml --limit web1

 

Для запуска только определённых задач по тегу добавьте теги в playbook и используйте --tags:

ansible-playbook -i inventory.ini setup.yml --tags "ufw,fail2ban"

 

 

 

 

Структура проекта для нескольких серверов

Когда playbook вырастает, имеет смысл разбить его на роли:

ansible-server-setup/
├── inventory.ini
├── setup.yml
├── group_vars/
│   ├── all.yml          # переменные для всех хостов
│   └── web.yml          # переменные только для группы web
├── host_vars/
│   └── web1.yml         # переменные только для web1
└── roles/
    ├── common/
    │   └── tasks/
    │       └── main.yml
    ├── nginx/
    │   └── tasks/
    │       └── main.yml
    └── security/
        └── tasks/
            └── main.yml

 

Такая структура позволяет применять роли к разным группам хостов независимо.

 

 

Итог

 

Ansible позволяет настроить новый сервер за один запуск команды вместо ручных операций. Playbook из этой статьи выполняет обновление системы, создание безопасного пользователя, настройку UFW, Fail2ban и Nginx. Режим --check позволяет проверить изменения до их применения.

 

После запуска playbook на VPS от Zomro вы получаете сервер с закрытым root-доступом, настроенным файрволом и автоматическими обновлениями безопасности.

Один и тот же playbook можно применить к любому количеству серверов.