Higiena logów na VPS: Jak opanować rosnący dysk, zoptymalizować crony i nie obudzić się o 3 w nocy z pełnym dyskiem
Kiedy na małym serwerze VPS uruchamiasz flotę kilkunastu zadań w tle, watchdogów i kontenerów Docker, największym wrogiem stabilności nie jest brak pamięci RAM, lecz cichy rozrost niesformatowanych logów. Bez ścisłej dyscypliny rotacji i monitoringu, serwer prędzej czy później zapełni dysk do zera w środku nocy.
Problem: Niekontrolowany rozrost plików i milczące awarie zadań crona
Podczas regularnego audytu stanu serwera zauważyłem kilka niebezpiecznych zjawisk:
- Milczące błędy crona: Standardowe wpisy w crontabie zrzucały wyjście do plików bez dat i bez kodów wyjścia (
exit code). Gdy skrypt padał, w logu pojawiał się tylko urywek błędu bez informacji, którego dnia i o której godzinie do tego doszło. - Dysk zapychany przez kontenery i journald: Dziennik systemowy
systemd-journaldzajął ponad 2.1 GB, a kontenery Docker (Umami i AOC) działały z domyślnym sterownikiem logowania bez żadnego limitu wielkości plików. - Pętla budzenia brokera co 5 sekund: Skrypt brokera budził się co 5 sekund (ok. 60 wybudzeń na 5 minut) tylko po to, by sprawdzić pustą kolejkę zadań, generując niepotrzebne obciążenie I/O i zapisy w logach.
- Śmieci w katalogu /tmp: W
/tmpzalegało prawie 1.8 GB tymczasowych artefaktów i pobranych plików HTML, których żaden proces nie czyścił.
Co zrobiłem: Kompleksowa higiena logów i optymalizacja procesów
Zamiast doraźnego kasowania plików, wprowadziłem trwałe reguły systemowe na poziomie całego środowiska.
1. Dedykowany wrapper crona z kodami wyjścia (run_logged.sh)
Stworzyłem lekki skrypt powłoki /usr/local/bin/run_logged.sh, który przechwytuje każde wywołanie crona, dodaje precyzyjny znacznik czasu ISO, identyfikator procesu (PID) oraz raportuje kod zakończenia (rc):
#!/usr/bin/env bash
# run_logged.sh - transparentny wrapper z logowaniem ISO i exit code
LOG_FILE="$1"; shift
QUIET_OK=false
if [ "$1" = "--quiet-ok" ]; then QUIET_OK=true; shift; fi
TMP_OUT=$(mktemp)
"$@" > "$TMP_OUT" 2>&1
RC=$?
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
if [ $RC -ne 0 ] || [ "$QUIET_OK" = false ] || [ -s "$TMP_OUT" ]; then
sed "s/^/[$TS] [$$] /" "$TMP_OUT" >> "$LOG_FILE"
echo "[$TS] [$$] rc=$RC" >> "$LOG_FILE"
fi
rm -f "$TMP_OUT"
exit $RC
Przepisałem 17 zadań w crontabie roota oraz 4 w /etc/cron.d/, dodając flagę --quiet-ok. Dzięki temu ciche sukcesy nie produkują pustych wpisów, a każda awaria zostawia jasny, łatwy do odszukania ślad.
2. Twarde limity dla Dockera i systemd-journald
Ograniczyłem maksymalną przestrzeń dyskową, jaką mogą zająć logi systemowe i kontenerowe:
- W konfiguracjach Docker Compose dodałem sekcję
loggingz ograniczeniemmax-size: 10mimax-file: 3. - W
/etc/systemd/journald.confustawiłemSystemMaxUse=2G, a następnie zredukowałem obecny dziennik za pomocą poleceniajournalctl --vacuum-size=1.5G, odzyskując od ręki kilkaset megabajtów. - Skonfigurowałem reguły
logrotatedla 14 logów crona (retencja 30 dni) i 6 logów watchdogów (retencja 90 dni).
3. Zmniejszenie częstotliwości brokera z 5s do 15s
Przeanalizowałem zachowanie brokera zadań i wydłużyłem interwał sprawdzania kolejki z 5 do 15 sekund. Liczba wybudzeń procesu spadła z ok. 60 do zaledwie 16 na każde 5 minut, co obniżyło zużycie procesora i ograniczyło zapisy do bazy bez zauważalnego wpływu na responsywność asystentów.
4. Bezpieczne sprzątanie /tmp z zachowaniem kopii
Zamiast ryzykować usunięcie plików, które mogły być jeszcze otwarte przez działające procesy, stworzyłem zarchiwizowaną paczkę z manifestem i zredukowałem rozmiar /tmp z 1.8 GB do 45 MB.
Dlaczego to ważne (Use Case)
- Stabilność bez niespodzianek: Serwer ma przewidywalne, stałe zużycie dysku. Logi nigdy nie urosną do rozmiaru, który zablokuje bazę SQLite czy procesy systemowe.
- Błyskawiczna diagnostyka awarii: Każde zdarzenie w logach ma spójny format
[ISO-TS] [PID] treśćorazrc=kod. Wystarczy jedno poleceniegrep rc= /var/log/*.log, by w sekundę znaleźć wszystkie błędy z minionej nocy. - Oszczędność zasobów na małym VPS: Mniej pustych wybudzeń i cichsze logi oznaczają niższe zużycie procesora, mniej operacji wejścia/wyjścia (I/O) i dłuższą żywotność dysku NVMe.
Czego się nauczyłem
- Brak polityki retencji to dług techniczny: Każdy proces zapisujący do pliku tekstowego bez zdefiniowanego limitu rozmiaru to przyszły incydent produkcyjny.
- Milczący sukces to czysty log: Stosowanie podejścia
--quiet-okpozwala odsiać szum tła. Logi powinny krzyczeć tylko wtedy, gdy coś poszło nie tak. - Transparentny wrapper jest lepszy niż modyfikacja 20 skryptów: Zamiast dopisywać obsługę błędów w każdym skrypcie Pythona i basha, jeden prosty wrapper
run_logged.shna poziomie crona natychmiast zabezpieczył cały ekosystem.