homelab-codex-ws/kb/audits/lustro-shipping-2026-07-16.md
oskar ff56a3430c feat(kb): 5 audytow/reconow -> kb/audits/ (type: audit, as_of)
czujniki-2026-07-30 (node-agent vs stability-agent)
lustro-shipping-2026-07-16 (event=dead prom=up, 1507 mismatchy)
prometheus-cutover-2026-07-06 (recon starego toru livenesci)
piha-slim-2026-07-02 (audyt odchudzania PIHA)
vps-stacki-2026-07-27 (audyt niezarzadzanych stackow na VPS)

ODSTEPSTWO OD RECONU — swiadome. Recon typowal te 5 plikow jako SPLIT
(audit+decision / audit+incident / audit+phase). Rozstrzygniecie 2 wprowadza
typ `audit` z polem as_of i mapuje kazdy z nich na JEDNA sciezke
kb/audits/<obszar>-<data>.md. Audyt jest spojna migawka stanu z konkretna
data — rozbicie go na "ustalenia" i "rekomendacje" rozerwaloby ten kontekst
i wymagaloby redakcji tresci, czego etap 2 zabrania. Zostaja w calosci.

Efekt: 29 SPLIT-ow z reconu realizowane jako 24 (10 service+runbook,
14 wielotypowych), 5 zamienionych na caloscowe dokumenty type: audit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00

12 KiB
Raw Blame History

okf type visibility status updated as_of links
0.1 audit private active 2026-07-16 2026-07-16

Recon: lustro event=dead prom=up — 1507 mismatchy w shadow-liveness.log (2026-07-16)

READ-ONLY recon. Zero zmian w kodzie/serwisach. Wszystkie czasy UTC (lokalny CEST = UTC+2). Źródła: /opt/homelab/logs/observer/shadow-liveness.log (VPS), /opt/homelab/events/lustro/ (VPS), Prometheus query_range up{node="lustro"} 07-14 12:00 → 07-16 12:15 step 60 s, docker logs/inspect node-agent na lustro (pimirror2, 100.99.85.73) i porównawczo na PIHA.

TL;DR — werdykt: (1) test + boot-race, shipping DZIAŁA

1507 wpisów node=lustro event=dead prom=up to dokładnie 2 epizody, oba wyjaśnione, żaden nie jest przerwą w shippingu eventów:

  • 1499 wpisów = wczorajszy kontrolowany test (stop node-agenta na żywym węźle) — tyle że node-agent stał 3 h 20 min (13:55:08 → 17:14:56), a nie ~15 min. Przy kadencji observera ~7,6 s daje to arytmetycznie 1499 wpisów. Prometheus przez całe okno widział up==1 (poprawnie — host żył). To jest zamierzony scenariusz z Etapu 1 recon, tylko dłuższy niż zapamiętany.
  • 8 wpisów = dzisiejszy poranny boot-race (04:30:51 → 04:31:47, ~56 s): po power-onie Prometheus zaliczył scrape szybciej, niż dotarł pierwszy poprawny event node-agenta. last_seen_age=25346s (~7 h) to wiek ostatniego eventu sprzed nocnego power-off 21:29, a nie długość mismatcha — samo okno dead/up trwało niecałą minutę.

Nocne 7 h (21:39 → 04:30) nie generuje wpisów, bo oba tory zgodnie mówią "dead/down". Nie ma recurring problemu shippingu — eventy płyną ~233/h (najświeższy dziś 12:19). Są za to dwa realne, chroniczne quirki opisane w C (log-noise rsync exit 23 od 06-11 + stale-timestamp pierwszego batcha po boocie przez fake-hwclock), oba nieblokujące i oba wzmacniające rekomendację cutoveru na Prometheus (E).

A. Rozkład czasowy 1507 wpisów

Klastrowanie po przerwach > 60 s między kolejnymi wpisami daje 2 epizody (suma 1499 + 8 = 1507 ✓):

ep okno (UTC) n czas trwania last_seen_age implikowany ostatni event
1 07-15 14:05:14 → 17:14:54 1499 3 h 10 min 606 s → 11 987 s (monotonicznie ↑) 07-15 13:55:08
2 07-16 04:30:51 → 04:31:47 8 56 s 25 297 s → 25 354 s 07-15 21:29:14
  • Ep1 = test. Pierwszy wpis dokładnie na granicy TTL-dead (606 s po ostatnim evencie 13:55:08). Kadencja ~7,6 s/wpis (cykl observera); 11 380 s / 7,6 ≈ 1497 ✓. Zero świeżych eventów w całym oknie (age rośnie liniowo bez resetu) i zero backlogu — po wznowieniu nie spłynęły żadne eventy ze stemplami 14:0017:14, czyli agent nie działał (nie "działał, ale nie wysyłał"). Granice z katalogu eventów na VPS: ostatni batch 13:55:08, pierwszy po przerwie 17:14:56, syntetyczny node_online 17:15:02. Test trwał 3 h 20 min, nie ~15 min — rozjazd z pamięcią operatora do weryfikacji (dane są jednoznaczne).
  • Ep2 = boot-race po nocnym power-off. 8 cykli × ~7,5 s = 56 s. Szczegóły i przyczyna wydłużenia w C2.
  • Log zawiera też (poza zakresem zagadki): 16× lustro fresh prom=down + 53× stale prom=down (wczorajszy power-off 21:3021:39 — znany wzorzec z analizy Etapu 2), analogiczne wpisy solaria oraz 1 wpis node=solaria event=dead prom=up (07-16 12:03:36, age 60 931 s) — pojedynczy cykl przy dzisiejszym power-onie solarii, ten sam boot-race co ep2, wygasł natychmiast (brak kolejnych wpisów do 12:19).

B. Korelacja z cyklem lustro i historią Prometheusa

up{node="lustro"} 07-14 12:00 → 07-16 12:15 (2896 próbek, step 60 s) ma dokładnie 4 przejścia — idealny nocny cykl, zero flappingu:

07-14 21:30 up=1→0   07-15 04:31 up=0→1   (noc 1: 7,0 h off)
07-15 21:30 up=1→0   07-16 04:31 up=0→1   (noc 2: 7,0 h off)
  • Okno ep1 (07-15 14:0517:14) leży w całości w up==1 → lustro żyło, a nie wysyłało eventów = definicja scenariusza testowego. Realny problem shippingu wyglądałby identycznie — rozstrzyga tu brak backlogu (A) + docker logs z lustro (C): agent był zatrzymany, nie "wysyłający w próżnię".
  • 7 h event=dead do 04:31 pokrywa się z up==0 (21:30 → 04:31) — czyli przez noc oba tory były zgodne (dead/down, zero mismatchy). Mismatch dead/up pojawia się wyłącznie w 56-sekundowym oknie na styku boot ↔ pierwszy event. Task-owa hipoteza "7 h okno event=dead prom=up" nie potwierdza się: 7 h to wiek stempla, nie długość rozbieżności.
  • Trwały log istnieje dopiero od 07-15 13:44 (deploy 9e7ed3e), więc wcześniejszych poranków w nim nie ma — dominacja lustro w liczniku (1507 vs 1 solaria) to artefakt tego, że test odbył się 21 min po włączeniu trwałego logu i trwał 3 h.

C. Stan shippingu na lustro (pimirror2)

Dostęp: klucz na pi@100.99.85.73 działa bez hasła (autoryzowany najpóźniej od wczorajszej sesji). Host: boot ~04:30 UTC (uptime 7 h 51 min o 12:21), System clock synchronized: yes, NTP active.

Kontener node-agent: Up (healthy), RestartCount=0, restart policy unless-stopped, bez OOM. Eventy płyną (najświeższy batch na VPS 12:19). Mounty: /home/pi/.ssh → /home/homelab/.ssh (ro), /opt/homelab (rw), docker.sock; kontener działa jako 1000:1000 = uid pibrak uid mismatch po stronie lustro (klucz czytelny).

C1. Chroniczny WARNING „Event shipping failed" — kosmetyczny, ale zaśmieca od 06-11

docker logs node-agent zawiera 34 325 wpisów Event shipping failed: … rsync: failed to set times on "/opt/homelab/events/lustro/.": Operation not permitted … (code 23)co cykl (~62 s), nieprzerwanie od 2026-06-11 (pierwszy wpis 19 s po starcie kontenera). Mimo WARNING pliki się przenoszą: exit 23 = "some files/attrs were not transferred" i dotyczy wyłącznie mtime katalogu docelowego. Przyczyna po stronie VPS: /opt/homelab/events/lustro/ należy do aerbot:aerbot (uid 1000), a rsync loguje się jako oskar (uid 1002, członek grupy aerbot) — może pisać pliki (group-write), nie może dotykać atrybutów katalogu. To ta sama klasa co na PIHA.

Fix już istnieje w repof37f85f (--omit-dir-times) + cc5c792 (--no-perms/--no-owner/--no-group), oba 2026-07-13, w services/node-agent/src/node_agent.py:648-671 — ale lustro działa na obrazie zbudowanym 2026-06-09 (kontener created 06-11), czyli sprzed fixa. Porównawczo PIHA: obraz z 07-13 21:17 (3 min po cc5c792), 0 błędów shippingu w ostatnich 24 h. To kolejna manifestacja buga deploy-node.sh bez --build (backlog c858dbc) — fix w repo, runtime na starym obrazie.

Ryzyko realne tego quirku to nie utrata eventów, lecz maskowanie: skoro każdy cykl od 35 dni loguje "failed", prawdziwa awaria shippingu wyglądałaby w logu identycznie (boy-who-cried-wolf).

C2. Fake-hwclock: pierwszy batch po boocie ma stempel sprzed power-off i jest cicho gubiony

Dowód z dzisiejszego poranka:

  • docker inspect node-agent: StartedAt=2026-07-15T21:30:34Z przy hoście zabootowanym dziś 04:30 — RPi wystartował z zegarem przywróconym przez fake-hwclock do wartości zapisanej przy wczorajszym shutdownie (21:30). Stąd też mylące docker ps „Up 15 hours" przy uptime hosta 7 h 51 min.
  • Batch evt-lustro-1784151037-* (stempel „07-15 21:30:37") jest poza kadencją (62 s: po 21:29:14 następny byłby ~21:30:16; 21:30:37 = StartedAt 21:30:34 + ~3 s startu agenta) → wyemitowany dziś ~04:30:37 realnego czasu, przy jeszcze niezsynchronizowanym zegarze.
  • Że nie istniał wczoraj wieczorem, dowodzi shadow-log: wczoraj o 21:39:12 observer liczył last_seen_age=598s → last_seen 21:29:14 (nie 21:30:37); dziś w ep2 o 04:31:47 age=25354s → last_seen nadal 21:29:14. Czyli batch dotarł na VPS dziś rano, ale observer nigdy go nie policzył: checkpoint lustro stał po syntetycznym node_offline (ts 21:39:20) > 21:30:37, więc timestampowy checkpoint (fix d5139c9) odrzucił go jako "starszy" — cichy drop. (Mtime pliku na VPS „21:30:37" niczego nie rozstrzyga — rsync -t przenosi mtime źródła.)
  • Skutek praktyczny dziś: ep2 wydłużony o ~45 s (gdyby pierwszy batch miał poprawny stempel, mismatch skończyłby się ~04:31:00, a nie po batchu 04:31:44). Skutek systemowy: każdego ranka pierwszy cykl node-agenta ściga się z NTP; przegrana = batch z 7-godzinnym wstecznym stemplem, na zawsze niewidzialny dla observera. Wczorajszy poranek (07-15 04:31) śladu stale-batcha nie ma — race bywa wygrywany. Uwaga: timedatectl na lustro pokazuje „RTC time" — czy istnieje fizyczny RTC (i czemu nie trzyma czasu) — do weryfikacji.

D. Werdykt: (1) — test + jednorazowy artefakt boot, NIE recurring bug shippingu

1507 = 1499 (test, 3 h 20 min) + 8 (boot-race, 56 s). Shipping eventów z lustro działa ciągle i dziś (233 batche/h, zero utraconych okien poza zatrzymanym agentem podczas testu). Nie ma dowodu na jakąkolwiek niezamierzoną przerwę shippingu po nocnym power-cycle.

Niuans do (1): recon ujawnił dwa chroniczne, nieblokujące defekty niższego rzędu, oba z gotowym lub znanym kierunkiem fixa:

  1. Log-noise rsync exit 23 (C1) — od 06-11, fix w repo od 07-13, niezdeployowany na lustro (stary obraz; bug deploy-node.sh --build).
  2. Stale-timestamp po boocie (C2) — fake-hwclock vs NTP race; dziś przegrany (batch zgubiony + ~45 s dłuższy dead/up), wczoraj wygrany. Klasyka motywu "nazwa/stempel ≠ tożsamość" z sesji 07-15: observer cicho dropuje zamiast kwarantannować.

E. Implikacja dla Etapu 3 — POTWIERDZONA, recon wzmacnia cutover

  • Prometheus w całym 48 h oknie ani razu się nie pomylił: up==1 przez cały test (host żył — racja), up==0 równo w oknach power-off (racja), up==1 w ≤60 s od porannego bootu (szybszy niż event-tor). Problem C1/C2 w ogóle nie dotyka toru Prometheusa.
  • Tor eventowy lustro jest strukturalnie kruchy wokół cyklu dobowego: (a) po boocie przegrywa wyścig z NTP i potrafi zgubić pierwszy batch, (b) jego wiarygodność zależy od działania node-agenta (test = 3 h 20 min fałszywego "dead" na żywym węźle — semantycznie poprawne, ale operacyjnie ślepe), (c) jego log błędów jest od 35 dni nieodróżnialny od realnej awarii. Po cutoverze na up{} każdy z tych trybów awarii przestaje wpływać na liveness lustro.
  • Wniosek zgodny z analizą Etapu 2: cutover lustro na Prometheus — GO; ten recon dostarcza dodatkowo brakujący w analizie 07-15 przypadek event=dead prom=up na dużej próbce (1499 cykli) z poprawnym werdyktem Prometheusa w 100% próbek.

Rekomendacje

  1. Redeploy node-agent na lustro z rebuildem obrazu (fix rsync już w repo; przy okazji wejdzie 4746ebe). Potwierdza też pilność fixa deploy-node.sh --build (backlog).
  2. Observer: kwarantanna zamiast cichego dropu eventów ze stemplem < checkpoint (dokładnie przypadek C2; spójne z istniejącym TODO walidacji node w treści vs katalog).
  3. Lustro: związać start node-agenta z synchronizacją czasu (np. warunek time-sync.target / sprawdzenie timedatectl przed pierwszą emisją) albo chrony makestep przed startem Dockera — eliminuje C2 u źródła. Wyjaśnić status RTC (do weryfikacji).
  4. Bez zmian dla planu Etapu 3 (kolejność solaria/lustro → vps/piha, mapping timestamp(up)compute_liveness).

Do weryfikacji (nie do ustalenia z dostępnych danych)

  • Dlaczego test 07-15 trwał 3 h 20 min zamiast ~15 min (przebieg po stronie operatora; dane wykluczają samoczynny restart — RestartCount=0, wznowienie 17:14:56 wygląda na ręczny docker start).
  • Czy lustro ma fizyczny RTC (timedatectl raportuje „RTC time"), a jeśli tak — czemu nie trzyma czasu przez noc.
  • Częstość przegrywania race'u NTP (C2): dziś tak, wczoraj nie; 1 próbka „tak" nie wystarcza do oceny częstości — obserwować poranki w trwałym logu (wpisy dead/up ~04:31 z age ~25 000 s).