Ansible + PatchMon — zarządzanie flotą serwerów
Inwentarz jest pobierany dynamicznie z PatchMon (patchmon_inventory.yml, plugin
patchmon.dynamic_inventory). Grupy hostów pochodzą wprost z grup przypisanych
w PatchMon — filtrowanie na potrzeby playbooków (--limit) odbywa się przez te grupy,
nie przez ręczne listy hostów.
Pierwsze uruchomienie (świeży klon repozytorium)
Sekrety nie są trzymane w gicie — trzeba je uzupełnić lokalnie:
cp patchmon_inventory.yml.example patchmon_inventory.yml
# uzupełnij api_url / api_key / api_secret w patchmon_inventory.yml
mkdir -p ssh_keys && chmod 700 ssh_keys
cp /ścieżka/do/twojego/klucza ssh_keys/id_rsa && chmod 600 ssh_keys/id_rsa
Grupy PatchMon używane w tym projekcie
| Grupa | Znaczenie |
|---|---|
root |
Hosty logowane bezpośrednio na konto root. |
mgmt |
Hosty logowane na konto mgmt (z sudo). |
Update-now |
Wszystkie hosty dopuszczone do automatycznej aktualizacji (root ∪ mgmt). Domyślny cel dla crona/autopilota. |
Type-HOST |
Fizyczne hosty Proxmox — traktuj ostrożniej (restart hosta restartuje też jego VM/CT). |
Type-VM / Type-CT |
Maszyny wirtualne / kontenery LXC. |
Site-nni / Site-on |
Lokalizacje. |
Konto logowania (ansible_user) nie jest już podawane ręcznie przez -e — jest
ustawiane automatycznie przez group_vars/root.yml i group_vars/mgmt.yml na
podstawie grupy PatchMon danego hosta. Dzięki temu jedno uruchomienie playbooka na
grupie mieszanej (np. Update-now) obsłuży poprawnie oba typy kont.
Struktura projektu
ssh_keys/
id_rsa # klucz prywatny SSH (poza gitem, patrz sekcja "Sekrety")
group_vars/
all.yml # klucz SSH, become, próg dysku, lista kluczy admina
root.yml / mgmt.yml # ansible_user per grupa PatchMon
playbooks/
playbook_upgrade.yml # apt update + dist-upgrade
playbook_cleanup.yml # autoremove/autoclean, czyszczenie journala, alarm dysku
playbook_docker_cleanup.yml # czyszczenie nieużywanych obrazów Docker (tylko hosty z Dockerem)
playbook_fleet_report.yml # raport: OS, uptime, dysk, liczba aktualizacji, reboot-required
playbook_security_baseline.yml # unattended-upgrades + wyłączenie logowania hasłem SSH
playbook_ssh.yml # blokada pustych haseł SSH
playbook_ssh_keys.yml # zarządzanie kluczami publicznymi na koncie logowania
playbook_autopilot.yml # orkiestracja pod cron: upgrade -> cleanup -> fleet_report
templates/
50unattended-upgrades.j2
fleet_report.j2
cron/
run-autopilot.sh # wrapper do crona (flock + log)
crontab.example
ansible-autopilot.logrotate
reports/ # tu trafiają wygenerowane raporty floty
Użycie
# Aktualizacja pakietów na całej flocie dopuszczonej do auto-update
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_upgrade.yml --limit "Update-now"
# Sprzątanie/kondycja systemu (autoremove, journal, alarm zajętości dysku)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_cleanup.yml --limit "Update-now"
# Czyszczenie nieużywanych obrazów Docker (tylko hosty, na których jest Docker)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_docker_cleanup.yml --limit "Update-now"
# Raport stanu floty -> plik w reports/
# WAŻNE: dopisz ",localhost" do --limit, inaczej faza renderowania raportu
# zostanie pominięta ("skipping: no hosts matched").
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_fleet_report.yml --limit "Update-now,localhost"
# Baza bezpieczeństwa: unattended-upgrades + wyłączenie logowania hasłem SSH
# (dotyka sshd_config — uruchamiaj świadomie, ręcznie, nie z crona)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_security_baseline.yml --limit "Update-now"
# Blokada logowania na puste hasła
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_ssh.yml --limit "Update-now"
# Zarządzanie kluczami SSH konta logowania (uzupełnij admin_ssh_keys w group_vars/all.yml)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_ssh_keys.yml --limit "Update-now"
# Pełny przebieg autopilot ręcznie (to samo co robi cron)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_autopilot.yml --limit "Update-now,localhost"
Można też celować w węższe grupy, np. --limit "Site-on", --limit "Type-CT",
albo pojedynczy host: --limit "pve".
Reboot — tylko raportowanie, nigdy automatycznie
playbook_fleet_report.yml sprawdza /var/run/reboot-required na każdym hoście
i wypisuje listę hostów wymagających restartu w raporcie (reports/fleet_report_*.txt).
Żaden playbook w tym projekcie nie restartuje hostów automatycznie — decyzję
i moment restartu (szczególnie dla Type-HOST, czyli fizycznego Proxmoksa) podejmuje
operator ręcznie, po przejrzeniu raportu.
Bezobsługowe uruchamianie (cron)
cron/run-autopilot.sh to wrapper na playbook_autopilot.yml:
- blokuje się przez
flock, żeby nie nakładały się dwa równoległe przebiegi, - loguje przebieg (start/koniec/kod wyjścia) do
/var/log/ansible-autopilot.log, - domyślnie celuje w grupę
Update-now(nadpisywalne zmiennąTARGET_GROUP).
Instalacja:
chmod +x cron/run-autopilot.sh
crontab -e # wklej zawartość cron/crontab.example
sudo cp cron/ansible-autopilot.logrotate /etc/logrotate.d/ansible-autopilot
Każdy przebieg playbook_fleet_report.yml (w tym z crona) dokłada nowy plik do
reports/ — warto od czasu do czasu posprzątać stare raporty, np.
find reports/ -name 'fleet_report_*.txt' -mtime +30 -delete.
Sekrety
- Klucz prywatny SSH leży w
ssh_keys/id_rsa(katalogchmod 700, plikchmod 600). Katalogssh_keys/jest w.gitignore— klucz nigdy nie trafia do repozytorium. Ścieżka do niego jest ustawiona centralnie wgroup_vars/all.yml(ansible_ssh_private_key_file: "{{ playbook_dir }}/../ssh_keys/id_rsa"), więc wszystkie playbooki icron/run-autopilot.shkorzystają z niej automatycznie — nie trzeba nic dodatkowo konfigurować. api_key/api_secret/api_urlPatchMon leżą wpatchmon_inventory.yml, który jest w.gitignore— nigdy nie trafia do repozytorium. Wersja do repo topatchmon_inventory.yml.example(same placeholdery, bez sekretów). Przy zakładaniu projektu od zera:cp patchmon_inventory.yml.example patchmon_inventory.ymli uzupełnijapi_url/api_key/api_secret. (Pluginpatchmon.dynamic_inventorywczytuje ten plik bezpośrednio, bez templatowania Jinja2/Vault, dlatego to rozdzielenie na plik realny +.example, a nie np.{{ lookup('env', ...) }}, jest tu jedynym niezawodnym sposobem.)