MCS control plane
The MCS control plane turns product intent into managed ClickHouse runtime resources. It has two cooperating parts:
- the management control plane accepts user intent through the Console or a future API, applies authentication, authorization, validation, and limits, and persists desired state as MCS custom resources;
- the runtime control plane, implemented by the MCS Operator, watches that desired state and executes it in Kubernetes.
The Runtime Operator is therefore not a separate product plane. It is the runtime executor of the MCS control plane. Together, the management control plane and Runtime Operator form the complete MCS control plane.
Today Kubernetes custom resources are the desired-state database and the handoff contract between these two parts. The Console is one client of the management control plane; it is not a second Operator.
The data plane consists of the runtime stacks produced from those custom
resources. One MCSService CR declares one isolated ClickHouse service data
plane, including its ClickHouse nodes, network endpoints, storage bindings, and
optional ElasticQuery nodes. An MCSKeeper CR similarly declares a managed
Keeper quorum. The CR belongs to the control plane; the reconciled workload
belongs to the data plane.
The ClickHouse data plane and object-storage layout are documented separately in ClickHouse Architecture.
Components and control flow
The management control-plane service is the stable product boundary. The Console UI and a future API application use the same models, authorization, limits, and repository contracts. Kubernetes types and controller-runtime clients stop at the repository adapter boundary.
The complete flow is:
Console / API
→ management control-plane service
→ MCS desired-state CR
→ Runtime Operator
→ Kubernetes runtime resources
→ ClickHouse service data plane
Status travels in the opposite direction: the Runtime Operator publishes observed state to the CR, and the management control plane projects that state to users.
Desired-state resources
| Resource | Control-plane purpose | Resulting runtime boundary |
|---|---|---|
MCSOrganization |
Ownership, users, Keeper placement, and organization limits. | Groups and authorizes desired state; it does not create a standalone data plane. |
MCSKeeper |
Desired Keeper quorum and its storage configuration. | One managed Keeper runtime quorum. |
MCSService |
Desired ClickHouse release, topology, storage, access, and ElasticQuery configuration. | One isolated ClickHouse service data plane. |
MCSServiceUser |
A service-scoped ClickHouse identity, grants, and managed credentials. | Credentials and grants inside the owning ClickHouse service. |
The Console creates, reads, updates, and deletes these resources through
DesiredStateRepository. The Runtime Operator watches them and produces the
runtime data plane. Other management control-plane applications may use the
same service layer without going through the Console UI.
Control-plane responsibilities
Management control plane
The management control plane owns product-facing intent:
- authentication, sessions, organization membership, and authorization;
- validation, supported choices, defaults, and quota policy;
- transport-neutral models and business operations;
- desired-state CRUD through
DesiredStateRepository; - Console rendering and the future API boundary;
- projecting CR status, endpoints, credentials, and runtime observations back to users.
It stops at desired state. Creating an MCSService CR is success at this layer;
it does not imply that the ClickHouse service is already Ready.
Runtime control plane
The Runtime Operator owns execution:
- watching MCS custom resources and generations;
- resolving release-specific ClickHouse, Keeper, storage, and ElasticQuery configuration;
- generating and reconciling Kubernetes resources;
- handling retries, idempotency, finalization, rollout ordering, and recovery;
- publishing observed status and conditions to the CR.
It consumes desired state but does not implement product-facing sessions, organization authorization, Console pages, or API request policy.
Data plane
The data plane owns data ingestion, storage, query execution, and runtime availability. It is composed of the resources reconciled for each service or Keeper CR:
- ClickHouse nodes and their Services;
- Keeper members used by those ClickHouse services;
- object-storage and local-storage bindings;
- optional read-only ElasticQuery nodes;
- supporting ConfigMaps, Secrets, PVCs, and workload Pods.
The Runtime Operator controls these resources, but it is not itself part of the data plane.
Runtime ownership
| Resource | Source of truth | Writer | Control-plane path |
|---|---|---|---|
| MCS custom resources | Kubernetes API | Console or another API client | DesiredStateRepository |
| StatefulSets, Deployments, Services, ConfigMaps, PVCs | Reconciled desired state | MCS Operator | Controller |
| Pods, readiness, and runtime status | Kubernetes runtime | Kubernetes/controllers | RuntimeReader |
| Operator-generated ClickHouse credential Secret | Reconciled desired state | MCS Operator | Controller |
| Service runtime Namespace | Kubernetes | Console direct write | DirectWriter |
| Console auth/session Secret | Kubernetes | Console direct write | DirectWriter |
| Copied static object-storage credential Secret | Kubernetes | Console direct write | DirectWriter |
The direct-write cases are bootstrap or Console infrastructure. They are
deliberately isolated behind DirectWriter; workload Services, StatefulSets,
Deployments, ConfigMaps, and PVCs are not exposed by that interface.
Every adapter write returns repository.WriteResult, including the repository,
desired-state or bootstrap classification, operation, direct-Kubernetes flag,
and resource identity. This is the common boundary for future audit logging and
API responses.
Control-plane packages
| Package | Responsibility |
|---|---|
internal/controlplane/service |
Management control-plane business operations, authorization, limits, and orchestration shared by every application. |
internal/controlplane/model |
Transport-neutral commands, identities, platform configuration, and usage models. |
internal/controlplane/repository |
Management control-plane desired-state, runtime-read, and direct-write contracts. |
internal/controlplane/repository/kubernetes |
Kubernetes implementation of those repository contracts. |
internal/controlplane/console |
Management control-plane Console application: routes, middleware, templates, static assets, and process wiring. |
internal/controlplane/console/handler |
HTML/HTTP parsing, redirects, validation responses, and rendering. |
internal/controlplane/api |
Future management control-plane API application; currently contains only a /healthz skeleton. |
internal/operator and controllers |
Runtime control-plane process wiring, watches, reconciliation, status, and finalization. |
internal/service, internal/keeper, and related generators |
Runtime definitions used by the Operator to produce the ClickHouse service data plane. |
The service package is the shared business layer. Console UI and future API
code are applications around it, while model remains independent of either
transport. Product entities use explicit names such as ClickHouse service,
organization, and Keeper so they are not confused with the Kubernetes
Service kind.
Identity and naming
Every product entity has a stable platform ID in metadata.name and a mutable
display name in spec.name. References, namespaces, DNS names, object-storage
paths, and labels use only the stable ID:
| Entity | Stable ID | Runtime boundary |
|---|---|---|
| Organization | org-<16 characters> |
Ownership and limits |
| Keeper | kpr-<16 characters> |
Shared data-mcs-keepers namespace |
| ClickHouse service | svc-<16 characters> |
data-mcs-<service-id> namespace |
| ClickHouse node | 8-character replica ID | StatefulSet, Pod, macro, and storage prefix |
Display names never become Kubernetes names or storage paths. ClickHouse node
IDs are allocated once and persisted by the Operator so scaling and restarts do
not rename existing nodes. MCSServiceUser objects live in the owning service
namespace; their managed password Secrets and grants cannot cross that service
boundary.
Storage contracts
DesiredStateRepository
Owns MCS entity CRUD. Its current implementation persists CRs because the Operator consumes CRs directly.
RuntimeReader
Reads actual Kubernetes state such as Pods and Secrets. Runtime observation remains a Kubernetes responsibility even if desired state later moves to a database.
DirectWriter
Contains only the small, reviewed set of Console-owned Kubernetes bootstrap writes. A new method is an ownership decision and requires an explanation for why an Operator controller cannot own it.
Reconciliation lifecycle
- A user action becomes a transport-neutral command.
- The control-plane service authorizes it and validates limits.
DesiredStateRepositorypersists the new desired state as a CR.- The Runtime Operator observes the generation and computes runtime resources.
- Kubernetes converges those resources.
- The Runtime Operator publishes status and conditions back to the CR.
- The management control plane reads desired state through
DesiredStateRepositoryand actual state throughRuntimeReader.
Deletes follow the same ownership model: the Console deletes the desired-state CR and Kubernetes garbage collection plus Runtime Operator finalization remove owned data-plane resources.
Consistency and limits
- Updates use optimistic concurrency and retry Kubernetes conflicts.
- Kubernetes not-found and already-exists errors become repository-neutral errors.
- Service-layer rules run before persistence calls.
- The organization service-count limit is enforced by the control-plane service.
- CPU and memory usage is calculated live from desired service topology rather than stored as a mutable counter.
- CPU and memory budgets are visible but currently do not block a write.
Future persistence
No database migration is part of the current architecture. The prepared seam is
DesiredStateRepository.
If a database later becomes authoritative, the control plane can add a
database-backed repository and publish desired state to MCS CRs for the
existing Operator. Console UI and future API applications continue to call the
same service. RuntimeReader remains Kubernetes-backed, and the explicit
bootstrap writes remain separate from entity persistence.
This separation prevents an unsafe partial migration: storing a record in a database is not considered runtime reconciliation, and writing runtime objects directly is not considered entity persistence.
Change rules
- Handlers must not import controller-runtime clients.
- Business rules belong in
service, not templates or adapters. - Kubernetes errors must not escape the Kubernetes adapter.
- Operator-owned runtime resources must never be created by Console.
- New direct writes require an explicit
DirectWritermethod, ownership documentation, and adapter tests. - Console UI and future API applications must share control-plane service methods.