Internal Documentation internal
TalkIDE internal documentation

Status: Accepted Datum: 2026-05-07 Oblast: Stopa B.1 / Kubernetes orchestrace

Context

Stopa B — orchestrace user-generated apps v K8s

TalkIDE platforma (DOKS, NYC3) deployí user-generated apps do Kubernetes. Každá app dostane vlastní namespace, deployment, service, ingress, configmapy a PVC. Build pipeline využívá Kaniko jobs. Orchestrace těchto K8s objektů je zodpovědností talkide-be.

Stopa B.1 je úplně první foundation krok: talkide-be pod musí umět mluvit s K8s API. Bez tohoto základu nelze implementovat žádný z dalších B.x kroků (namespace provision, image build, deploy, ingress, watch loop).

Existující pre-requisite design — Noop pattern

V prereq fázi byl zaveden env-aware bean wiring pattern přes NoopXxxProvisioner:

  • ProjectDatabaseProvisioner — lokál = no-op, cloud = skutečný DB provision
  • ProjectStorageProvisioner — lokál = no-op, cloud = DO Spaces provision

Stopa B.1 musí být konzistentní s tímto vzorem. Zavádíme K8sClient jako další člen rodiny provisionerů řídících se stejnou konvencí.

Deployment topology

ProstředíKde běží BEPřístup ke K8s
local (default)Host OS (žádný K8s)Žádný K8s — no-op bean
cloud (prod)Pod v namespace talkide (DOKS)In-cluster ServiceAccount token

Architektonické prerekvizity

  • ADR-012 — Kai cloud deploy capability; definuje Stopu B a URL konvence
  • ADR-013 — Git inside NFS; Git je lokální, žádný external remote
  • talkide-be#39 (Delete UC cleanup) — referenční implementace NoopXxxProvisioner patternu

Decision

1. Klientská knihovna: fabric8 kubernetes-client

Volba: io.fabric8:kubernetes-client:7.x (fabric8io).

Fabric8 DSL je výrazně více Kotlin-friendly než verbózní Builder API u oficiálního K8s SIG klienta. Konkrétní důvody:

Kritériumfabric8 7.xio.kubernetes:client-java
Kotlin DXFluent DSL, přirozené Kotlin idiomyVerbose builder chain, Java-centric
Watch APIFirst-class watch {} support (nutné pro Stopu B.6 — pod status sync)Callbacks, větší boilerplate
EkosystémQuarkus default, Strimzi, Crossplane, TektonRancher, kubectl Java plugins
MaintenanceAktivní development, stable major 7.xGenerovaný kód, pomalejší iterace
Dependency sizeMenší, modulárníVětší (generated client stubs pro všechna API)

Dependency (přidat do talkide-be/build.gradle.kts):

io.fabric8:kubernetes-client:<latest-7.x-stable>

Aktuální latest stable 7.x zjistit v Maven Central při implementaci issue #14.

2. Auth: jeden code path, runtime auto-detection

Fabric8 KubernetesClientBuilder().build() auto-detekuje prostředí:

  • In-cluster (cloud BE pod): přečte ServiceAccount token + CA cert z /var/run/secrets/kubernetes.io/serviceaccount/ (K8s standard mount)
  • Local dev: načte ~/.kube/config automaticky
@Bean
fun kubernetesClient(): KubernetesClient = KubernetesClientBuilder().build()

Žádný if/else, žádný branch podle profilu pro samotný KubernetesClient bean — to řeší fabric8 sama. Podmínění beanu na talkide.k8s.enabled=true se děje na úrovni wrapperu (viz rozhodnutí 3).

3. Wrapper architektura konzistentní s Noop patternem

K8sClient  (Kotlin interface — definuje operace)
├── FabricK8sClient   — @ConditionalOnProperty(talkide.k8s.enabled=true)
│     → deleguje na fabric8 KubernetesClient bean
└── NoopK8sClient     — @ConditionalOnProperty(name = ["talkide.k8s.enabled"], havingValue = "false", matchIfMissing = true)
      → loguje volání, vrací prázdné/stub response

Stejný mechanismus jako ProjectDatabaseProvisioner / ProjectStorageProvisioner. Nový developer vidí jeden konzistentní vzor; nemusí se učit výjimky.

FabricK8sClient je instantiated pouze pokud talkide.k8s.enabled=true — to znamená, že lokální dev nikdy nepotřebuje kubeconfig ani přístup ke clusteru.

Pozn. (errata 2026-05-07): Původní návrh používal @ConditionalOnMissingBean(K8sClient::class) na Noop. Praxe v Spring Boot 3.4.4 test context loaderu ukázala, že @ConditionalOnMissingBean není v některých test scenarios spolehlivý — Noop bean se nevyhodnotí jako fallback a context load selže s NoSuchBeanDefinitionException. Přechod na @ConditionalOnProperty s matchIfMissing=true na obou implementacích je deterministický (single source of truth = property hodnota), bez order dependency. Pattern objeven při B.2 implementaci (ADR-015), retroaktivně sjednocen napříč k8s package.

4. Spring property mapping na profil

Spring profiltalkide.k8s.enabledWired bean
local (default, lokální dev)false (absence = false)NoopK8sClient
cloud (prod pod)true (via ConfigMap)FabricK8sClient

Hodnota true je injectována přes Helm ConfigMap talkide-be-config:

# talkide-infra/deploy/helm/talkide-platform/templates/be-configmap.yaml
TALKIDE_K8S_ENABLED: "true"

Spring property binding: TALKIDE_K8S_ENABLEDtalkide.k8s.enabled (automatická konverze Spring relaxed binding).

5. RBAC: ClusterRole (technicky nutné, ne overengineering)

BE pod potřebuje vytvářet user namespaces (Stopa B.2). Resource Namespace je cluster-scoped (nepatří do žádného namespace) — per-namespace Role ho jednoduše nedosáhne. Jediná volba je ClusterRole.

Helm templates přidané v rámci Stopy B.1 (talkide-infra/deploy/helm/talkide-platform/templates/):

ServiceAccounttalkide-be v namespace talkide:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: talkide-be
  namespace: talkide

ClusterRoletalkide-be-orchestrator s minimálně potřebnými permissions:

ResourceVerbs
namespacescreate, get, list, delete
podscreate, get, list, watch, delete
servicescreate, get, list, update, delete
deployments (apps)create, get, list, update, delete
configmapscreate, get, list, update, delete
secretscreate, get, list, update, delete
ingresses (networking.k8s.io)create, get, list, update, delete
jobs (batch)create, get, list, watch, delete
persistentvolumeclaimscreate, get, list, delete
resourcequotascreate, get, list
networkpolicies (networking.k8s.io)create, get, list

ClusterRoleBinding — váže talkide-be ServiceAccount na talkide-be-orchestrator.

Deployment updatetalkide-be.yaml přidá:

spec:
  template:
    spec:
      serviceAccountName: talkide-be

6. Test strategie

VrstvaToolÚčel
Unitmockito-kotlin (fabric8 KubernetesClient)~95 % logiky: validace, transformace, error handling
Integration smokeorg.testcontainers:k3s1× test: FabricK8sClient skutečně zavolá K8s API a pods().list() vrátí bez výjimky

org.testcontainers:k3s je mainline modul testcontainers-java. K3s kontejner startuje za ~30 s a poskytuje real K8s API parity — není to mock, je to skutečný cluster.

Smoke test je záměrně minimalistický: ověřuje auth, RBAC bootstrap a fabric8 auto-detection. Komplexní K8s interakce jsou testovány v navazujících B.x issue testech.

7. Scope Stopy B.1 (issue #14) — úzce vymezený

Co je zahrnuto v #14:

  • K8sClient Kotlin interface
  • FabricK8sClient implementace s @ConditionalOnProperty
  • NoopK8sClient implementace s @ConditionalOnProperty(matchIfMissing=true)
  • Spring @Configuration třída drátující oba beany
  • Dependency: io.fabric8:kubernetes-client:7.x v build.gradle.kts
  • Helm: ServiceAccount + ClusterRole + ClusterRoleBinding + serviceAccountName na BE Deployment
  • ConfigMap update: TALKIDE_K8S_ENABLED: "true"
  • Integration smoke test s K3sContainer

Co je mimo scope (jiné B.x issues):

FeatureIssue
Namespace creation#15 / Stopa B.2
Kaniko image build pipeline#16 / Stopa B.3
Pod / Deployment manipulace#17 / Stopa B.4
Ingress provisioning#22 / Stopa B.5
Pod status watch loop (SSE)#23 / Stopa B.6

Consequences

Pozitiva

  • KonzistenceNoopK8sClient zapadá do existujícího patternu bez výjimek; nový developer vidí jeden vzor napříč všemi provisionery.
  • Lokální dev bez K8s — výchozí profil local wires NoopK8sClient; developer nespouštějící DOKS cluster může normálně pracovat.
  • Jednoduchá auth — fabric8 auto-detection eliminuje konfigurační branch logic; jeden @Bean, funguje jak in-cluster, tak s kubeconfig.
  • Kotlin DX — fabric8 fluent DSL snižuje boilerplate v navazujících B.x issues.
  • Smoke test s K3s — reálný K8s cluster v CI, ne mock; zachytí RBAC chyby a auth problémy dříve než prod deploy.

Rizika a omezení

  • ClusterRole scope — BE má clusterwide permissions na listed resources. V alpha fázi se jedním ServiceAccountem orchestruje celý cluster; multi-tenant isolation je na úrovni K8s namespaces a NetworkPolicy (Stopa B.2+). Produkční security review před public launch musí zvážit, zda jsou permissions dostatečně granulární.
  • K3s startup v CI (~30 s) — integration smoke test prodlouží CI pipeline. Akceptovatelné pro smoke vrstvu; unit testy (mockito-kotlin) zůstávají rychlé.
  • fabric8 major upgrade risk — 7.x je stable major, ale fabric8 historicky obsahuje breaking changes mezi major verzemi. Aktualizace na 8.x (až přijde) bude vyžadovat review API usage ve všech B.x implementacích.
  • Noop vs real behavior gapNoopK8sClient vrací stub response; lokální developer musí mít na paměti, že plné chování testuje až integration vrstva nebo DEV cloud prostředí.

Alternatives Considered

io.kubernetes:client-java (K8s SIG oficiální klient)

Odmítnuto. Oficiální status je jediná výhoda. Nevýhody:

  • Verbózní builder API špatně sedí s Kotlin idiomy
  • Watch API vyžaduje více boilerplate (callback-based, ne suspend-friendly)
  • Kód je z velké části generovaný ze Swagger spec → větší dependency, pomalejší response na K8s API změny
  • Slabší ekosystém v JVM prostředí (dominantní je fabric8 u Quarkus, operators)

Přímé HTTP volání na K8s REST API (žádná knihovna)

Odmítnuto okamžitě. K8s API versioning, auth token management, watch streaming a TLS handling jsou netriviální. Reinventing the wheel bez zjevného benefitu.

Podmínění přes Spring profil (@Profile(“cloud”))

Zvažováno, odmítnuto ve prospěch @ConditionalOnProperty. Důvod: property je explicitní (TALKIDE_K8S_ENABLED=true), nezávisí na profilu — lze ji nastavit i lokálně pro testování s kubeconfig bez změny profilu. Konzistentní s existujícím patternem ve zbytku aplikace.


Implementation Notes

  • Issue: talkide-be#14https://gitlab.com/talkide/talkide-be/-/work_items/14
  • Helm changes jsou v: talkide-infra/deploy/helm/talkide-platform/templates/
  • Noop pattern reference: talkide-be#39 (Delete UC cleanup)
  • Navazující Stopa B issues: #15 (namespace), #16 (build), #17 (deploy), #22 (ingress), #23 (watch)

Was this page helpful?

Thanks for the feedback.