Как настроить Ansible на Ubuntu 24.04: гайд
Установите Ansible через pipx на Ubuntu 24.04 и создайте первый playbook для защиты VPS. Решение ошибок Permission denied и проблем с sudo в коде.
Что вы создаете
Одна управляющая машина с установленным Ansible и один или несколько новых VPS на базе Ubuntu 24.04 с чистой стандартной ОС. В итоге у вас будет файл inventory с именами ваших серверов, ad-hoc команда ping для проверки сквозной аутентификации и playbook, который выполняет весь чек-лист настройки нового VPS в виде кода: создание пользователя для развертывания с вашим SSH key, настройка hardened sshd, fail2ban, unattended upgrades и firewall, который разрешает OpenSSH перед запретом всех остальных соединений. Вы можете использовать это как для одного сервера, так и для двадцати. Если запустить playbook дважды, второй запуск не внесет никаких изменений — в этом заключается основная цель.
За пятнадцать лет настройки VPS я заметил закономерность: все настраивают первые пять серверов вручную, а затем тратят целые выходные на шестом сервере, потому что никто не помнит, что именно было сделано на первых пяти. Это руководство расширяет тему управления несколькими Linux-серверами — начните изучение в тот день, когда поймаете себя на вводе одной и той же apt install в трех терминалах.
Что такое Ansible на самом деле (в одном абзаце)
Ansible не требует установки агентов. На управляемых серверах не нужно устанавливать демоны: управляющая машина подключается через стандартный SSH, копирует небольшой Python-модуль на целевой узел, выполняет его, считывает выведенный JSON и удаляет модуль. От целевого узла требуется только python3, который уже присутствует в стандартном образе Ubuntu. Ключевое понятие — идемпотентность. Это означает следующее: задача описывает состояние, а не действие. state: present для пакета означает «убедиться, что пакет установлен», а не «запустить установщик». Если состояние уже достигнуто, Ansible ничего не изменяет и сообщает о результате как ok вместо changed. Это свойство является основой продукта — именно оно делает повторный запуск playbook безопасным. Безопасные повторные запуски превращают shell-скрипты в инструменты управления инфраструктурой.
Предварительные требования и важные нюансы
- Управляющая машина: ваш ноутбук или небольшой VPS. Я подразумеваю Ubuntu 24.04; macOS работает идентично после установки pipx через Homebrew.
- Один или несколько целевых VPS под управлением Ubuntu 24.04 на KVM, доступных по root. На них ничего не устанавливается.
- Аутентификация по SSH key для каждого целевого узла. Ansible использует те же методы аутентификации, что и ваша команда
ssh— еслиssh root@hostзапрашивает пароль, Ansible завершит работу с ошибкой. - В Ubuntu 24.04
pip install ansibleзавершается с ошибкойerror: externally-managed-environment. Это намеренная политика дистрибутива, а не программная ошибка. Используйте pipx. - Пробелы в YAML являются частью синтаксиса. Неправильный отступ приводит к
mapping values are not allowed in this context, а использование символа табуляции приведет к критической ошибке. - Держите активную SSH-сессию открытой на каждом целевом узле, пока playbook выполняет настройку безопасности sshd. Все случаи блокировки доступа, в восстановлении которых я помогал клиентам, были связаны с закрытием последней сессии «для проверки в чистом окружении».
Шаг 1: Установите Ansible на управляющей машине с помощью pipx, а не pip
Обычно используют pip3 install ansible. На чистом образе 24.04, где ошибка возникает на раннем этапе — Command 'pip3' not found, but can be installed with: sudo apt install python3-pip — установка pip лишь создает дополнительную проблему:
pip3 install ansibleerror: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.В Ubuntu 24.04 системный Python помечен как управляемый извне (PEP 668), поэтому pip не может изменять те же файлы, что и apt. Не используйте --break-system-packages; название флага говорит само за себя. Правильный способ — использовать pipx. Он создает для Ansible изолированное виртуальное окружение и добавляет бинарные файлы в ваш PATH:
sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansibleПосле выполнения pipx ensurepath откройте новый терминал, чтобы изменения PATH вступили в силу. --include-deps — это не просто дополнение: пакет ansible не содержит собственных консольных скриптов — ansible, ansible-playbook и остальные являются точками входа зависимости ansible-core — поэтому без этого флага pipx выдаст ошибку No apps associated with package ansible or its dependencies. Устанавливайте пакет ansible, а не просто ansible-core. Полный пакет включает в себя коллекции сообщества, а данный плейбук использует модули из двух из них (ansible.posix и community.general).
ansible --versionПравильный результат начинается со строки вида ansible [core 2.19.x] и указывает версию Python; для текущих задач подходит любая актуальная версия. ansible: command not found означает, что ~/.local/bin еще не в вашем PATH — откройте новый терминал или используйте source ~/.bashrc.
Это все этапы установки. На целевых хостах ничего устанавливать не нужно.
Шаг 2: Доступ по SSH-ключу ко всем целевым хостам
ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20Затем проверьте доступ для каждого хоста:
ssh root@10.0.0.10 true && echo okЭта команда выполняет две задачи: подтверждает работу аутентификации по ключу без пароля и сохраняет ключ хоста в known_hosts. Выполните это сейчас, так как Ansible выводит запрос на подтверждение неизвестного ключа хоста в процессе выполнения. Это выглядит как зависание системы.
Шаг 3: инвентарь — сначала INI, затем YAML при масштабировании
Инвентарь — это текстовый файл со списком машин, с которыми может работать Ansible. Создайте inventory.ini в новой директории проекта:
[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20
[vps:vars]
ansible_user=rootweb1 — это выбранный вами псевдоним; он отображается в выводе и используется как цель для --limit web1. ansible_host — это реальный адрес. [vps] — это группа, а [vps:vars] задает переменные для каждого хоста в ней; ansible_user — это пользователь, под которым Ansible выполняет вход. Рядом укажите ansible.cfg, чтобы не вводить -i вручную:
[defaults]
inventory = inventory.iniAnsible считывает ansible.cfg из текущей директории. Тот же инвентарь в формате YAML — сохраните его как inventory.yml и укажите этот файл в ansible.cfg — станет предпочтительным, когда у каждого хоста будет много переменных:
vps:
hosts:
web1:
ansible_host: 10.0.0.10
web2:
ansible_host: 10.0.0.20
vars:
ansible_user: rootЭти форматы эквивалентны. INI удобнее для проверки двух серверов; YAML лучше масштабируется при работе с двадцатью хостами. Выберите один формат и не отвлекайтесь на выбор.
Шаг 4: ad-hoc команды — проверка работоспособности
ansible all -m pingЭто не ICMP. Модуль ping выполняет полную проверку: вход по SSH, копирование модуля, выполнение Python на целевой системе и очистка. Правильный результат — зеленый, по одному блоку на каждый хост:
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}Зеленый SUCCESS означает, что аутентификация, интерпретатор Python и транспорт работают корректно — playbook также будет работать. Красный UNREACHABLE! означает сбой транспорта до запуска модуля; описание конкретных ошибок и способы их устранения приведены в разделе ниже. Еще две полезные ad-hoc команды:
ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --becomeAd-hoc команды предназначены для разовых задач и проверок. Любые действия, которые вы планируете выполнять дважды, должны быть оформлены в виде playbook.
Шаг 5: первый playbook — checklist для нового VPS в виде кода
Здесь описаны все действия, которые вы выполняете вручную в первые десять минут работы с новым сервером. Сохраните файл как site.yml:
---
- name: Baseline a fresh Ubuntu VPS
hosts: vps
become: true
vars:
deploy_user: deploy
deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
baseline_packages:
- fail2ban
- unattended-upgrades
- ufw
baseline_services:
- fail2ban
- unattended-upgrades
tasks:
- name: Create the deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: sudo
append: true
shell: /bin/bash
- name: Install the deploy user's SSH key
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
key: "{{ deploy_pubkey }}"
- name: Passwordless sudo for the deploy user
ansible.builtin.copy:
dest: /etc/sudoers.d/deploy
content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Install baseline packages
ansible.builtin.apt:
name: "{{ baseline_packages }}"
state: present
update_cache: true
- name: Enable and start baseline services
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop: "{{ baseline_services }}"
- name: Harden sshd with a drop-in
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/00-hardening.conf
content: |
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
X11Forwarding no
mode: "0644"
validate: /usr/sbin/sshd -t -f %s
notify: Restart ssh
- name: Allow OpenSSH through ufw
community.general.ufw:
rule: allow
name: OpenSSH
- name: Enable ufw with default deny
community.general.ufw:
state: enabled
policy: deny
handlers:
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restartedСтроки, которые необходимо понять, а не просто скопировать:
Переменные находятся в секции vars: и вызываются через "{{ deploy_user }}" — заключайте все выражение в кавычки, если значение начинается с фигурной скобки, иначе YAML-парсер может прочитать его неверно. lookup('file', ...) считывает ваш публичный ключ с управляющей машины во время выполнения, поэтому playbook не содержит конфиденциальных данных.
Цикл. loop: "{{ baseline_services }}" выполняет задачу для сервиса один раз для каждого элемента; в выводе каждый элемент отображается с новой строки. Обратите внимание, что задача apt обрабатывает весь список пакетов целиком — одна транзакция apt работает быстрее, и это предпочтительный метод для пакетов; циклы используются для модулей, которые работают строго с одним объектом за раз.
Handler (обработчик) — это концепция, которую нужно усвоить. notify: Restart ssh не означает «перезапустить ssh прямо сейчас». Он ставит обработчик в очередь, который запускается один раз в конце play, и только если задача, вызвавшая его, вернула статус changed. Запустите playbook завтра: конфигурационный файл уже будет верным, задача копирования вернет ok, и sshd не будет перезапущен. Строка validate: служит предохранителем — sshd проверяет файл перед заменой старого, поэтому опечатка приведет к ошибке задачи, а не к поломке демона.
PermitRootLogin prohibit-password, а не no — намеренно. Этот playbook подключается под root с помощью ключа. prohibit-password отключает вход по паролю для root, сохраняя доступ для вашего ключа. После проверки пользователя развертывания (ssh deploy@10.0.0.10 sudo true — обычный адрес, так как web1 является псевдонимом, известным только Ansible), измените ansible_user=deploy в inventory и ужесточите настройки до no при следующем запуске. Усиливайте защиту в таком порядке, чтобы не потерять доступ к системе.
Префикс 00- имеет значение. Для большинства ключевых слов sshd учитывает первое встреченное вхождение. В Ubuntu файл sshd_config включает sshd_config.d/*.conf в лексическом порядке перед основным содержимым. Облачные образы Ubuntu 24.04 уже содержат 60-cloudimg-settings.conf в этой директории, а провайдеры, включающие вход по паролю через cloud-init, добавляют 50-cloud-init.conf с PasswordAuthentication yes; мы называем наш файл 00-hardening.conf, чтобы он шел первым при сортировке и имел приоритет над обоими.
Порядок задач — это безопасность сетевого экрана. Allow OpenSSH запускается перед Enable ufw с политикой deny. Ansible выполняет задачи строго в указанном порядке, поэтому «дыра» в безопасности создается до того, как возводится «стена». fail2ban не требует настройки для работы в данном сценарии; стандартные настройки Ubuntu уже обеспечивают мониторинг sshd, а то, как работают jail и что нужно настраивать, описано в руководстве по fail2ban на Ubuntu 24.04.
Шаг 6: пробный запуск с флагом --check, затем фактический запуск
ansible-playbook site.yml --checkРежим проверки (check mode) устанавливает соединение, вычисляет планируемые действия, но не вносит никаких изменений. Проверьте значение changed= в PLAY RECAP в нижней части вывода — это количество задач, которые изменят каждый хост. Важное ограничение: режим проверки не работает, если последующая задача зависит от изменений, внесенных предыдущей задачей. Стандартный образ Ubuntu Server содержит предустановленный ufw, поэтому данный playbook проходит проверку без ошибок. Однако на минимальном образе без этого пакета задачи ufw в режиме проверки завершатся ошибкой, так как пакет не был фактически установлен и модулю не к чему обращаться. Это ограничение режима пробного запуска, а не ошибка в вашем playbook. Если план выглядит корректно:
ansible-playbook site.ymlКаждая задача выводит одну строку для каждого хоста — желтым цветом changed, зеленым ok. Итоговый отчет должен выглядеть так:
PLAY RECAP *********************************************************************
web1 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Десять ok — это сбор данных, плюс восемь задач, плюс обработчик (handler). Ваш результат changed может отличаться от моего на один или два пункта: стандартный образ Ubuntu содержит ufw и unattended-upgrades, а service fail2ban запускается сразу после установки через apt, поэтому задача может легитимно сообщить ok при самом первом запуске — состояние уже считается достигнутым. Значения unreachable и failed должны быть равны нулю. Примечание по become: true: это формальность при подключении под root, но как только вы переключите ansible_user на deploy, sudo начнет работать. Файл sudoers с параметром NOPASSWD, который устанавливает этот playbook, предотвращает появление -K в командной строке. Без него вы получите Missing sudo password, описание которой приведено ниже.
Шаг 7: запустите команду дважды — пример идемпотентности
Сразу же запустите ту же команду повторно:
web1 : ok=9 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0 и ok уменьшились на единицу, так как обработчик (handler) не был вызван из-за отсутствия уведомления. Ничего не было переустановлено, sshd не перезапускался, ufw не изменялся. Это делает playbook не только средством настройки, но и средством аудита: добавьте web3 в inventory в следующем месяце и запустите его снова — новый сервер будет настроен, а старые серверы пройдут проверку. Ненулевое значение changed на сервере, который вы не изменяли, означает дрейф конфигурации (drift). Это указывает на то, что кто-то внес изменения вручную там, где изменения должны были вноситься через playbook.
Далее этот подход масштабируется. Следующий полезный playbook должен настраивать WireGuard VPN на том же VPS и ужесточать правила ufw так, чтобы SSH отвечал только через туннель; после этого можно написать playbook для установки Docker и Compose на каждый сервер приложений. Когда site.yml превысит объем в три экрана, разделите его на роли — но не раньше.
Режимы сбоев и соответствующие сообщения
UNREACHABLE with Permission denied.
web1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
"unreachable": true
}Ошибка SSH-транспорта произошла до запуска модулей: неверно указан ansible_user, ключ не был скопирован на этот хост или передается неверный ключ. Воспроизведите ошибку с помощью команды ssh root@10.0.0.10, затем выполните ssh -v, чтобы увидеть список предложенных ключей. Если вход по паролю через SSH работает, а Ansible — нет, значит, вы пропустили шаг ssh-copy-id.
Missing sudo password.
web1 | FAILED! => {
"msg": "Missing sudo password"
}Вы установили become: true и подключились под пользователем без прав root, которому требуется пароль для выполнения sudo. Добавьте -K (--ask-become-pass) в командную строку или предоставьте пользователю запись NOPASSWD в файле sudoers. Именно для этого playbook устанавливает такую запись для deploy перед переключением на этого пользователя.
error: externally-managed-environment. Вы запустили pip для системного Python в Ubuntu 24.04. Это описано в шаге 1: используйте pipx вместо pip и не используйте --break-system-packages.
mapping values are not allowed in this context.
ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this contextПричина почти всегда в отступах: ключ находится на неверном уровне вложенности или после двоеточия пропущен пробел. Указанный номер строки указывает рядом с ошибкой, а не на саму ошибку — проверьте строку выше. Похожая ошибка found character '\t' that cannot start any token означает наличие табуляции; YAML запрещает их. Сделайте проверку ansible-playbook site.yml --syntax-check привычкой перед каждым запуском и настройте редактор на использование двух пробелов для отступов в YAML.
/usr/bin/python3: not found. Редкая ошибка для стандартных образов Ubuntu 24.04, но частая для минимальных или netboot образов: выполнение модуля невозможно, так как на целевой системе отсутствует Python. Установите его с помощью модуля raw — это единственный модуль, которому не требуется ничего на удаленной стороне: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, затем перезапустите playbook.
FAQ
Do I need to install Ansible on the servers it manages?
No. Ansible is agentless: the control machine pushes small Python modules over SSH, runs them, and removes them. A target needs only python3 and SSH access, both of which stock Ubuntu images already have. The only install in this entire guide happens on your control machine.
Why does Ansible say "Permission denied (publickey)"?
The UNREACHABLE! block with Permission denied (publickey) means SSH authentication failed before Ansible ran anything. Check that ansible_user in the inventory matches the account you actually set up, that you ran ssh-copy-id to that host, and that plain ssh user@host logs in without a password. Whatever fixes the plain ssh command fixes Ansible, because they are the same transport.
What does idempotent mean in Ansible?
A task declares a desired state — "this package is present", "this line is in this file" — rather than an action to perform. If the state already holds, Ansible does nothing and reports ok instead of changed. That is why running a playbook twice shows changed=0 the second time, and why a rerun is a safe audit rather than a risky re-install.
Should I use pip or pipx to install Ansible on Ubuntu 24.04?
pipx. Ubuntu 24.04 marks the system Python as externally managed, so pip install ansible fails with error: externally-managed-environment by design. pipx install --include-deps ansible puts Ansible in an isolated virtualenv and exposes ansible, ansible-playbook, and the rest on your PATH cleanly.
What is the difference between the ansible and ansible-core packages?
ansible-core is the engine plus only the ansible.builtin modules. The ansible package bundles core with the curated community collections — including ansible.posix (the authorized_key module) and community.general (the ufw module), both used in this guide. Start with the full package; slim down to core plus hand-picked collections only when you have a reason to.