Ansible – это программа, которая живёт на твоём компьютере, заходит на серверы по SSH и приводит их к тому состоянию, которое ты описал в текстовом файле. Ставить на VPS ничего не нужно: хватает SSH-доступа и Python, который на Ubuntu и Debian уже есть. Один раз пишешь «на сервере должны стоять такие пакеты, такой пользователь, такие правила файрвола», и дальше хоть второй сервер, хоть пятый, хоть пересобранный после аварии первый настраиваются одной командой.
Если у тебя один VPS и ты заходишь на него раз в месяц поправить конфиг, Ansible честно не нужен, быстрее сделать руками. Он начинает окупаться, когда серверов два и больше, когда ты периодически пересоздаёшь сервер с нуля или когда хочешь через полгода помнить, что именно на нём настраивал. Если сервер ещё совсем голый и ты не заходил на него по SSH, начни с настройки VPS для новичков и входа по SSH-ключу, без ключа дальше будет неудобно.
Быстрый старт: от установки до первого прогона
Команды ниже рассчитаны на то, что Ansible ставится на твой компьютер с Linux, macOS или Windows с WSL, а управляемые серверы работают на Ubuntu 24.04, Ubuntu 26.04 или Debian 13. Вместо 203.0.113.10 подставляй IP своего VPS.
- Поставь Ansible через pipx (на macOS pipx ставится через Homebrew, на Ubuntu и Debian через apt):
sudo apt install pipx pipx ensurepath pipx install --include-deps ansible ansible --version
После pipx ensurepath открой терминал заново, иначе команда ansible не найдётся.
- Создай папку проекта и в ней файл инвентаря
hosts.ini:
[web] web1 ansible_host=203.0.113.10 [all:vars] ansible_user=deploy
- Рядом положи
ansible.cfg, чтобы не указывать инвентарь в каждой команде:
[defaults] inventory = hosts.ini interpreter_python = auto_silent [connection] pipelining = True
- Проверь связь. Пользователя
deployна свежем сервере ещё нет, поэтому первый раз заходим под root:
ansible all -m ping -e ansible_user=root
- Возьми playbook
site.ymlиз раздела ниже, сначала прогони его вхолостую, потом по-настоящему:
ansible-playbook site.yml -e ansible_user=root --check --diff ansible-playbook site.yml -e ansible_user=root
- Все следующие запуски делай уже без
-e ansible_user=root, от имениdeploy.
Если ping ответил "ping": "pong", значит, всё в порядке. Учти, что это не ICMP-пинг: Ansible зашёл по SSH, запустил на сервере крошечную программу на Python и получил ответ. Поэтому зелёный pong гарантирует сразу три вещи: сеть, SSH-доступ и рабочий Python на той стороне.
Куда ставить Ansible и почему не из apt
Ставить его нужно туда, откуда ты управляешь серверами, т.е. на свой рабочий компьютер. В документации это называется control node, а серверы, которыми управляют, называются managed nodes. Нативно под Windows Ansible не работает, только через WSL. Ставить Ansible на сам VPS, чтобы он настраивал сам себя, тоже можно, но тогда теряется главный смысл: описание всех серверов лежит в одном месте, а не размазано по каждому из них.
С пакетом из репозитория дистрибутива есть подвох, и он в версиях. Проект выпускает новую версию ansible-core каждые полгода и поддерживает три последние, а дистрибутивы замораживают то, что было на момент релиза:
| Откуда | Версия ansible-core | Что это значит |
|---|---|---|
| apt в Ubuntu 24.04 | 2.16 | Проект перестал её поддерживать в июле 2025 года |
| apt в Debian 13 | 2.19 | Поддержка проекта заканчивается в ноябре 2026 года |
| apt в Ubuntu 26.04 | 2.20 | Пока поддерживается, до мая 2027 года |
| pipx | 2.21 (пакет ansible 14) | Актуальная ветка на момент написания |
Старая версия сама по себе не катастрофа, дистрибутивы латают в ней дыры. Неприятности начинаются, когда ты берёшь роль или пример из свежей документации, а твоя версия его не понимает, или наоборот: учишься по старому руководству из интернета, а новая версия отказывается его выполнять. Про второй случай ниже будет отдельный раздел, он на удивление частый.
pipx ставит Ansible в изолированное окружение, не трогая системный Python. Обычный pip install в Ubuntu 24.04 и Debian 13 выдаст ошибку externally-managed-environment, это защита системы от смешивания пакетов, и обходить её флагом --break-system-packages ради Ansible не стоит. Обновляется всё одной командой pipx upgrade --include-injected ansible.
Есть ограничение по Python. Ansible-core 2.20 и 2.21 требуют на твоём компьютере Python 3.12 или новее, а на серверах Python 3.9 или новее. На практике это означает две вещи. Debian 12 в роли управляющей машины получит максимум ansible-core 2.19, потому что там Python 3.11. А сервер на Ubuntu 20.04 с Python 3.8 новые версии уже не обслуживают, и если такой ещё крутится в хозяйстве, его пора обновить до свежего релиза.
И выбор между двумя пакетами. ansible-core содержит только движок и встроенные модули, а ansible добавляет к нему больше 85 коллекций, в том числе модули для ufw, SSH-ключей, MySQL, Docker. Новичку проще брать полный пакет. Если поставить только core, первый же playbook с файрволом упадёт с ошибкой couldn't resolve module/action 'community.general.ufw', и это не опечатка, а просто отсутствующая коллекция: её доставляют командой ansible-galaxy collection install community.general.
Инвентарь, ansible.cfg и первое подключение
Инвентарь – это список серверов, разложенных по группам. Группа [web] в примере выше нужна, чтобы потом запускать задачи только на веб-серверах, а не на всём подряд; группа all существует всегда и включает все хосты. Переменная ansible_host говорит, куда подключаться, а имя слева (web1) используется только внутри Ansible, в выводе и в командах.
Теперь про -e ansible_user=root, это место многих ставит в тупик. Логичнее выглядит флаг -u root, и он действительно задаёт пользователя. Но у Ansible жёсткая иерархия приоритетов, и переменная ansible_user из инвентаря сильнее флага командной строки. В итоге ты пишешь -u root, а Ansible упрямо лезет под deploy, которого ещё нет, и получает отказ. Переменные через -e стоят на самой вершине иерархии, их не перебивает ничто.
Мелочь, но на ней легко потерять первый вечер знакомства с Ansible, раз за разом перечитывая правильный на вид инвентарь.
При первом подключении может вылезти Host key verification failed. Ansible по умолчанию проверяет отпечаток сервера так же, как обычный ssh, и если ты ни разу не заходил на этот IP, отпечатка в ~/.ssh/known_hosts нет. Самое простое решение: один раз зайти на сервер руками по SSH и подтвердить отпечаток. Совет из интернета выключить проверку через host_key_checking = False работает, но делает тебя беззащитным перед подменой сервера, так что держать его включённым постоянно я бы не стал. Ещё неприятнее ситуация, когда провайдер пересоздал VPS на том же IP: отпечаток сменился, и SSH честно ругается. Старую запись удаляют так:
ssh-keygen -R 203.0.113.10
Файл ansible.cfg Ansible ищет сначала в текущей папке, потом в домашней, потом в /etc/ansible/, и берёт первый найденный. Здесь ловушка для пользователей WSL. Если папка проекта лежит на диске Windows (путь вида /mnt/c/Users/...), она выглядит для Linux как доступная на запись всем, и Ansible из соображений безопасности игнорирует конфиг в ней. Единственный след этого – предупреждение про world writable directory в начале вывода, которое легко пропустить. Инвентарь вдруг «не находится», хотя файл на месте. Держи проект внутри файловой системы WSL, в домашней папке Linux, и проблема исчезает.
Playbook базовой настройки VPS
Playbook – это YAML-файл, в котором по шагам описано желаемое состояние. Каждый шаг называется задачей (task) и вызывает модуль: apt ставит пакеты, user создаёт пользователей, copy кладёт файлы. Ниже рабочий site.yml, который делает с новым VPS то же, что обычно делают руками в первый час: обновляет систему, создаёт пользователя с sudo и твоим ключом, включает файрвол и закрывает вход по паролю.
- name: Базовая настройка VPS
hosts: all
become: true
vars:
admin_user: deploy
admin_key: "{{ lookup('ansible.builtin.file', '~/.ssh/id_ed25519.pub') }}"
tasks:
- name: Обновить индекс пакетов и систему
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
upgrade: dist
lock_timeout: 300
- name: Поставить базовые пакеты
ansible.builtin.apt:
name:
- ufw
- htop
- curl
state: present
- name: Создать пользователя
ansible.builtin.user:
name: "{{ admin_user }}"
groups: sudo
append: true
shell: /bin/bash
- name: Положить SSH-ключ пользователю
ansible.posix.authorized_key:
user: "{{ admin_user }}"
key: "{{ admin_key }}"
- name: Разрешить sudo без пароля
ansible.builtin.copy:
dest: "/etc/sudoers.d/90-{{ admin_user }}"
content: "{{ admin_user }} ALL=(ALL) NOPASSWD:ALL\n"
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Открыть порты SSH, HTTP и HTTPS
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "22"
- "80"
- "443"
- name: Включить файрвол
community.general.ufw:
state: enabled
- name: Запретить вход по паролю и под root
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/10-hardening.conf
content: |
PasswordAuthentication no
PermitRootLogin no
mode: "0644"
validate: /usr/sbin/sshd -t -f %s
notify: Перечитать конфиг SSH
handlers:
- name: Перечитать конфиг SSH
ansible.builtin.systemd_service:
name: ssh
state: reloaded Большая часть этого файла читается без пояснений, но несколько строк здесь стоят не для красоты.
Порядок задач. Порт 22 открывается раньше, чем включается ufw, а вход под root закрывается в самом конце, когда пользователь, ключ и sudo уже на месте. Если поменять эти шаги местами, playbook отработает без ошибок и отрежет тебя от сервера. Если любая задача упадёт, Ansible остановит выполнение для этого хоста, и до закрытия root-входа дело не дойдёт. На всякий случай перед первым прогоном держи открытой веб-консоль провайдера: при неудаче через неё придётся восстанавливать доступ. Как устроен сам файрвол и почему Docker умеет его обходить, разобрано в статье про UFW и iptables.
Параметр validate. Прежде чем положить файл на место, Ansible проверяет его командой: visudo -cf для sudoers, sshd -t для конфига SSH. Сломанный sudoers на сервере без root-пароля превращается в очень грустный вечер, а с этой проверкой кривой файл просто не будет записан.
Префикс 10 в имени файла. В Ubuntu на многих облачных образах в папке /etc/ssh/sshd_config.d/ уже лежит 50-cloud-init.conf со строкой PasswordAuthentication yes. OpenSSH берёт первое встреченное значение параметра, а файлы читает по алфавиту. Назовёшь свой файл 60-hardening.conf, и пароль останется включённым, хотя в конфиге написано обратное. Именно так на клиентских серверах чаще всего и обнаруживается «отключённый» вход по паролю, который на деле работает. Подробнее про защиту SSH будет в отдельной статье о настройке SSH.
Handler. Задача с notify не перезапускает SSH сразу, а ставит отметку. Обработчик выполнится один раз в конце прогона и только если файл действительно изменился. Используется reloaded, а не restarted: демон перечитает конфиг, а уже открытые сессии, в том числе сессия самого Ansible, не оборвутся.
sudo без пароля. Это компромисс, а не рекомендация на все случаи. С NOPASSWD Ansible работает без лишних вопросов, но украденный ключ пользователя deploy тут же становится ключом от root. Если хочешь, чтобы sudo спрашивал пароль, удали эту задачу, задай пользователю пароль и запускай playbook с флагом -K, Ansible спросит пароль один раз в начале. Зачем вообще отдельный пользователь и как устроены группы, будет разобрано в материале о пользователях и sudo.
lock_timeout. На только что созданном VPS система нередко в первые минуты после загрузки сама запускает фоновое обновление пакетов и держит блокировку apt. По умолчанию модуль ждёт 60 секунд, потом падает с ошибкой про lock. Пять минут ожидания снимают эту проблему почти полностью, и это, пожалуй, самая частая причина, почему «вчера работало, а на новом сервере упало».
Для Debian 13 файл подходит без изменений. Для AlmaLinux и Rocky пришлось бы заменить apt на dnf, ufw на firewalld, группу sudo на wheel и имя службы ssh на sshd, т.е. переписать почти половину.
Второй прогон важнее первого
После прогона Ansible показывает сводку по каждому хосту: сколько задач ok (уже было как надо), сколько changed (пришлось поменять), сколько failed. Главная проверка качества playbook проста: запусти его второй раз подряд. В хорошем playbook второй прогон даёт changed=0. Это и есть идемпотентность, громкое слово, за которым стоит одна мысль: ты описываешь не действия, а результат, и повторное выполнение ничего не ломает и не дублирует.
Встроенные модули идемпотентны сами. apt с state: present не станет переустанавливать nginx, user не создаст второго deploy. А вот модули command и shell ничего не знают о результате и на каждом запуске честно отчитываются changed. Новички любят переносить в Ansible свои bash-скрипты строчка за строчкой через shell, и получается тот же скрипт, только медленнее и с YAML-отступами. Если без команды не обойтись, подскажи Ansible, когда её не нужно запускать:
- name: Скачать и распаковать приложение
ansible.builtin.shell: curl -fsSL https://example.com/app.tar.gz | tar xz -C /opt
args:
creates: /opt/app/bin/app С параметром creates задача выполнится, только если файла ещё нет.
Про --check --diff из быстрого старта. Холостой прогон показывает, что Ansible собирается поменять, а --diff выводит построчную разницу в конфигах, так что перед изменением на рабочем сервере это лучший способ не удивиться. Но у проверки есть слепое пятно: задачи, которые зависят от предыдущих, в холостом режиме могут падать. Если первый шаг ставит пакет, а второй настраивает его службу, в --check пакет не поставится, и второй шаг пожалуется на отсутствующую службу. Это не ошибка playbook, а ограничение режима.
Когда старый playbook перестал работать
С середины 2025 года в Ansible переписан движок шаблонов, и начиная с ansible-core 2.19 часть старых playbook, спокойно работавших годами, падает. Интернет полон примеров, написанных до этого, так что ты почти наверняка на них наткнёшься. Самая частая ошибка выглядит так:
Conditional result was 'nginx' of type 'str', which evaluates to True. Conditionals must have a boolean result.
Раньше условие when: web_server срабатывало, если переменная просто была непустой. Теперь условие обязано давать ровно «да» или «нет». Звучит как придирка, но в документации к этой версии прямо сказано, что такие «примерно истинные» условия годами прятали ошибки: условие с лишними кавычками было истинным всегда, и задача выполнялась там, где не должна была. Исправляется явной проверкой:
when: web_server | length > 0 when: web_server == "nginx"
Вторая типичная находка в старых примерах – фигурные скобки внутри условий, вроде when: "{{ install_docker }}". Сейчас это даёт предупреждение об устаревшем синтаксисе, а в будущих версиях станет ошибкой. В when переменные пишутся без скобок: when: install_docker.
Третья перемена пока только предупреждает. Факты о сервере вроде ansible_distribution или ansible_memtotal_mb исторически доступны как отдельные переменные, но в ansible-core 2.24 это поведение выключат по умолчанию. Уже сейчас лучше писать ansible_facts['distribution'] и ansible_facts['memtotal_mb'], внутри словаря приставка ansible_ отбрасывается.
Секреты: Vault без самообмана
Рано или поздно в playbook понадобится пароль базы данных или токен API. Класть их открытым текстом в папку проекта, которая потом окажется в Git, плохая идея, и для этого есть встроенный Ansible Vault. Удобная схема такая:
mkdir -p group_vars/all ansible-vault create group_vars/all/vault.yml
В зашифрованный файл кладёшь сами значения с приставкой vault_, например vault_db_password: "...", а в соседнем открытом group_vars/all/vars.yml ссылаешься на них: db_password: "{{ vault_db_password }}". Смысл в том, что поиском по проекту видно, где переменная используется, хотя её значение зашифровано. Запуск с паролем от хранилища:
ansible-playbook site.yml --ask-vault-pass
Теперь про самообман. Шифрование защищает файл в репозитории, но не вывод Ansible. Если задача получает пароль в параметрах и падает, пароль может оказаться прямо в сообщении об ошибке на экране или в логе CI. Задачам с секретами добавляй no_log: true. А если пароль от Vault лежит в файле рядом с проектом, чтобы не вводить его каждый раз, этот файл должен быть вне репозитория, с правами 600, иначе шифровать было незачем…
Роли и Galaxy: когда пора
Пока playbook умещается на пару экранов, роли не нужны. Когда он разрастается или одни и те же куски повторяются в нескольких проектах, их выносят в роли: папку со стандартной структурой, где отдельно лежат задачи, обработчики, шаблоны и значения по умолчанию. Заготовку создаёт команда ansible-galaxy role init nginx, после чего в playbook остаётся строчка roles: [nginx].
Готовые роли и коллекции можно брать из Ansible Galaxy, и для типовых вещей вроде Docker или PostgreSQL там есть хорошо поддерживаемые варианты. Проверяй дату последнего обновления и число открытых проблем, и фиксируй версии в файле requirements.yml, иначе через полгода ansible-galaxy install притащит новую мажорную версию роли с другими переменными. Роль из Galaxy выполняется с правами root на всех твоих серверах, так что это ровно тот случай, когда стоит хотя бы пролистать её tasks/main.yml, прежде чем запускать.
Скорость
С настройками по умолчанию Ansible на каждый шаг копирует на сервер временный файл с модулем и потом удаляет его. Строка pipelining = True из нашего ansible.cfg убирает эту пересылку, и на playbook из пары десятков задач разница видна невооружённым глазом. По умолчанию она выключена из-за старой настройки sudo requiretty, которой в Ubuntu и Debian нет, так что включать можно смело.
Параллельно Ansible работает максимум с пятью серверами (параметр forks). Для небольшого парка этого хватает. Сбор фактов в начале каждого прогона занимает несколько секунд на хост; если playbook ими не пользуется, строка gather_facts: false экономит это время.
Частые ошибки и что они означают
| Что видишь | В чём обычно дело |
|---|---|
UNREACHABLE, Permission denied (publickey) | Ansible заходит не тем пользователем или не с тем ключом. Проверь ansible_user в инвентаре и помни, что -u его не перебивает |
Host key verification failed | Первое подключение к серверу или сервер пересоздан на том же IP |
Missing sudo password | У пользователя sudo с паролем. Запусти с -K или настрой NOPASSWD |
couldn't resolve module/action | Установлен только ansible-core без нужной коллекции |
Conditional result was ... must have a boolean result | Условие из старого примера, см. раздел про новые версии |
| Ошибка блокировки apt на новом сервере | Работает фоновое обновление после первой загрузки. Увеличь lock_timeout или подожди пару минут |
| Предупреждение про world writable directory | Проект лежит на диске Windows в WSL, конфиг проигнорирован |
Задача с shell всегда changed | Ansible не знает результата команды. Добавь creates или changed_when |
Для любой непонятной ошибки полезен запуск с -vvv: Ansible покажет, какой командой подключается по SSH и что именно вернул сервер. Вывод длинный, но нужная строка там почти всегда есть.
Чем Ansible не является
Ansible настраивает серверы, которые уже существуют. Создавать сами VPS у провайдера через API он в принципе умеет, но для этого обычно берут Terraform или OpenTofu, а Ansible подключается следом. Он же не заменяет контейнеры: если приложение упаковано в Docker, Ansible отлично ставит Docker, кладёт на сервер compose.yaml и запускает его, но описывать сам набор контейнеров удобнее в Docker Compose, а основы есть в статье о Docker на VPS.
Кстати, с панелями управления Ansible тоже уживается плохо, хотя причина тут другая. Панель хранит свою картину сервера в собственной базе и переписывает конфиги nginx или PHP при каждом изменении через веб-интерфейс, а Ansible на следующем прогоне возвращает свою версию. Кто победит, зависит от того, кто запустился последним, и так можно бодаться бесконечно. Сервер под панелью лучше либо целиком отдать панели, либо обойтись без неё. Сравнение панелей есть в обзоре панелей управления для VPS.
И последнее: Ansible не следит за сервером между запусками. Если кто-то поправил конфиг руками, Ansible узнает об этом только на следующем прогоне, и то лишь для тех файлов, которые он сам ведёт. Поэтому playbook ценен, пока ты им пользуешься: как только правки пошли мимо него, через пару месяцев описание и реальный сервер расходятся настолько, что запускать playbook становится страшно. Привычка, которая это лечит, скучная: любое изменение сначала вносится в playbook и только потом прогоняется. Когда проектов станет больше, playbook удобно держать в Git и запускать из CI/CD, но это уже следующий шаг, а для начала хватит папки на ноутбуке и привычки не лезть на сервер руками.
Источники
- Installing Ansible – Ansible Community Documentation
- Releases and maintenance – Ansible Community Documentation
- Ansible-core 2.19 Porting Guide
- Ansible-core 2.20 Porting Guide
- Ansible Configuration Settings
- How to build your inventory – Ansible Community Documentation
- Using variables: variable precedence – Ansible Community Documentation
- Protecting sensitive data with Ansible vault
- ansible.builtin.apt module
- community.general.ufw module
- ansible.posix.authorized_key module
- sshd_config(5) – OpenSSH manual
- Пакет ansible-core в релизах Ubuntu
- Пакет ansible-core в релизах Debian
- pipx – документация

