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 --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).