Najdroższy czasowo moment pierwszego wdrożenia, więc zapisany wprost.
FAKT: eksport poprawiony (mapall_user), usługa NFS zrestartowana, pod
zrestartowany przez `rollout restart` — a zapis z poda nadal odbijał się
o Permission denied. Jednocześnie ręczne zamontowanie TEGO SAMEGO eksportu
z TEGO SAMEGO węzła pozwalało pisać.
HIPOTEZA (niepotwierdzona u źródła): jądro współdzieli strukturę montowania NFS
między montowania tego samego eksportu na węźle, razem z cache odpowiedzi ACCESS.
`rollout restart` zostawia stary i nowy pod na chwilę współistniejące — widać to
w jego własnym logu jako „1 old replicas are pending termination" — więc
montowanie ani na moment nie zostaje bez użytkownika i nowy pod dziedziczy
odpowiedź sprzed zmiany.
ROZWIĄZANIE (zweryfikowane): zejść do zera replik, poczekać na zniknięcie podów,
wrócić do jednej. W ArgoCD ten sam skutek daje force delete poda.
Dopisany też test rozdzielający winę serwera od winy klienta: ręczny montaż
z węzła z pominięciem Kubernetesa. SERWER-OK przy jednoczesnej odmowie w podzie
znaczy, że na NAS-ie nie ma czego poprawiać — i oszczędza rundy zmian
w konfiguracji udziału, które niczego nie dają.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runbook kazał robić `chown apps:apps` na użytkownika, którego na tym NAS-ie nie
ma, i nie tłumaczył, DLACZEGO zapis się nie udaje. A przyczyna jest myląca:
kontener działa jako root, NFS domyślnie stosuje root_squash, więc root z klienta
ląduje jako `nobody` — czyli w kategorii „inni", dla której świeży dataset
(drwxrwx--- root root) nie daje żadnych praw. `ls` w podzie pokazuje przy tym
„root root", co sugeruje, że wszystko jest w porządku.
Teraz są dwa jawne warianty: prostszy (mapall_user: root, katalog bez zmian —
squash wyłączony dla TEGO JEDNEGO udziału, w którym leży jeden plik) i czystszy
(dedykowany użytkownik + chown). Dopisane, czego nie robić: chmod 777 zadziała,
ale uczyni katalog zapisywalnym dla każdego lokalnego użytkownika NAS-a, a leżą
tam hashe haseł.
Dołożona sekcja rozstrzygająca „jeśli mimo wszystko Permission denied": trzy
komendy pokazujące jednocześnie stronę kontenera, stronę eksportu i stronę
katalogu, plus opis zestawu, który działa.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Instrukcja kazała wołać sharing.nfs.create, co przy istniejącym udziale odbija się
komunikatem „Export conflict" — i zostawia człowieka bez następnego kroku.
Teraz najpierw query, potem create ALBO update, zależnie od wyniku.
Dołożony sprawdzian `exportfs -v`: zapisana konfiguracja udziału i stan eksportu
to dwie różne rzeczy, a „access denied by server" przy poprawnej liście hostów
najczęściej znaczy właśnie, że usługa nie przeładowała eksportów.
Tabela objawów rozróżnia cztery przypadki, które łatwo pomylić: brak praw, brak
wpuszczenia przez serwer, brak katalogu i negocjację wersji NFS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pod presentation nie wstawał: CreateContainerConfigError, „failed to create
subPath directory for volumeMount state".
MÓJ BŁĄD, NIE ZAGADKA. Udział z bazami jest wyeksportowany `ro: true`
z `root_squash` — ustawiłem to runbookiem DAN-25 i sam tam napisałem ostrzeżenie,
że po tej zmianie nikt nic nie wgra. Dokładając wolumen na konta zmieniłem
`readOnly` przy MONTOWANIU w podzie i uznałem sprawę za załatwioną, nie sprawdzając
eksportu po stronie serwera.
Przyczyna była zresztą podwójna i za każdym razem ta sama:
1. kubelet nie mógł utworzyć podkatalogu, bo udział jest tylko do odczytu,
2. a gdyby nawet mógł — kontenery działają jako root, a root_squash mapuje
roota na nobody, więc aplikacja i tak nie zapisałaby tam pliku kont.
ROZWIĄZANIE BEZ RUSZANIA DAN-25: osobny, mały udział /mnt/Tank1/astrololo-state,
zapisywalny, zawężony do tych samych trzech węzłów, z mapall_user na
nieuprzywilejowanego użytkownika (bezpieczniejsze niż no_root_squash, bo nie
oddaje roota). Udział z bazami ZOSTAJE tylko do odczytu.
Przy okazji znika subPath, czyli znika potrzeba, żeby kubelet cokolwiek zakładał —
katalog istnieje, bo jest korzeniem udziału.
Runbook README-stan-prezentacji.md zawiera test zapisu Z WĘZŁA do wykonania PRZED
wdrożeniem — dokładnie ten, którego zabrakło za pierwszym razem.
UWAGA: DAN-27 (zarządzanie plikami baz) uderzy w tę samą ścianę, bo wymaga zapisu
do udziału z BAZAMI. Ten commit tego nie rozwiązuje i celowo nie rusza DAN-25 —
to osobna decyzja, opisana na końcu runbooka.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ekran „Konta" zapisuje konta do pliku, więc prezentacja potrzebuje trwałego
miejsca — inaczej wszystkie konta znikałyby przy restarcie poda.
subPath, NIE cały udział: prezentacja dostaje wyłącznie własny podkatalog
`presentation-state` i nie widzi baz interpretacyjnych. Zamontowanie tu całego
/mnt/Tank1/astrololo dałoby jej wgląd w bazy i obeszłoby bokiem zamknięcie
dostępu z DAN-25.
Konto administracyjne zostaje w APP_USER/APP_PASSWORD z sekretu — celowo poza
plikiem, żeby nie dało się go skasować ani ograniczyć z aplikacji.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
updates image gitea/astrololo-data tag 'aec3f843' to 'baf4e0e3'
updates image gitea/astrololo-logic tag '8b6ecc72' to 'baf4e0e3'
updates image gitea/astrololo-presentation tag '8b6ecc72' to 'baf4e0e3'
updates image gitea/astrololo-data tag '40f5e459' to 'aec3f843'
updates image gitea/astrololo-logic tag '1be57a47' to 'aec3f843'
updates image gitea/astrololo-presentation tag '40f5e459' to 'aec3f843'
updates image gitea/astrololo-render tag 'latest' to 'aec3f843'
updates image gitea/astrololo-data tag 'e3114f3e' to '40f5e459'
updates image gitea/astrololo-logic tag '86a0f16f' to '40f5e459'
updates image gitea/astrololo-presentation tag 'e3114f3e' to '40f5e459'
updates image gitea/astrololo-data tag 'bc80745a' to 'e3114f3e'
updates image gitea/astrololo-logic tag 'bc80745a' to 'e3114f3e'
updates image gitea/astrololo-presentation tag 'bc80745a' to 'e3114f3e'
updates image gitea/astrololo-data tag '70c83cfc' to 'bc80745a'
updates image gitea/astrololo-logic tag '70c83cfc' to 'bc80745a'
updates image gitea/astrololo-presentation tag '70c83cfc' to 'bc80745a'
Wszystkie cztery usługi biegły na koncie `default` z AUTOMATYCZNIE montowanym
tokenem API Kubernetesa. Żadna z nich nie rozmawia z API klastra — sekrety dostają
przez `secretKeyRef`, który wstrzykuje kubelet, nie pod. Token był więc zbędny,
a leżał w każdym kontenerze jako gotowy punkt wyjścia do klastra dla kogoś, kto
przejmie proces (np. przez lukę w zależności).
`automountServiceAccountToken: false` w szablonie poda data/logic/presentation/
render. Zweryfikowane `kubectl kustomize` — pole trafia do `.spec.template.spec`,
nie do specu Deploymentu (tam byłoby ciche i bez efektu).
Zero wpływu na działanie: nic w kodzie nie woła API Kubernetesa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
updates image gitea/astrololo-data tag '4c1e7f88' to '70c83cfc'
updates image gitea/astrololo-logic tag '4c1e7f88' to '70c83cfc'
updates image gitea/astrololo-presentation tag '4c1e7f88' to '70c83cfc'
updates image gitea/astrololo-data tag 'dd32f7e8' to '4c1e7f88'
updates image gitea/astrololo-logic tag 'dd32f7e8' to '4c1e7f88'
updates image gitea/astrololo-presentation tag 'd3d9b365' to '4c1e7f88'
updates image gitea/astrololo-data tag '78af6d47' to 'dd32f7e8'
updates image gitea/astrololo-logic tag '7b435d42' to 'dd32f7e8'
updates image gitea/astrololo-presentation tag '7b435d42' to 'dd32f7e8'
The production bot now tracks conjurer-bot-deploy instead of conjurer-bot,
so it updates only when the conjurer CI promotes a build (commit message
contains [deploy]). Adds the image-updater 'deploy-bot' alias and the
kustomization images entry for it; DEPLOY-BOT.md documents the channel and
the one-time bootstrap. Test bot + librarian keep tracking every build.
Pairs with conjurer#20 (the CI promotion step).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pairs with conjurer#18: each bot advertises its own callback so the shared
librarian answers results/pongs back to the bot that asked - test bot
http://192.168.1.73:32442, deploy bot :32443. No CONJURER_MAIN_BOT
repointing needed for the librarian anymore; DEPLOY-BOT.md updated (only
the musician stays single-target).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A second Conjurer bot alongside the test one, sharing librarian/musician/
radio and the API key, differing only in:
* Discord token from the deploy-conjurer-netrc secret,
* distinct names/labels (deploy-bot) and NodePort 32443,
* /data on an NFS export (RWX) instead of a block PVC - so config/state
can be uploaded while it runs (copy onto the share) and backed up
concurrently.
deploy-bot-backup: a daily CronJob that mirrors /data and keeps 30 days of
dated snapshots of the critical small state (both memories, settings,
accident log, transcripts) on NFS. Production only - the test bot's
amnesia is fine. DEPLOY-BOT.md documents seeding, backup/restore, and the
librarian/musician callback routing (they push to one bot; repoint
CONJURER_MAIN_BOT to :32443 to feed this one).
kubectl kustomize builds cleanly (10 objects).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
updates image gitea/astrololo-data tag 'a0d1135d' to 'b36b3bee'
updates image gitea/astrololo-logic tag 'a0d1135d' to 'b36b3bee'
updates image gitea/astrololo-presentation tag 'a0d1135d' to 'b36b3bee'
The librarian now checkpoints an in-progress search on SIGTERM and exits
within CONJURER_LIBRARIAN_GRACEFUL_TIMEOUT (default 45s). Raise k8s
terminationGracePeriodSeconds to 60 so that graceful checkpoint isn't cut
short by SIGKILL - otherwise the long DB scan restarts instead of resuming.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
updates image gitea/astrololo-data tag '8ebce816' to 'a0d1135d'
updates image gitea/astrololo-logic tag '8ebce816' to 'a0d1135d'
updates image gitea/astrololo-presentation tag '8ebce816' to 'a0d1135d'
The work-queue OOM is fixed in code (bounded queue), but a deep search
still loads up to 15000 Crossref records and the accumulating result
JSONs into RAM. Give 2Gi of headroom so a big search isn't OOM-killed;
raise the request to 512Mi to match its real baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wyniki wyszukań (librarian) i eventy 'now playing' (musician) POST-owane do
bota ginęły, bo droga powrotna do bota była krucha:
* Service 'bot' był NodePort BEZ przypiętego nodePort -> k8s losował port z
30000-32767 przy każdym (od)tworzeniu Service, a musician/betoniarka z
Dockera adresują bota na sztywno http://192.168.1.73:32442. Rozjazd = każdy
POST leci w zamknięty port. Przypinam nodePort: 32442.
* Librarian jest w tym samym klastrze co bot, a mimo to szedł przez nodePort
węzła. Przełączam na DNS Service'u http://bot:5000 - odporne na
przetasowania nodePortu (nodePort zostaje tylko dla zewnętrznych z Dockera).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>