This commit is contained in:
2026-07-12 08:44:40 +02:00
commit 55991dcb4e
21 changed files with 610 additions and 0 deletions

139
readme.md Normal file
View File

@@ -0,0 +1,139 @@
# 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:
```bash
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
```bash
# 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:
```bash
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` (katalog `chmod 700`, plik `chmod 600`).
Katalog `ssh_keys/` jest w `.gitignore` — klucz nigdy nie trafia do repozytorium.
Ścieżka do niego jest ustawiona centralnie w `group_vars/all.yml`
(`ansible_ssh_private_key_file: "{{ playbook_dir }}/../ssh_keys/id_rsa"`), więc
wszystkie playbooki i `cron/run-autopilot.sh` korzystają z niej automatycznie —
nie trzeba nic dodatkowo konfigurować.
- **`api_key`/`api_secret`/`api_url` PatchMon** leżą w `patchmon_inventory.yml`, który
jest w `.gitignore` — nigdy nie trafia do repozytorium. Wersja do repo to
`patchmon_inventory.yml.example` (same placeholdery, bez sekretów). Przy zakładaniu
projektu od zera: `cp patchmon_inventory.yml.example patchmon_inventory.yml` i
uzupełnij `api_url`/`api_key`/`api_secret`.
(Plugin `patchmon.dynamic_inventory` wczytuje 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.)