14 KiB
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-deploypodpięte do Argo:Application/argocd/trade-staging(auto-sync)namespace/trade-staging: Postgres/Timescale + Hasura + pgAdmin + Jobhasura-bootstrapnamespace/trade-staging:trade-api+trade-ingestor(obrazy z registryrv32i.pl)namespace/trade-staging:trade-frontend+ Ingresstrade.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→ scopereadPOST /v1/ingest/tick→ scopewrite
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.pl→77.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.pl→77.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(Let’s 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
- TimescaleDB/Postgres (
- App stack:
devops/app/docker-compose.ymltrade-api(api, port 8787; health:GET /healthz)trade-frontend(frontend, port 8081; health:GET /healthz)trade-ingestor(ingestor, profilingest) – worker ingestujący ticki
- One-shot bootstrap:
devops/tools/bootstrap/docker-compose.ymldb-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):
- CD (pull): Argo CD czy Flux
- Argo CD + (opcjonalnie) Image Updater – popularny i „klikany”
- Flux + Image Automation – bardzo „git-first”
- Registry obrazów: Gitea Registry czy osobne (Harbor / registry:2)
- Sekrety w GitOps: manualne seedy (na start) czy docelowo SOPS/SealedSecrets/ExternalSecrets
- DB: w klastrze (StatefulSet + PVC) czy zewnętrzny Postgres/Timescale
- Środowiska:
staging+prod? (opcjonalnie preview per branch/PR) - Ingress/TLS: Traefik + cert-manager + Let’s Encrypt (lub inny wariant)
3) Docelowa architektura na k3s (mapowanie 1:1)
Minimalny, praktyczny podział zasobów:
timescaledb(Postgres/Timescale):StatefulSet+PVC+Servicehasura:Deployment+Servicetrade-api:Deployment+Servicetrade-frontend:Deployment+Service+Ingresstrade-ingestor:Deployment(zwyklereplicas: 1) alboCronJob(jeśli ingest ma być okresowy)- migracje/bootstrap:
Jobdladb-init/db-version/db-backfillJobdlahasura-bootstrap
Ważne:
trade-apiitrade-frontendmają gotowe endpointy/healthzdoreadinessProbe/livenessProbe.trade-ingestorpotrzebuje egress do internetu (RPC do Helius/Drift).
4) Podział repozytoriów (rekomendowane)
Żeby GitOps było czyste i proste:
- Repo aplikacji:
trade(to repo)- kod i Dockerfile:
devops/api/Dockerfile,devops/ingestor/Dockerfile,devops/app/frontend/Dockerfile
- kod i Dockerfile:
- 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-apitrade-frontendtrade-ingestor
- taguj deterministycznie, np.:
sha-<shortsha>(zawsze)- opcjonalnie
vNna 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.yamlwtrade-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→ dlatrade-api(admin do Hasury)tokens/api.json→ dlatrade-api(admin secret do tokenów API)tokens/read.json→ dlatrade-frontend(proxy do API)tokens/frontend.json→ dlatrade-frontend(basic auth)tokens/alg.json→ dlatrade-ingestor(write token)tokens/heliusN.json(lubrpcUrl) → dlatrade-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
- Namespace’y (np.
trade,gitea,argocd/flux-system). - StorageClass (na start
local-path, docelowo rozważ Longhorn). - Ingress controller (Traefik w k3s) + cert-manager + ClusterIssuer.
- Registry obrazów (Gitea Registry / Harbor / registry:2).
- GitOps controller (Argo CD albo Flux) podpięty do
trade-deploy.
Etap 2: DB + Hasura
- Deploy TimescaleDB (StatefulSet + PVC + Service).
- Uruchom
Jobdb-init(idempotent) – odpowiednikdevops/tools/bootstrap:db-init. - Deploy Hasura (Deployment + Service + Secret na admin/jwt/db-url).
- Uruchom
Jobhasura-bootstrap– odpowiednikdevops/tools/bootstrap:hasura-bootstrap.
Etap 3: App stack
- Deploy
trade-api(Deployment + Service, envHASURA_GRAPHQL_URL,TICKS_TABLE,CANDLES_FUNCTION). - Sprawdź
GET /healthzpo Service DNS i (opcjonalnie) z zewnątrz. - Wygeneruj i wgraj tokeny:
- read token dla frontendu (
tokens/read.json) - write token dla ingestora (
tokens/alg.json)
- read token dla frontendu (
- Deploy
trade-frontend+ Ingress (TLS opcjonalnie); ustawAPI_UPSTREAM=http://trade-api:8787. - Deploy
trade-ingestor(replicas: 1); potwierdź, że ticki wpadają.
Etap 4: CI/CD end-to-end
- Skonfiguruj runnera CI (Gitea Actions / Woodpecker).
- Workflow CI: build+push obrazów (i ewentualny commit do
trade-deploy). - 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 release’y Helm (np.
trade-v1,trade-v2) albo osobne namespace’y - wspólny DB/Hasura bez zmian
Job„version-init”:- uruchamia SQL
db-version(tworzydrift_ticks_vNi funkcje) - uruchamia
hasura-bootstrapzTICKS_TABLE/CANDLES_FUNCTIONustawionymi na vN
- uruchamia SQL
- cutover:
- start
trade-ingestorvN - stop
trade-ingestorv1 - (opcjonalnie)
db-backfilljako osobny job
- start
10) Definition of Done (DoD)
trade-apiitrade-frontenddziałają na k3s (probyReady=True, brak crashloopów).trade-ingestorzapisuje ticki do właściwej tabeli, a UI pokazuje wykres.- Deploy jest sterowany przez GitOps (zmiana w
trade-deploy→ rollout bez ręcznegokubectl 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 dlatrade-apitrade/trade-ingestor– kod + Dockerfile dlatrade-ingestortrade/trade-frontend– kod + Dockerfile dlatrade-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)
- Zainicjalizować
trade/trade-infrajako superproject i dodać submodule do wszystkich subrepo. - Wyciąć obecny kod do subrepo:
- API:
services/api/*+devops/api/Dockerfile→trade-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/
- API:
- Przenieść dokumentację do
trade-doc(w tym log:doc/steps.md) i zostawić w pozostałych repo linki do docs. - Ujednolicić nazwy obrazów i tagowanie:
rv32i.pl/trade/trade-api:<tag>rv32i.pl/trade/trade-ingestor:<tag>rv32i.pl/trade/trade-frontend:<tag>
- 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).
- Walidacja end-to-end na k3s:
- rollout
trade-staging, - UI
https://trade.rv32i.pl, - ingest ticków (
trade-ingestor→trade-api).
- rollout
Uwaga: lokalnie repo nie ma historii commitów, więc refaktor będzie startem „od zera” w nowych repo (initial commit per subrepo).