Минимальный Playbook для обновления серверов (Ansible, Ubuntu 24.04+)
Короткая «заметка» для BookStack: готовые playbook’и и команды. Работает как с ansible-core из APT, так и с полным ansible из pipx/PPA. Предполагается, что у тебя уже есть проект с ansible.cfg и инвентори inventories/inventory.ini (из прошлой заметки).
Быстрая проверка окружения
ansible --version
ansible all -i inventories/inventory.ini --list-hosts
Самый простой playbook (Debian/Ubuntu)
Файл: simple-update.yml
---
- name: Simple update for Debian/Ubuntu
hosts: all
become: yes
gather_facts: yes
tasks:
- name: Update cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Upgrade packages
apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
- name: Check if reboot is required
stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
- name: Reboot if required
reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 600
pre_reboot_delay: 10
when: ansible_os_family == "Debian" and reboot_required.stat.exists
- name: Wait for host after reboot
wait_for_connection:
timeout: 600
delay: 20
when: ansible_os_family == "Debian" and reboot_required.stat.exists
Запуск:
ansible-playbook -i inventories/inventory.ini simple-update.yml
Универсальный playbook (Debian/Ubuntu, RHEL/CentOS/Fedora, Arch)
Файл: update-all-systems.yml
---
- name: Update all Linux servers (multi-distro)
hosts: all
become: yes
gather_facts: yes
serial: 1 # по одному хосту за раз (безопаснее на проде)
tasks:
# --- Debian/Ubuntu ---
- name: Update cache (Debian/Ubuntu)
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Upgrade packages (Debian/Ubuntu)
apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
- name: Check if reboot required (Debian/Ubuntu)
stat:
path: /var/run/reboot-required
register: debian_reboot_required
when: ansible_os_family == "Debian"
# --- RHEL/CentOS/Fedora ---
- name: Ensure needs-restarting is available (RHEL-like)
package:
name:
- dnf-utils
- yum-utils
- dnf-plugins-core
state: present
when: ansible_os_family == "RedHat"
ignore_errors: yes
- name: Update cache (RHEL-like)
yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == "RedHat"
- name: Check if reboot required (RHEL-like)
command: needs-restarting -r
register: rhel_reboot_required
failed_when: false
changed_when: false
when: ansible_os_family == "RedHat"
# --- Arch Linux ---
- name: Update packages (Arch)
pacman:
update_cache: yes
upgrade: yes
when: ansible_os_family == "Archlinux"
# --- Unified reboot logic ---
- name: Reboot if required (Debian/Ubuntu)
reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 10
when: ansible_os_family == "Debian" and debian_reboot_required.stat.exists
- name: Reboot if required (RHEL-like)
reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 10
when: ansible_os_family == "RedHat" and rhel_reboot_required.rc == 1
- name: Wait for host after reboot (when reboot happened)
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 update-all-systems.yml
«Безопасное» обновление с базовыми проверками
Файл: secure-update.yml
---
- name: Secure system update with basic safety checks
hosts: all
become: yes
gather_facts: yes
serial: 1
vars:
max_load_1min: 2.0
min_free_space_mb: 1000
backup_before_update: true
backup_dir: "/backup/pre-update-{{ ansible_date_time.epoch }}"
tasks:
- name: Check 1-minute loadavg
assert:
that:
- (ansible_loadavg['1'] | float) <= max_load_1min
fail_msg: "System load too high: {{ ansible_loadavg['1'] }}"
# ansible_loadavg приходит из gather_facts
- name: Fail if any mount has too little free space
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
file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
when: backup_before_update
- name: Save package list (Debian/Ubuntu)
shell: dpkg --get-selections > "{{ backup_dir }}/packages.txt"
args:
warn: false
when: ansible_os_family == "Debian" and backup_before_update
- name: Save package list (RHEL-like)
shell: rpm -qa > "{{ backup_dir }}/packages.txt"
args:
warn: false
when: ansible_os_family == "RedHat" and backup_before_update
- name: Update cache / packages (Debian/Ubuntu)
block:
- apt:
update_cache: yes
cache_valid_time: 3600
- apt:
upgrade: dist
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
- name: Update cache / packages (RHEL-like)
yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == "RedHat"
- name: Check if reboot required (Debian/Ubuntu)
stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
- name: Check if reboot required (RHEL-like)
command: needs-restarting -r
register: rhel_reboot_required
failed_when: false
changed_when: false
when: ansible_os_family == "RedHat"
- name: Reboot if required
reboot:
msg: "Rebooting due to system updates"
reboot_timeout: 900
pre_reboot_delay: 15
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
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)
Логирование и уведомления (опционально)
Минимальный лог в файл на целевом хосте. Уведомления почтой работают при наличии локального MTA на контрол-ноде (используется delegate_to: localhost). Если MTA нет — проще интегрировать Telegram/SMTP через коллекции (community.general).
Файл: update-with-logging.yml
---
- name: Update with simple logging
hosts: all
become: yes
gather_facts: yes
vars:
log_file: "/var/log/ansible-updates.log"
tasks:
- name: Start log
lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Start update on {{ inventory_hostname }}"
create: yes
- name: Update cache (generic)
package:
update_cache: yes
become: yes
- name: Update all packages
package:
name: "*"
state: latest
register: update_result
- name: Log completion
lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Update completed on {{ inventory_hostname }}"
when: update_result is succeeded
- name: Check reboot flag (Debian/Ubuntu)
stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
- name: Log reboot requirement
lineinfile:
path: "{{ log_file }}"
line: "{{ ansible_date_time.iso8601 }} - Reboot required on {{ inventory_hostname }}"
when: ansible_os_family == "Debian" and reboot_required.stat.exists
Для e-mail/Telegram: подключи коллекцию и соответствующий модуль (например,
community.general.mailс параметрами SMTP,community.general.telegram), затем добавь задачуdelegate_to: localhost.
Полезные варианты запуска
# Простой запуск
ansible-playbook -i inventories/inventory.ini update-all-systems.yml
# Dry-run (ничего не меняет, показывает план)
ansible-playbook -i inventories/inventory.ini update-all-systems.yml --check --diff
# Только определённая группа
ansible-playbook -i inventories/inventory.ini update-all-systems.yml --limit webservers
# Подробный вывод
ansible-playbook -i inventories/inventory.ini update-all-systems.yml -vvv
# Передача переменных
ansible-playbook -i inventories/inventory.ini secure-update.yml -e "backup_before_update=false"
Частые нюансы и быстрые решения
-
Reboot на RHEL-подобных: команда
needs-restarting -rможет отсутствовать. В playbook’е выше мы ставимdnf-utils/yum-utils/dnf-plugins-coreи игнорируем ошибку — этого достаточно, чтобы на поддерживаемых системах появился инструмент. -
Порядок обновлений: для прод-групп уместно
serial: 1илиserial: 20%иmax_fail_percentage: 0. -
Почта: модуль
mailтребует локальныйsendmailна контрол-ноде. Для SMTP используйcommunity.general.mail(нужна коллекция). -
Arch: перезагрузка не определяется автоматически — решай по политике (например, всегда перезагружать по расписанию или проверять обновление ядра).
Мини-шпаргалка по структуре проекта
~/ansible-projects/
├─ ansible.cfg
├─ inventories/
│ └─ inventory.ini
├─ playbooks/
│ ├─ simple-update.yml
│ ├─ update-all-systems.yml
│ ├─ secure-update.yml
│ └─ update-with-logging.yml
└─ roles/ (по мере роста)
Готово
Эти четыре playbook’а покрывают быстрые обновления, мульти-дистрибутив, «безопасный» режим и базовое логирование. Дальше можно вынести логику в роли, добавить уведомления (Telegram/SMTP) и интегрировать в расписание (cron/systemd timers).
