Files
deploy/astrololo/README-postgres.md
T
gitea 99b9a0426f feat(astrololo): Postgres jako lustro baz w SQL + runbook (DAN-28)
Manifest, wpięcie w kustomization i runbook krok po kroku.

DECYZJE ZAPISANE W MANIFEŚCIE, ŻEBY NIE TRZEBA ICH BYŁO ODTWARZAĆ Z GŁOWY:

local-path, NIE NFS. Postgres zakłada semantykę blokad i fsync, której NFS nie
gwarantuje — to klasyczne źródło uszkodzenia bazy przy nagłym restarcie. Ceną
jest przywiązanie do węzła; przy luście odtwarzalnym z Excela to akceptowalne.

strategy: Recreate. Wolumen jest ReadWriteOnce, a dwa procesy Postgresa na jednym
katalogu danych to uszkodzona baza — rolling próbowałby wstać z nowym podem,
zanim stary zejdzie.

PGDATA w PODKATALOGU wolumenu: katalog główny potrafi zawierać wpisy systemu
plików, a initdb odmawia pracy w niepustym katalogu.

Wersja PRZYPIĘTA i poza image-updaterem: podbicie majora wymaga migracji katalogu
danych, więc nie może się zdarzyć samo, w nocy, przy okazji builda aplikacji.

C.UTF-8 zamiast pl_PL.UTF-8: dopasowanie tekstu robimy przez unaccent i pg_trgm,
nie przez collation, a pl_PL wymagałby obrazu z wygenerowanymi lokalizacjami.

DATA_PROVIDER zostaje na `excel`. Postgres można wdrożyć i obejrzeć BEZ ryzyka
dla działającego wyszukiwania; przełączenie to osobna, późniejsza decyzja.

SPRAWDZONE PRZED ODDANIEM: `kubectl kustomize astrololo` składa komplet 21
zasobów bez błędu, YAML parsuje się poprawnie, audyt odwołań potwierdza, że
jedynym brakującym sekretem jest astrololo-postgres (oba klucze), a DSN
postgresql+psycopg:// jest rozpoznawany przez SQLAlchemy z psycopg 3.

NIE SPRAWDZONE: skrypt inicjalizujący nie biegł przeciwko prawdziwemu Postgresowi
— na maszynie, na której to powstawało, nie ma Dockera. Zapisane wprost
w runbooku; krok 4 jest tam prawdziwym testem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 12:06:10 +02:00

11 KiB

Postgres — lustro baz w SQL (DAN-28)

Runbook krok po kroku. Zakłada, że nie pamiętasz nic z rozmowy, w której to powstało.


Co to jest — i czego to NIE jest

Postgres trzyma lustro plików Excela, żeby wyszukiwanie było szybsze i mądrzejsze.

ŹRÓDŁEM PRAWDY SĄ PLIKI EXCELA NA NFS. Utrata tej bazy nie jest utratą danych — odbudowuje się z plików. Dlatego kopie zapasowe Postgresa są tu opcjonalne, a wolumen stoi na local-path zamiast na NFS.

Po co w ogóle Postgres, skoro danych jest mało (~54 tys. wierszy)? Nie dla skali — przy takim rozmiarze SQLite bywa szybszy. Dla wyszukiwania w polskim, nieznormalizowanym tekście: pg_trgm (dopasowanie mimo literówek) i unaccent (ogonki) nie mają w SQLite taniego zamiennika.


Część 1 — co zrobiłem za Ciebie (w repo)

Nic z tego nie wymaga Twojej ręki, ale wiedz, co jest gdzie:

plik co w nim jest
astrololo/postgres.yaml PVC (5 Gi, local-path), ConfigMap z SQL-em inicjalizującym, Deployment (Recreate), Service ClusterIP
astrololo/kustomization.yaml postgres.yaml dopisany przed data.yaml
astrololo/data.yaml SQL_URL z sekretu; DATA_PROVIDER zostaje excel
repo aplikacji: services/data/requirements.txt sterownik psycopg[binary]

Decyzje, które w tych plikach zapadły — żeby nie trzeba było ich odtwarzać z głowy:

  • local-path, nie NFS. Postgres zakłada semantykę blokad i fsync, której NFS nie gwarantuje — to klasyczne źródło uszkodzenia bazy przy nagłym restarcie. Ceną jest przywiązanie do węzła; przy luście odtwarzalnym z Excela to nie boli.
  • strategy: Recreate. Wolumen jest ReadWriteOnce, a dwa procesy Postgresa na jednym katalogu danych to uszkodzona baza. Rolling próbowałby wstać z nowym podem, zanim stary zejdzie.
  • PGDATA w podkatalogu (…/data/pgdata). Katalog główny wolumenu potrafi zawierać wpisy systemu plików, a initdb odmawia pracy w niepustym katalogu.
  • Wersja przypięta (postgres:17) i POZA image-updaterem. Podbicie majora wymaga migracji katalogu danych — nie może się zdarzyć samo, w nocy, przy okazji builda aplikacji.
  • DATA_PROVIDER zostaje na excel. Postgres można wdrożyć i obejrzeć bez żadnego ryzyka dla działającego wyszukiwania. Przełączenie na sql to osobna, późniejsza decyzja — po wdrożeniu lustra.

Część 2 — co musisz zrobić Ty

Krok 0. Sprawdź, że klaster ma local-path

kubectl get storageclass

Oczekiwany wynik: linia z local-path i dopiskiem (default). W k3s jest domyślnie. Jeśli jej nie ma — zatrzymaj się tutaj i daj znać; bez niej PVC zawiśnie w Pending.

Krok 1. Utwórz sekret astrololo-postgres

Ta sama konwencja co astrololo-auth: sekretu nie ma w repo, bo to repo GitOps i cokolwiek by tu wpadło, zostałoby w historii gita na zawsze.

PGPASS="$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)"

kubectl -n astrololo create secret generic astrololo-postgres \
  --from-literal=POSTGRES_PASSWORD="$PGPASS" \
  --from-literal=SQL_URL="postgresql+psycopg://astrololo:${PGPASS}@postgres:5432/astrololo"

unset PGPASS

Hasła nie musisz nigdzie zapisywać ani nigdy oglądać — używają go tylko usługi między sobą. Gdybyś kiedyś go potrzebował:

kubectl -n astrololo get secret astrololo-postgres \
  -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d; echo

Dlaczego dwa klucze, skoro hasło jest w obu? POSTGRES_PASSWORD czyta sam Postgres przy inicjalizacji, SQL_URL czyta warstwa danych. Rozdzielone, bo to dwa różne formaty i dwóch różnych odbiorców — sklejanie DSN-a w manifeście wymagałoby wstawienia tam hasła.

Krok 2. Wdróż

Jeśli ArgoCD pilnuje katalogu astrololo, wystarczy zmergować PR — reszta zrobi się sama. Ręcznie:

kubectl apply -k astrololo

Krok 3. Sprawdź, że wstało

kubectl -n astrololo rollout status deploy/postgres
kubectl -n astrololo get pvc postgres-data

Oczekiwane: deployment "postgres" successfully rolled out i PVC w stanie Bound.

Krok 4. Sprawdź, że rozszerzenia się założyły

To jest najważniejszy sprawdzian tego wdrożenia — bez tych rozszerzeń cały sens stawiania Postgresa znika.

kubectl -n astrololo exec deploy/postgres -- \
  psql -U astrololo -d astrololo -c "\dx"

Na liście muszą być pg_trgm i unaccent. Sprawdź też, że działają:

kubectl -n astrololo exec deploy/postgres -- psql -U astrololo -d astrololo -c \
  "SELECT unaccent('różdżka')                              AS bez_ogonkow,
          similarity(unaccent('różdżka'), 'rozdzka')       AS po_zdjeciu_ogonkow,
          similarity('kowalski', 'kowlaski') > 0.3         AS literowka_lapana;"

Oczekiwane dokładnie:

 bez_ogonkow | po_zdjeciu_ogonkow | literowka_lapana
-------------+--------------------+------------------
 rozdzka     |                  1 | t

Pierwsza kolumna pokazuje, że unaccent zna polskie znaki; druga, że po zdjęciu ogonków słowa są identyczne (stąd równo 1); trzecia, że pg_trgm łapie przestawione litery. Jeśli druga kolumna nie jest równa 1, rozszerzenia są, ale reguły unaccent nie obsługują polskich znaków — daj znać, bo to zmienia sposób budowania indeksów.

Krok 5. Sprawdź, że warstwa danych ma połączenie

kubectl -n astrololo rollout restart deploy/data
kubectl -n astrololo rollout status deploy/data
kubectl -n astrololo logs deploy/data --tail=30 | grep -i -E "sql|postgres|error" || echo "brak wzmianek — OK"

Na tym etapie warstwa danych nadal czyta Excela (DATA_PROVIDER=excel). Postgres tylko stoi i czeka. Aplikacja ma działać dokładnie jak przedtem — i to jest oczekiwany wynik tego wdrożenia.


Część 3 — obsługa na co dzień

Zajrzeć do bazy

kubectl -n astrololo exec -it deploy/postgres -- psql -U astrololo -d astrololo

Przydatne w środku: \dt mirror.* (tabele lustra), \dx (rozszerzenia), \l (bazy), \q (wyjście).

Podłączyć narzędzie graficzne z laptopa

Baza celowo nie jest wystawiona poza klaster. Na czas jednej sesji:

kubectl -n astrololo port-forward deploy/postgres 5432:5432

i łączysz się na localhost:5432, użytkownik astrololo, baza astrololo, hasło jak w kroku 1. Po zamknięciu terminala tunel znika.

Dodanie rozszerzenia do ISTNIEJĄCEJ bazy

⚠️ Pułapka. Skrypty z /docker-entrypoint-initdb.d/ uruchamiają się wyłącznie przy pierwszej inicjalizacji, na pustym katalogu danych. Dopisanie rozszerzenia do ConfigMapy nie zrobi nic, jeśli baza już istnieje.

kubectl -n astrololo exec deploy/postgres -- \
  psql -U astrololo -d astrololo -c "CREATE EXTENSION IF NOT EXISTS nazwa;"

Kopia zapasowa (opcjonalna — patrz nagłówek)

kubectl -n astrololo exec deploy/postgres -- \
  pg_dump -U astrololo -d astrololo -Fc > astrololo-$(date +%F).dump

Odtworzenie:

kubectl -n astrololo exec -i deploy/postgres -- \
  pg_restore -U astrololo -d astrololo --clean --if-exists < astrololo-2026-08-06.dump

Wyczyścić lustro i wczytać od nowa

kubectl -n astrololo exec deploy/postgres -- psql -U astrololo -d astrololo -c \
  "DROP SCHEMA mirror CASCADE; CREATE SCHEMA mirror;"

Po tym warstwa danych odbuduje lustro z plików Excela (gdy lustro będzie już zaimplementowane — dziś ta komenda tylko czyści pusty schemat).

Podbicie wersji Postgresa

Nie robi się tego przez zmianę tagu w manifeście. Major wymaga migracji katalogu danych. Ponieważ to lustro:

  1. kubectl -n astrololo scale deploy/data --replicas=0
  2. skasuj Deployment i PVC (kubectl -n astrololo delete deploy/postgres pvc/postgres-data)
  3. zmień tag obrazu w postgres.yaml, wdróż ponownie
  4. kubectl -n astrololo scale deploy/data --replicas=1 — lustro odbuduje się z Excela

To jest właśnie ta sytuacja, w której „lustro, nie źródło prawdy" oszczędza dzień pracy.


Co zostało sprawdzone przed oddaniem, a co nie

Sprawdzone: kubectl kustomize astrololo składa komplet 21 zasobów bez błędu; YAML wszystkich manifestów parsuje się poprawnie; audyt odwołań do sekretów potwierdza, że jedyne brakujące to astrololo-postgres (oba klucze); DSN postgresql+psycopg://… jest poprawnie rozpoznawany przez SQLAlchemy 2.0 z zainstalowanym psycopg 3.

NIE sprawdzone, bo nie było na czym: skrypt inicjalizujący nie został uruchomiony przeciwko prawdziwemu Postgresowi (na maszynie, na której to powstawało, nie ma Dockera). Składnia jest prosta i przejrzana, ale krok 4 jest tu prawdziwym testem — jeśli coś ma nie zadziałać, to właśnie tam.


Część 4 — typowe problemy

objaw przyczyna co zrobić
PVC wisi w Pending brak local-path albo brak miejsca na węźle kubectl get storageclass, kubectl describe pvc postgres-data
pod w CrashLoopBackOff, w logach directory not empty PGDATA wskazuje katalog główny wolumenu sprawdź, czy PGDATA to …/data/pgdata (jest w manifeście)
pod nie startuje, secret not found nie wykonany krok 1 utwórz sekret i kubectl -n astrololo rollout restart deploy/postgres
\dx nie pokazuje pg_trgm baza założona przed dodaniem ConfigMapy patrz „Dodanie rozszerzenia do istniejącej bazy"
warstwa danych: ModuleNotFoundError: psycopg obraz zbudowany przed dodaniem sterownika poczekaj na build z CI albo kubectl -n astrololo rollout restart deploy/data po nowym obrazie
password authentication failed SQL_URL w sekrecie nie zgadza się z POSTGRES_PASSWORD odtwórz sekret krokiem 1 (oba klucze naraz) i zrestartuj oba deploymenty

Gdy nic nie pasuje — pierwsze dwie komendy do wklejenia:

kubectl -n astrololo describe pod -l app=postgres | tail -30
kubectl -n astrololo logs deploy/postgres --tail=50

Część 5 — pełne odtworzenie ręczne, bez ArgoCD i bez repo

Gdyby trzeba było postawić to od zera na czystym klastrze:

kubectl create namespace astrololo

PGPASS="$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)"
kubectl -n astrololo create secret generic astrololo-postgres \
  --from-literal=POSTGRES_PASSWORD="$PGPASS" \
  --from-literal=SQL_URL="postgresql+psycopg://astrololo:${PGPASS}@postgres:5432/astrololo"
unset PGPASS

kubectl apply -f astrololo/postgres.yaml
kubectl -n astrololo rollout status deploy/postgres
kubectl -n astrololo exec deploy/postgres -- psql -U astrololo -d astrololo -c "\dx"

Reszta aplikacji wymaga jeszcze sekretów astrololo-auth i astrololo-link — patrz README.md.