Skip to main content

Минимальный Playbook для обновления серверов (Ansible, Ubuntu 24.04+)

image.png

Короткая «заметка» для 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 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
          # RHEL 7+/CentOS
          - dnf-plugins-core   # Fedora/новые DNF
        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/UbuntuUbuntu: кэш + обновление + чистка.
    - 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/FedoraAlma/Rocky/Fedora: обновление всех пакетов.
    - name: Update & upgrade (RHEL-like)
      ansible.builtin.yum:
        name: "*"
        state: latest
        update_cache: yes
      when: ansible_os_family == "RedHat"

    # ArchArch: полное обновление через 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/

Готово. Эти четыре плейбука закрывают быстрые апдейты, мульти-дистрибутив, «безопасный» режим и логирование; остальное (уведомления, расписание) можно добавить отдельно.