Service Architecture
DataKit has many components, but only one long-lived platform service: the Kubernetes controller layer. The dk CLI and SDK packages are client-side utilities and libraries; they prepare artifacts, validate manifests, and drive promotions, but they do not hold platform state.
System Components
| Component | Role |
|---|---|
dk CLI | Builds, validates, publishes, and promotes packages from a user machine or CI runner. |
| SDK | Shared libraries for manifest parsing, validation, OCI access, promotion, runners, lineage, and semantic packaging. |
| OCI Registry | Stores immutable package artifacts and exposes digest resolution to the controller. |
| ArgoCD | Watches GitOps state and applies Kubernetes resources, including PackageDeployment CRs. |
| Kubernetes | Runs the controller and the workloads it creates. |
| Controller | Reconciles PackageDeployment, DataSet, and ClickHouse migration resources into cluster state. |
Service Boundaries
Operationally, the controller is the platform service boundary.
- CLI: a utility that authors manifests, publishes OCI artifacts, and updates GitOps state.
- SDK: reusable libraries consumed by the CLI and controller.
- Controller: the only continuously running control-plane service.
Today that controller layer is split into two controller-runtime binaries:
platform/controller/cmd/main.gostarts the main controller manager and registersPackageDeploymentandDataSetreconcilers.platform/controller/cmd/ch-migration/main.gostarts the ClickHouse migration controller manager and registersClickHouseMigrationReconciler.
That split does not change the architectural boundary: both binaries belong to the controller service tier.
How It Works
A promotion does not deploy workloads directly. Instead, it moves desired state through OCI, Git, ArgoCD, and the controller.
sequenceDiagram
participant User as Developer or CI
participant CLI as dk CLI
participant Registry as OCI Registry
participant Git as GitHub
participant Argo as ArgoCD
participant API as Kubernetes API
participant Controller as dk-controller
participant Workload as Job / CronJob / Deployment
User->>CLI: dk build / dk publish
CLI->>Registry: Push OCI artifact
User->>CLI: dk promote --to <env> --cell <cell>
CLI->>Git: Update GitOps values and open/refresh PR
Git-->>Argo: Merged desired state becomes visible
Argo->>API: Apply PackageDeployment CR
API-->>Controller: Reconcile event
Controller->>Registry: Resolve digest and download package metadata
Controller->>API: Create or update Job/CronJob/Deployment
API-->>Workload: Start workload in target cell namespace
Controller Runtime Behavior
Startup
At startup, the main controller binary:
- builds a shared runtime scheme for core Kubernetes types and DataKit CRDs
- configures metrics, health probes, and optional leader election
- creates an ORAS-backed registry client
- constructs helpers for store resolution, dataset lookup, and lineage reporting
- registers reconcilers with the manager
- starts the controller-runtime manager and its shared caches
The migration binary follows the same pattern, but only wires the ClickHouseMigrationReconciler and a default ClickHouse connector.
Registered Controllers
| Reconciler | Watches | Primary side effects |
|---|---|---|
PackageDeploymentReconciler | PackageDeployment, owned Job, CronJob, Deployment | resolves OCI packages, injects store env, creates workloads, tracks run status, emits lineage |
DataSetReconciler | DataSet, Store | sets status.ready and StoreResolved condition based on store existence |
ClickHouseMigrationReconciler | ClickHouseMigration | resolves store credentials, interpolates SQL, executes DDL, updates migration conditions |
Reconciliation Loops
PackageDeploymentReconciler is phase-driven:
- Pending: validates package reference and moves to
Pulling - Pulling: resolves the OCI reference and digest
- Ready: dispatches by mode to batch, streaming, or semantic handling
- Running: monitors Jobs or Deployments
- Completed / Failed: terminal handling, with bounded retry for pull failures
DataSetReconciler is intentionally minimal. It only checks whether spec.store resolves in the namespace and reflects that in status.
ClickHouseMigrationReconciler is order-aware. It blocks on lower-order migrations for the same store and database, resolves the Store plus backing Secret, interpolates ${VAR} placeholders from envFrom, executes DDL, and records Applied and Ready conditions.
sequenceDiagram
participant API as Kubernetes API
participant Queue as controller-runtime work queue
participant Reconciler as PackageDeploymentReconciler
participant Registry as OCI Registry
participant K8s as Kubernetes API
API-->>Queue: PackageDeployment event
Queue->>Reconciler: Reconcile(name, namespace)
Reconciler->>K8s: Read PackageDeployment status/spec
alt phase = Pulling
Reconciler->>Registry: Resolve ref and digest
Reconciler->>K8s: Update status to Ready
else phase = Ready
Reconciler->>K8s: Create/Update Job, CronJob, or Deployment
Reconciler->>K8s: Update status to Running
else phase = Running
Reconciler->>K8s: Inspect workload state
Reconciler->>K8s: Update LastRun / terminal phase
end
Request Lifecycle
A package deployment crosses several boundaries:
- the CLI publishes a package to an OCI registry
- the CLI updates GitOps state for an environment and cell
- ArgoCD syncs the shared chart output into the cluster
- a
PackageDeploymentCR appears in Kubernetes - the controller resolves the referenced package and target cell namespace
- the controller injects resolved
DK_STORE_*environment variables fromStoreCRs - the controller creates a
Job,CronJob,Deployment, or semantic resources - the controller keeps status current as workloads run or fail
For batch workloads, the controller requeues and monitors the newest matching Job. For streaming workloads, it creates or updates a Deployment and monitors rollout state. For semantic packages, it writes ConfigMaps or Cube-related resources instead of process workloads.
Concurrency Model
DataKit relies on controller-runtime’s standard concurrency model:
- watch events are placed onto a rate-limited work queue
- reconciles are keyed by object name and namespace
- each reconcile is expected to be idempotent because the same object can be requeued repeatedly
- transient failures are represented either by returned errors or explicit
RequeueAfterdelays - optional leader election ensures only one manager instance actively reconciles when the flag is enabled
Leader election is configured but disabled by default in both controller binaries. When enabled, only the elected instance processes queue items for that manager.
Failure Modes
OCI registry unreachable
If the registry cannot resolve a package reference, handlePulling sets the Ready condition reason to PullFailed, marks the deployment Failed, and records the failed pull. handleFailed retries that case up to five times, requeueing after 30 seconds each time. After the retry cap, the object stays failed until the spec changes.
ClickHouse unavailable
If the migration controller cannot connect or execute DDL, it sets Applied=False with reason ExecutionFailed, keeps status.applied=false, and requeues after 60 seconds. Store lookup or env interpolation problems also stay non-terminal and are retried after 30 seconds.
Kubernetes API errors
Read, create, update, or status update failures are returned from reconcile when they cannot be handled locally. In that case controller-runtime requeues the key according to its work-queue backoff behavior. For some recoverable cases, the reconcilers also set explicit conditions before requeueing so the CR reflects the current failure state.