kubernetes
Kubernetes integration for Webda —
KubeRunnerdispatches async jobs as Kubernetes batch Jobs,KubernetesSecretsStorereads model data from K8s Secrets, andCronJobDeployermanages CronJob lifecycle.
When to use it
- You are using
@webda/asyncand want async job workers to run as Kubernetes Jobs (one pod per job, auto-retried, auto-cleaned). - You want to store Webda model instances in Kubernetes Secrets (useful for small, sensitive configuration objects).
- You need to deploy a scheduled Webda service operation as a Kubernetes CronJob.
- You read Kubernetes resources from a service: typed API clients and a self-restarting informer for custom resources.
- You store configuration-like models as Kubernetes custom resources (
KubernetesStore), editable with kubectl/GitOps. - You reconcile those models like a Kubernetes controller (
KubernetesOperator).
Install
pnpm add @webda/kubernetes
Configuration
KubeRunner (async job runner)
{
"services": {
"kubeRunner": {
"type": "KubeRunner",
"jobImage": "my-registry/my-webda-app:latest",
"context": "my-k8s-context"
},
"asyncJobService": {
"type": "AsyncJobService",
"queue": "AsyncActionsQueue",
"runners": ["kubeRunner"]
}
}
}
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
jobImage | string | — | Yes* | Docker image to use for the Kubernetes Job pod (* or jobResources) |
jobResources | object | string | — | Yes* | Full Kubernetes Job manifest (object or path to YAML file) overriding the default template |
config | string | object | — | No | Kubernetes config path or inline K8sConfiguration (defaults to in-cluster config) |
context | string | — | No | Kubernetes context name (from kubeconfig) to use |
Usage
// Workers are dispatched automatically by AsyncJobService.
// KubeRunner translates each AsyncAction into a Kubernetes Job:
//
// 1. AsyncJobService.launchAsAsyncAction("MyService", "runReport", reportId)
// → saves AsyncAction to store → sends to queue
//
// 2. KubeRunner picks up the queue message and creates a Kubernetes Job:
// kubectl get jobs
// NAME COMPLETIONS DURATION
// kuberunner-abc123 1/1 12s
//
// 3. The Job pod runs: webda runAsyncAction
// → calls MyService.runReport(reportId) inside the cluster
// To run locally (without Kubernetes), switch to ServiceRunner:
{
"services": {
"asyncJobService": {
"runners": ["serviceRunner"] // swap in for dev
}
}
}
KubernetesStore (models as custom resources)
{
"services": {
"tenantStore": {
"type": "Webda/KubernetesStore",
"group": "myapp.example.com",
"namespace": "tenants",
"models": ["MyApp/Tenant"],
"labelFields": { "MyApp/Tenant": ["plan"] },
"statusFields": { "MyApp/Tenant": ["ready"] }
}
}
}
Each model of models is a CustomResourceDefinition <plural>.<group>; its subclasses share its resources. The primary
key is the resource name (a lowercase DNS subdomain: a composite key needs - or . as PrimaryKeySeparator), the
stored fields its spec, the statusFields its status. Writes are optimistic (resourceVersion, retried on
conflict); = and IN on labelFields are filtered by the API server, the rest of a query in memory.
webda kubernetes crd export --target ./crds writes the definitions to apply, or set installCrds: true to create
them at init (needs the rights on customresourcedefinitions). webda kubernetes rbac export --target ./rbac.yaml
writes the Roles, ClusterRole and bindings the stores and operators need.
| Parameter | Default | Description |
|---|---|---|
group | — (required) | API group of the custom resources |
version | v1 | API version |
namespace | default | Namespace of the resources (ignored for Cluster scope) |
scope | Namespaced | Namespaced or Cluster |
labelFields | {} | Fields mirrored as labels, by model |
statusFields | {} | Fields written through the status subresource, by model |
installCrds | false | Create or update the definitions at init |
maxRetries | 5 | Attempts of a read-modify-write on conflicts |
watch | false | Emit the repository events of the changes made by others (kubectl, GitOps, another replica) |
config, context, timeout | in-cluster / kubeconfig, 10000 ms | Kubernetes client |
Kubernetes is not a database: use it for configuration-like models (tens to thousands of objects, at most ~1.5 MiB each). A label field must hold label-compatible values; resources created with kubectl need the labels too to be found by a query on them.
KubernetesOperator (reconcile models)
class Tenant extends UuidModel implements Reconcilable {
name!: string;
ready?: boolean;
async reconcile() {
// provision a database, a namespace...
await this.patch({ ready: true });
// optional: reconcile again later
return { requeueAfter: 60_000 };
}
async finalize() {
// clean up before the object is deleted (needs `finalizer`)
}
}
{
"services": {
"tenantOperator": {
"type": "Webda/KubernetesOperator",
"models": ["MyApp/Tenant"],
"finalizer": "myapp.example.com/finalizer"
}
}
}
webda kubernetes operator runs it (or autoStart: true at init). The models must be stored in a KubernetesStore; the
CRD of a model with a reconcile method gets a status subresource with observedGeneration and conditions.
reconcile()is called when the spec changes (metadata.generation), on resync (resyncPeriod, 5 min) and again after a failure (exponential backoff up tomaxBackoff). Status-only changes, its own included, are not reconciled.- The status gets
observedGenerationand aReadycondition (Reconciled, orReconcileErrorwith the message). - With
finalizer, it is added to the objects andfinalize()runs before the API server deletes them. - With
leaderElection(default), only the replica holding thecoordination.k8s.ioLease reconciles: the service account needsget,createandupdateonleases, andget,list,watch,updateon the resources and their status. - Metrics:
reconcile_total{model,result},reconcile_duration_seconds{model},queue_depth{model}.
API clients and informer
import * as k8s from "@kubernetes/client-node";
import { getKubernetesApiClient, KubernetesInformer } from "@webda/kubernetes";
// In-cluster service account, else the local kubeconfig; each request aborts after 4s
const core = getKubernetesApiClient({ timeout: 4000 }, k8s.CoreV1Api);
await core.listNamespacedPod({ namespace: "default" });
// List then watch custom resources, restarting with a backoff after any error
const projects = new KubernetesInformer({ timeout: 4000 }, { group: "example.com", version: "v1", plural: "projects" });
projects.on("change", project => console.log("changed", project.metadata?.name));
projects.on("error", err => console.error("informer restarts", err));
await projects.start(); // resolves after the first list, check projects.synced
projects.list();
The informer needs the list and watch verbs on the resource.
Reference
- API reference: see the auto-generated typedoc at
docs/pages/Modules/kubernetes/. - Source:
packages/kubernetes - Related:
@webda/asyncfor the job orchestration layer;@webda/awsforLambdaDeployeras an AWS equivalent;@webda/corefor theRunnerbase class.
Classes
- CustomResources
- KubernetesCrdExporter
- KubernetesCronExporter
- KubernetesInformer
- KubernetesOperator
- KubernetesOperatorParameters
- KubernetesRepository
- KubernetesStore
- KubernetesStoreParameters
- KubeRunner
- KubeRunnerParameters
- LeaderElector
- ModelController
- ResourceWatcher
- WorkQueue
- WriteTracker
Interfaces
- ControllerOptions
- CrdModel
- CrdModelMetadata
- CrdNames
- CrdOptions
- CustomResource
- CustomResourceDefinition
- CustomResourceType
- K8sConfiguration
- KubeJobReference
- KubernetesInformerOptions
- KubernetesInformerResource
- KubernetesParameters
- KubernetesRepositoryOptions
- LeaderCallbacks
- LeaderElectionOptions
- PolicyRule
- RbacGrant
- RbacOptions
- Reconcilable
- ResourceCondition
- StructuralSchema
- WorkQueueOptions