Минимальный Playbook для обновления серверов (Ansible, Ubuntu 24.04+)
Короткая «заметка» для BookStack: готовые playbook’и и команды. Работает как с ansible-core из APT, так и с полным ansible из pipx/PPA. Предполагается, что у тебя уже есть проект с ansible.cfg и инвентори inventories/inventory.ini (из прошлой заметки).
Предпосылки
-
В проекте уже есть
ansible.cfgиinventories/inventory.ini. -
На целевых хостах установлен Python 3.
-
Для Arch нужен модуль
community.general.pacman:ansible-galaxy collection install community.general -
Файл инвентори используйте реальный (частая ошибка — неверный путь).
Быстрая проверка окружения
ansible --version
ansible all -i inventories/inventory.ini --list-hosts
1) Простой плейбук (Debian/Ubuntu)
Файл: playbooks/simple-update.yml
---
# Обновляет Debian/Ubuntu-хосты и при необходимости перезагружает.
- name: Simple update for Debian/Ubuntu
hosts: all
become: yes # Выполнять с sudo
gather_facts: yes # Нужны факты (ansible_os_family и др.)
tasks:
# Обновляем кэш пакетного менеджера (только на семействах Debian/Ubuntu).
- name: Update cache
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600 # Если кэш свежее 1 часа, не обновлять
when: ansible_os_family == "Debian"
# Делаем полноценное обновление + чистку.
- name: Upgrade packages
ansible.builtin.apt:
upgrade: dist # Эквивалент apt-get dist-upgrade
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
# Стандартный флаг ребута на Ubuntu (/run/reboot-required).
- name: Check if reboot is required
ansible.builtin.stat:
path: /run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
# Перезагружаем только если флаг существует.
- name: Reboot if required
ansible.builtin.reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 600
pre_reboot_delay: 60 # <60 сек на Linux округляется до 0 → ставим 60
when: ansible_os_family == "Debian" and reboot_required.stat.exists
# Ждём восстановления SSH (дополнительно; модуль reboot сам ждёт).
- name: Wait for host after reboot (optional)
ansible.builtin.wait_for_connection:
timeout: 600
delay: 20
when: ansible_os_family == "Debian" and reboot_required.stat.exists
Запуск:
ansible-playbook -i inventories/inventory.ini playbooks/simple-update.yml
2) Универсальный плейбук (Debian/Ubuntu, RHEL/CentOS/Alma/Rocky/Fedora, Arch)
Файл: playbooks/update-all-systems.yml
---
# Последовательно обновляет хосты разных семейств, по одному за раз (serial: 1).
- name: Update all Linux servers (multi-distro)
hosts: all
become: yes
gather_facts: yes
serial: 1 # Безопаснее на проде: обновляем по одному хосту
tasks:
# --- Debian/Ubuntu ---
# Обновление кэша APT.
- name: Update cache (Debian/Ubuntu)
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
# Полное обновление + чистка.
- name: Upgrade packages (Debian/Ubuntu)
ansible.builtin.apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
# Проверяем флаг необходимости ребута.
- name: Check if reboot required (Debian/Ubuntu)
ansible.builtin.stat:
path: /run/reboot-required
register: debian_reboot_required
when: ansible_os_family == "Debian"
# --- RHEL/CentOS/Fedora ---
# Утилита needs-restarting доступна через yum-utils (RHEL) / dnf-plugins-core (Fedora).
- name: Ensure needs-restarting is available (RHEL-like)
ansible.builtin.package:
name:
- yum-utils
- dnf-plugins-core
state: present
when: ansible_os_family == "RedHat"
ignore_errors: yes # На старых/минимальных системах пакеты могут различаться
# Обновление всех пакетов.
- name: Update cache and upgrade (RHEL-like)
ansible.builtin.yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == "RedHat"
# Проверяем, нужен ли ребут (RC=1 → требуется перезагрузка).
- name: Check if reboot required (RHEL-like)
ansible.builtin.command: needs-restarting -r
register: rhel_reboot_required
failed_when: false # Не падать, если команды нет/ошибка
changed_when: false
when: ansible_os_family == "RedHat"
# --- Arch Linux ---
# Полное обновление через pacman (нужна коллекция community.general).
- name: Update packages (Arch)
community.general.pacman:
update_cache: yes
upgrade: yes
when: ansible_os_family == "Archlinux"
# --- Unified reboot logic ---
# Перезагрузка Debian/Ubuntu при наличии флага.
- name: Reboot if required (Debian/Ubuntu)
ansible.builtin.reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 60
when: ansible_os_family == "Debian" and debian_reboot_required.stat.exists
# Перезагрузка RHEL-подобных, если needs-restarting вернул RC=1.
- name: Reboot if required (RHEL-like)
ansible.builtin.reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 60
when: ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1
# Ожидаем восстановление подключения (только если ребут был).
- name: Wait for host after reboot (when reboot happened)
ansible.builtin.wait_for_connection:
timeout: 900
delay: 20
when: >
(ansible_os_family == "Debian" and debian_reboot_required.stat.exists) or
(ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1)
Запуск:
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml
3) «Безопасное» обновление с пред-чеками и «бэкапом» списка пакетов
Файл: playbooks/secure-update.yml
---
# Делает базовые проверки (нагрузка/свободное место), сохраняет список пакетов,
# обновляет систему и перезагружает при необходимости.
- name: Secure update with pre-checks
hosts: all
become: yes
gather_facts: yes
vars:
max_load_1min: 4.0 # Порог средней нагрузки за 1 минуту
min_free_space_mb: 512 # Минимум свободного места на каждом разделе
backup_before_update: true # Сохранять список пакетов до обновления
backup_dir: "/var/backups/preupdate-{{ ansible_date_time.date }}"
tasks:
# Проверяем, что нагрузка не слишком высокая (чтобы обновление не усугубляло ситуацию).
- name: Fail if 1min load too high
ansible.builtin.assert:
that:
- (ansible_loadavg['1'] | float) <= max_load_1min
fail_msg: "System load too high: {{ ansible_loadavg['1'] }}"
# Прерываемся, если на каком-либо разделе слишком мало места.
- name: Fail if any mount has too little free space
ansible.builtin.fail:
msg: "Low space on {{ item.mount }}: {{ (item.size_available // 1024 // 1024) }} MB"
loop: "{{ ansible_mounts }}"
loop_control:
label: "{{ item.mount }}"
when: (item.size_available // 1024 // 1024) < min_free_space_mb
# Директория для «бэкапа» списка пакетов.
- name: Create backup directory
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
when: backup_before_update
# Сохраняем список установленных пакетов на Debian/Ubuntu.
- name: Save package list (Debian/Ubuntu)
ansible.builtin.shell: dpkg --get-selections > "{{ backup_dir }}/packages.txt"
args: { warn: false }
when: ansible_os_family == "Debian" and backup_before_update
# Сохраняем список установленных пакетов на RHEL-подобных.
- name: Save package list (RHEL-like)
ansible.builtin.shell: rpm -qa > "{{ backup_dir }}/packages.txt"
args: { warn: false }
when: ansible_os_family == "RedHat" and backup_before_update
# Обновляем Debian/Ubuntu (кэш → обновление → чистка).
- name: Update cache / packages (Debian/Ubuntu)
block:
- ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
- ansible.builtin.apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
# Обновляем RHEL-подобные.
- name: Update cache / packages (RHEL-like)
ansible.builtin.yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == "RedHat"
# Проверяем необходимость ребута на Debian/Ubuntu.
- name: Check if reboot required (Debian/Ubuntu)
ansible.builtin.stat:
path: /run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
# Проверяем необходимость ребута на RHEL-подобных (RC=1 → нужен ребут).
- name: Check if reboot required (RHEL-like)
ansible.builtin.command: needs-restarting -r
register: rhel_reboot_required
failed_when: false
changed_when: false
when: ansible_os_family == "RedHat"
# Перезагружаем, если необходимо.
- name: Reboot if required
ansible.builtin.reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 60
when: >
(ansible_os_family == "Debian" and reboot_required.stat.exists) or
(ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1)
# Ждём возврата соединения после ребута.
- name: Wait for host after reboot
ansible.builtin.wait_for_connection:
timeout: 900
delay: 20
when: >
(ansible_os_family == "Debian" and reboot_required.stat.exists) or
(ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1)
Запуск (пример без «бэкапа»):
ansible-playbook -i inventories/inventory.ini playbooks/secure-update.yml -e "backup_before_update=false"
4) Обновление с простым логированием
Файл: playbooks/update-with-logging.yml
---
# Записывает начало/окончание обновления и помечает, что требуется ребут.
- name: Update with simple logging (cross-distro)
hosts: all
become: yes
gather_facts: yes
vars:
log_file: "/var/log/ansible-updates.log" # Локальный лог на целевом хосте
tasks:
# Фиксируем старт.
- name: Start log
ansible.builtin.lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Start update on {{ inventory_hostname }}"
create: yes
# Debian/Ubuntu: кэш + обновление + чистка.
- name: Update & upgrade (Debian/Ubuntu)
block:
- ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
- ansible.builtin.apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
# RHEL/CentOS/Alma/Rocky/Fedora: обновление всех пакетов.
- name: Update & upgrade (RHEL-like)
ansible.builtin.yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == "RedHat"
# Arch: полное обновление через pacman.
- name: Update & upgrade (Arch)
community.general.pacman:
update_cache: yes
upgrade: yes
when: ansible_os_family == "Archlinux"
# Фиксируем завершение.
- name: Log completion
ansible.builtin.lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Update completed on {{ inventory_hostname }}"
# Проверяем флаги ребута для последующей отметки в логе.
- name: Check reboot flag (Debian/Ubuntu)
ansible.builtin.stat:
path: /run/reboot-required
register: debian_reboot_required
when: ansible_os_family == "Debian"
- name: Check reboot flag (RHEL-like)
ansible.builtin.command: needs-restarting -r
register: rhel_reboot_required
failed_when: false
changed_when: false
when: ansible_os_family == "RedHat"
# Логируем необходимость перезагрузки.
- name: Log reboot requirement
ansible.builtin.lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Reboot required on {{ inventory_hostname }}"
when: >
(ansible_os_family == "Debian" and debian_reboot_required.stat.exists) or
(ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1)
Полезные варианты запуска
# Простой запуск
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml
# Dry-run (ничего не меняет, показывает план)
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml --check --diff
# Только определённая группа или хост
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml --limit webservers
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml --limit n-1-dsw
# Подробный вывод
ansible-playbook -i inventories/inventory.ini playbooks/update-all-systems.yml -vvv
Ручной «план Б» (без Ansible)
Debian/Ubuntu
sudo apt-get update
sudo apt-get dist-upgrade -y
sudo apt-get autoremove -y
sudo apt-get autoclean -y
test -f /run/reboot-required && sudo reboot
RHEL/CentOS/Alma/Rocky/Fedora
sudo dnf -y install dnf-plugins-core || sudo yum -y install yum-utils
sudo dnf -y upgrade || sudo yum -y update
sudo needs-restarting -r; [ "$?" -eq 1 ] && sudo reboot
Arch
sudo pacman -Syu
# ребут — по вашей политике (например, после обновления ядра)
Мини-шпаргалка по структуре проекта
~/ansible-projects/
├─ ansible.cfg
├─ inventories/
│ └─ inventory.ini
├─ playbooks/
│ ├─ simple-update.yml
│ ├─ update-all-systems.yml
│ ├─ secure-update.yml
│ └─ update-with-logging.yml
└─ roles/
Готово. Эти четыре плейбука закрывают быстрые апдейты, мульти-дистрибутив, «безопасный» режим и логирование; остальное (уведомления, расписание) можно добавить отдельно.
