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 provisionProjectStorageProvisioner— 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ěží BE | Pří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
NoopXxxProvisionerpatternu
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érium | fabric8 7.x | io.kubernetes:client-java |
|---|---|---|
| Kotlin DX | Fluent DSL, přirozené Kotlin idiomy | Verbose builder chain, Java-centric |
| Watch API | First-class watch {} support (nutné pro Stopu B.6 — pod status sync) | Callbacks, větší boilerplate |
| Ekosystém | Quarkus default, Strimzi, Crossplane, Tekton | Rancher, kubectl Java plugins |
| Maintenance | Aktivní development, stable major 7.x | Generovaný kód, pomalejší iterace |
| Dependency size | Menší, 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/configautomaticky
@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@ConditionalOnMissingBeannení v některých test scenarios spolehlivý — Noop bean se nevyhodnotí jako fallback a context load selže sNoSuchBeanDefinitionException. Přechod na@ConditionalOnPropertysmatchIfMissing=truena 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 profil | talkide.k8s.enabled | Wired 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_ENABLED → talkide.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/):
ServiceAccount — talkide-be v namespace talkide:
apiVersion: v1
kind: ServiceAccount
metadata:
name: talkide-be
namespace: talkide
ClusterRole — talkide-be-orchestrator s minimálně potřebnými permissions:
| Resource | Verbs |
|---|---|
namespaces | create, get, list, delete |
pods | create, get, list, watch, delete |
services | create, get, list, update, delete |
deployments (apps) | create, get, list, update, delete |
configmaps | create, get, list, update, delete |
secrets | create, get, list, update, delete |
ingresses (networking.k8s.io) | create, get, list, update, delete |
jobs (batch) | create, get, list, watch, delete |
persistentvolumeclaims | create, get, list, delete |
resourcequotas | create, get, list |
networkpolicies (networking.k8s.io) | create, get, list |
ClusterRoleBinding — váže talkide-be ServiceAccount na talkide-be-orchestrator.
Deployment update — talkide-be.yaml přidá:
spec:
template:
spec:
serviceAccountName: talkide-be
6. Test strategie
| Vrstva | Tool | Účel |
|---|---|---|
| Unit | mockito-kotlin (fabric8 KubernetesClient) | ~95 % logiky: validace, transformace, error handling |
| Integration smoke | org.testcontainers:k3s | 1× 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:
K8sClientKotlin interfaceFabricK8sClientimplementace s@ConditionalOnPropertyNoopK8sClientimplementace s@ConditionalOnProperty(matchIfMissing=true)- Spring
@Configurationtřída drátující oba beany - Dependency:
io.fabric8:kubernetes-client:7.xvbuild.gradle.kts - Helm: ServiceAccount + ClusterRole + ClusterRoleBinding +
serviceAccountNamena BE Deployment - ConfigMap update:
TALKIDE_K8S_ENABLED: "true" - Integration smoke test s
K3sContainer
Co je mimo scope (jiné B.x issues):
| Feature | Issue |
|---|---|
| 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
- Konzistence —
NoopK8sClientzapadá 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
localwiresNoopK8sClient; 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 gap —
NoopK8sClientvrací 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#14 — https://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)
Thanks for the feedback.