Files
trade-doc/migration.md
2026-01-06 12:33:47 +01:00

14 KiB
Raw Blame History

Migracja trade do k3s + GitOps (pull) na Gitea + CI/CD

Ten dokument opisuje plan migracji obecnego stacka (Docker Compose) do k3s z CD w modelu pull-based (GitOps): klaster sam synchronizuje „desired state” z repo na Gitei, a CI jedynie buduje/publikuje obrazy i aktualizuje repo deploymentu.

Status (VPS / k3s)

Na VPS jest już uruchomione (k3s single-node):

  • Ingress: Traefik (80/443)
  • TLS: cert-manager + ClusterIssuer/letsencrypt-prod
  • Gitea (Ingress https://rv32i.pl) + registry (https://rv32i.pl/v2/)
  • Argo CD w namespace argocd
  • Gitea Actions runner w namespace gitea-actions (act_runner + DinD)
  • GitOps repo trade/trade-deploy podpięte do Argo:
    • Application/argocd/trade-staging (auto-sync)
    • namespace/trade-staging: Postgres/Timescale + Hasura + pgAdmin + Job hasura-bootstrap
    • namespace/trade-staging: trade-api + trade-ingestor (obrazy z registry rv32i.pl)
    • namespace/trade-staging: trade-frontend + Ingress trade.rv32i.pl (basic auth, TLS OK)

Szybka weryfikacja (VPS):

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get deploy trade-api trade-ingestor -o wide
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/trade-ingestor --tail=40

Tokeny trade-api (read/write)

Endpointy w trade-api są chronione tokenami z tabeli api_tokens:

  • GET /v1/ticks, GET /v1/chart → scope read
  • POST /v1/ingest/tick → scope write

W staging tokeny są przechowywane jako K8s Secrets (bez commitowania do gita):

  • trade-staging/Secret/trade-ingestor-tokens: alg.json (write) + heliusN.json (RPC)
  • trade-staging/Secret/trade-read-token: read.json (read)
  • trade-staging/Secret/trade-frontend-tokens: frontend.json (basic auth) + read.json (proxy do API)

Dostęp: Argo CD UI (bez konfliktu z lokalnym Hasurą na 8080)

Port-forward uruchamiasz „na żądanie” (to nie jest stały serwis). Jeśli lokalnie masz Hasurę na 8080, użyj 8090.

Na VPS:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd port-forward --address 127.0.0.1 svc/argocd-server 8090:443

Na swoim komputerze:

ssh -L 8090:127.0.0.1:8090 user@rv32i.pl

UI: https://localhost:8090

Argo CD: username / hasło

  • Username: admin
  • Hasło startowe (pobierz na VPS; nie commituj / nie wklejaj do gita):
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d; echo

Po pierwszym logowaniu ustaw własne hasło i (opcjonalnie) usuń argocd-initial-admin-secret.

Runner: log (czy CI działa)

Na VPS:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n gitea-actions logs deploy/gitea-act-runner -c runner --tail=80

Portainer (Kubernetes UI): portainer.rv32i.pl

Portainer jest wdrażany przez Argo CD jako osobna aplikacja portainer (namespace portainer) z Ingressem i cert-managerem.

Wymagania:

  • DNS: rekord A portainer.rv32i.pl77.90.8.171

Weryfikacja na VPS:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd get application portainer -o wide
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n portainer get pods,svc,ingress,certificate -o wide

Jeśli cert-manager „utknie” na HTTP-01 z powodu cache NXDOMAIN w klastrze, pomocne jest zrestartowanie CoreDNS:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n kube-system rollout restart deploy/coredns

Jeśli Portainer pokaże ekran timeout.html (setup/login timed out), zrestartuj deployment i od razu dokończ inicjalizację:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n portainer rollout restart deploy/portainer

Frontend UI: trade.rv32i.pl

Frontend jest wdrożony w trade-staging i ma Ingress na trade.rv32i.pl (Traefik + cert-manager).

Wymagania:

  • DNS: rekord A trade.rv32i.pl77.90.8.171

Weryfikacja na VPS:

KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get deploy trade-frontend -o wide
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get ingress trade-frontend -o wide
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get certificate trade-rv32i-pl-tls -o wide

Status na dziś:

  • Certificate/trade-rv32i-pl-tls = Ready=True (Lets Encrypt)

0) Stan wejściowy (co dziś jest w repo)

Projekt ma już logiczny podział „mikroserwisowy”:

  • DB stack: devops/db/docker-compose.yml
    • TimescaleDB/Postgres (postgres, port 5432)
    • Hasura (hasura, port 8080)
    • pgAdmin (pgadmin, port 5050) raczej tylko dev/staging
  • App stack: devops/app/docker-compose.yml
    • trade-api (api, port 8787; health: GET /healthz)
    • trade-frontend (frontend, port 8081; health: GET /healthz)
    • trade-ingestor (ingestor, profil ingest) worker ingestujący ticki
  • One-shot bootstrap: devops/tools/bootstrap/docker-compose.yml
    • db-init, db-version, db-backfill (SQL)
    • hasura-bootstrap (track tabel + permissions)

Sekrety/konfiguracja lokalnie są dziś w tokens/*.json (gitignored) i są montowane jako pliki do kontenerów.

1) Cel migracji

  • Przeniesienie usług do k3s (Kubernetes) bez zmiany logiki aplikacji.
  • Wdrożenia przez GitOps (pull, bez „kubectl apply” z CI).
  • Powtarzalność: jedna ścieżka build → deploy, jednoznaczne tagi/digests.
  • Minimalny downtime: możliwość uruchamiania wersji równolegle (v1/v2…), jak w scripts/ops/*.

2) Założenia i decyzje do podjęcia (checklista)

Ustal na start (zaznacz jedną opcję, resztę możesz doprecyzować później):

  1. CD (pull): Argo CD czy Flux
    • Argo CD + (opcjonalnie) Image Updater popularny i „klikany”
    • Flux + Image Automation bardzo „git-first”
  2. Registry obrazów: Gitea Registry czy osobne (Harbor / registry:2)
  3. Sekrety w GitOps: manualne seedy (na start) czy docelowo SOPS/SealedSecrets/ExternalSecrets
  4. DB: w klastrze (StatefulSet + PVC) czy zewnętrzny Postgres/Timescale
  5. Środowiska: staging + prod? (opcjonalnie preview per branch/PR)
  6. Ingress/TLS: Traefik + cert-manager + Lets Encrypt (lub inny wariant)

3) Docelowa architektura na k3s (mapowanie 1:1)

Minimalny, praktyczny podział zasobów:

  • timescaledb (Postgres/Timescale): StatefulSet + PVC + Service
  • hasura: Deployment + Service
  • trade-api: Deployment + Service
  • trade-frontend: Deployment + Service + Ingress
  • trade-ingestor: Deployment (zwykle replicas: 1) albo CronJob (jeśli ingest ma być okresowy)
  • migracje/bootstrap:
    • Job dla db-init/db-version/db-backfill
    • Job dla hasura-bootstrap

Ważne:

  • trade-api i trade-frontend mają gotowe endpointy /healthz do readinessProbe/livenessProbe.
  • trade-ingestor potrzebuje egress do internetu (RPC do Helius/Drift).

4) Podział repozytoriów (rekomendowane)

Żeby GitOps było czyste i proste:

  1. Repo aplikacji: trade (to repo)
    • kod i Dockerfile: devops/api/Dockerfile, devops/ingestor/Dockerfile, devops/app/frontend/Dockerfile
  2. Repo deploymentu: trade-deploy (nowe repo na Gitei)
    • manifesty K8s (Helm chart albo Kustomize)
    • overlays per środowisko: staging/, prod/

Zasada: CI nie wykonuje deploya do klastra; CI tylko aktualizuje trade-deploy, a CD w klastrze to „pulluje”.

5) CI na Gitei (build → push)

Do wyboru:

  • Gitea Actions + act_runner (najbliżej GitHub Actions)
  • alternatywnie Woodpecker/Drone, jeśli już masz w infra

Zakres CI:

  • zbuduj i wypchnij obrazy:
    • trade-api
    • trade-frontend
    • trade-ingestor
  • taguj deterministycznie, np.:
    • sha-<shortsha> (zawsze)
    • opcjonalnie vN na release
  • po push obrazu: zaktualizuj trade-deploy (tag/digest) lub zostaw to automatyzacji obrazów (patrz niżej).

6) CD (pull) z klastra: GitOps

Wariant A (najprostszy operacyjnie): CI robi commit do trade-deploy

  • CI po zbudowaniu obrazu aktualizuje values.yaml/kustomization.yaml w trade-deploy (tag lub digest) i robi commit/push.
  • Argo CD / Flux wykrywa zmianę i robi rollout.

Wariant B (bardziej „autopilot”): automatyczna aktualizacja obrazów

  • Argo CD Image Updater albo Flux Image Automation obserwuje registry i sam aktualizuje repo trade-deploy.
  • CI tylko buduje/pushuje obrazy.

7) Sekrety: tokens/*.json → Kubernetes Secret (plikowy mount)

Docelowo w K8s montujesz te same pliki co w Compose:

  • tokens/hasura.json → dla trade-api (admin do Hasury)
  • tokens/api.json → dla trade-api (admin secret do tokenów API)
  • tokens/read.json → dla trade-frontend (proxy do API)
  • tokens/frontend.json → dla trade-frontend (basic auth)
  • tokens/alg.json → dla trade-ingestor (write token)
  • tokens/heliusN.json (lub rpcUrl) → dla trade-ingestor

Nie commituj sekretów do gita. W GitOps wybierz jedną drogę:

  • start: ręczne kubectl create secret ... na środowisko
  • docelowo: SOPS (age) / SealedSecrets / ExternalSecrets (Vault itp.)

8) Plan wdrożenia (kolejność kroków)

Etap 1: Klaster i narzędzia bazowe

  1. Namespacey (np. trade, gitea, argocd/flux-system).
  2. StorageClass (na start local-path, docelowo rozważ Longhorn).
  3. Ingress controller (Traefik w k3s) + cert-manager + ClusterIssuer.
  4. Registry obrazów (Gitea Registry / Harbor / registry:2).
  5. GitOps controller (Argo CD albo Flux) podpięty do trade-deploy.

Etap 2: DB + Hasura

  1. Deploy TimescaleDB (StatefulSet + PVC + Service).
  2. Uruchom Job db-init (idempotent) odpowiednik devops/tools/bootstrap:db-init.
  3. Deploy Hasura (Deployment + Service + Secret na admin/jwt/db-url).
  4. Uruchom Job hasura-bootstrap odpowiednik devops/tools/bootstrap:hasura-bootstrap.

Etap 3: App stack

  1. Deploy trade-api (Deployment + Service, env HASURA_GRAPHQL_URL, TICKS_TABLE, CANDLES_FUNCTION).
  2. Sprawdź GET /healthz po Service DNS i (opcjonalnie) z zewnątrz.
  3. Wygeneruj i wgraj tokeny:
    • read token dla frontendu (tokens/read.json)
    • write token dla ingestora (tokens/alg.json)
  4. Deploy trade-frontend + Ingress (TLS opcjonalnie); ustaw API_UPSTREAM=http://trade-api:8787.
  5. Deploy trade-ingestor (replicas: 1); potwierdź, że ticki wpadają.

Etap 4: CI/CD end-to-end

  1. Skonfiguruj runnera CI (Gitea Actions / Woodpecker).
  2. Workflow CI: build+push obrazów (i ewentualny commit do trade-deploy).
  3. Zasady promocji: staging → prod (np. tylko tag/release).

9) Wersjonowanie v1/v2… (równoległe wdrożenia + cutover)

Repo ma już pattern wersjonowania w Compose (scripts/ops/* + devops/versions/vN.env).

Odwzorowanie na K8s:

  • osobne releasey Helm (np. trade-v1, trade-v2) albo osobne namespacey
  • wspólny DB/Hasura bez zmian
  • Job „version-init”:
    • uruchamia SQL db-version (tworzy drift_ticks_vN i funkcje)
    • uruchamia hasura-bootstrap z TICKS_TABLE/CANDLES_FUNCTION ustawionymi na vN
  • cutover:
    • start trade-ingestor vN
    • stop trade-ingestor v1
    • (opcjonalnie) db-backfill jako osobny job

10) Definition of Done (DoD)

  • trade-api i trade-frontend działają na k3s (proby Ready=True, brak crashloopów).
  • trade-ingestor zapisuje ticki do właściwej tabeli, a UI pokazuje wykres.
  • Deploy jest sterowany przez GitOps (zmiana w trade-deploy → rollout bez ręcznego kubectl apply).
  • Sekrety są poza gitem (manualnie lub przez wybraną metodę GitOps).

11) Referencje w repo

  • Compose: devops/db/docker-compose.yml, devops/app/docker-compose.yml, devops/tools/bootstrap/docker-compose.yml
  • Workflow end-to-end: doc/workflow-api-ingest.md
  • Szkic migracji K8s: doc/k8s-migracja.md
  • Gitea na k3s (Traefik/TLS): doc/gitea-k3s-rv32i.md

12) Metoda „superproject” (subrepo) plan refaktoru repozytoriów

Cel: utrzymać osobne repo dla usług (API/ingestor/frontend) i osobne repo GitOps (trade-deploy), a do tego mieć jedno repo „główne” (superproject) spinające wszystko (np. jako git submodules).

Wybór repo głównego

Rekomendacja: trade/trade-infra jako repo główne (superproject):

  • jest neutralne (infra/ops), nie miesza kodu aplikacji z manifestami GitOps,
  • pozwala trzymać jedną „zafiksowaną” wersję całego systemu (SHAs submodułów),
  • nie wymaga zmiany Argo CD (który nadal może śledzić trade/trade-deploy).

Docelowy podział ról repo

  • trade/trade-api kod + Dockerfile dla trade-api
  • trade/trade-ingestor kod + Dockerfile dla trade-ingestor
  • trade/trade-frontend kod + Dockerfile dla trade-frontend (apps/visualizer + serwer)
  • trade/trade-deploy GitOps: Kustomize/Helm dla k3s (Argo CD pull)
  • trade/trade-doc dokumentacja całego projektu (doc/), w tym log działań
  • trade/trade-infra superproject + narzędzia/ops (np. makefile, skrypty, checklisty)

Proponowany układ w superprojekcie

Przykład (ścieżki w trade/trade-infra):

trade-infra/
  api/        -> submodule: trade-api
  ingestor/   -> submodule: trade-ingestor
  frontend/   -> submodule: trade-frontend
  deploy/     -> submodule: trade-deploy
  doc/        -> submodule: trade-doc

Plan wdrożenia refaktoru (po akceptacji)

  1. Zainicjalizować trade/trade-infra jako superproject i dodać submodule do wszystkich subrepo.
  2. Wyciąć obecny kod do subrepo:
    • API: services/api/* + devops/api/Dockerfiletrade-api/
    • Frontend: apps/visualizer/* + services/frontend/* + devops/app/frontend/*trade-frontend/
    • Ingestor: scripts/ingest-drift-oracle.ts (+ minimalny zestaw zależności i Dockerfile) → trade-ingestor/
  3. Przenieść dokumentację do trade-doc (w tym log: doc/steps.md) i zostawić w pozostałych repo linki do docs.
  4. Ujednolicić nazwy obrazów i tagowanie:
    • rv32i.pl/trade/trade-api:<tag>
    • rv32i.pl/trade/trade-ingestor:<tag>
    • rv32i.pl/trade/trade-frontend:<tag>
  5. CI/CD (Gitea Actions) per repo usługi:
    • build → push do registry,
    • aktualizacja trade-deploy (commit/PR z bumpem tagu/digesta),
    • staging auto-sync w Argo (prod manual).
  6. Walidacja end-to-end na k3s:
    • rollout trade-staging,
    • UI https://trade.rv32i.pl,
    • ingest ticków (trade-ingestortrade-api).

Uwaga: lokalnie repo nie ma historii commitów, więc refaktor będzie startem „od zera” w nowych repo (initial commit per subrepo).