Mastering ClickHouse on Kubernetes part 1: A Hands-On Guide to the Official ClickHouse Operator
For years, the de-facto way to run ClickHouse on Kubernetes was the Altinity operator. It worked, powered countless production deployments…
Mastering ClickHouse on Kubernetes part 1: A Hands-On Guide to the Official ClickHouse Operator
For years, the de-facto way to run ClickHouse on Kubernetes was the Altinity operator. It worked, powered countless production deployments, and gave teams everything they needed, but it belonged to a third party.

A Kubernetes operator is essentially an automation controller for complex applications. Instead of manually creating StatefulSets, Services, ConfigMaps, persistent volumes, upgrade procedures, and recovery logic, you declare the desired state in YAML, and the operator continuously reconciles the cluster to match it. For databases, this is especially valuable because operational tasks like scaling, rolling upgrades, failover, configuration management, and storage orchestration are far more complex than simply starting containers.
In 2025, ClickHouse Inc. released its own operator, and it changes the picture: a first-party, Kubebuilder-based operator that ships with two simple Custom Resource Definitions, full lifecycle management, and tight integration with ClickHouse Keeper.
Why Run ClickHouse on Kubernetes?
ClickHouse is a column-oriented OLAP database designed for sub-second analytical queries over billions of rows. Teams reach for it when their workloads outgrow PostgreSQL or MySQL log analytics, real-time dashboards, ad-tech aggregations, observability backends, financial telemetry.
Running it on bare VMs is fine for a single-shard setup. It stops being fine once you need multiple shards spread across availability zones, replication with quorum coordination, frequent version upgrades without downtime, predictable rollouts when configuration changes, or storage that follows the pod when it reschedules.
These are exactly the problems Kubernetes solves for stateful workloads, and the operator pattern wires them into a declarative API. Instead of writing shell scripts to provision and reconcile a cluster, you write one YAML file and let the controller do the rest.
What the Operator Actually Does
The official ClickHouse/clickhouse-operator introduces two Custom Resource Definitions under the clickhouse.com API group:
• ClickHouseCluster: ClickHouse database cluster (shards, replicas, storage, configuration)
• KeeperCluster: ClickHouse Keeper cluster used for coordination (a modern, ClickHouse-native replacement for ZooKeeper)
When you kubectl apply one of these, the controller does the heavy lifting. It generates the right ClickHouse XML configuration, creates StatefulSets, Services, ConfigMaps, Secrets, PodDisruptionBudgets, and PVCs, wires the ClickHouse cluster to its Keeper cluster, performs rolling updates when you change the spec (with revision tracking), reconciles drift back to the declared state, validates resources via admission webhooks before they are stored, and exposes Prometheus metrics for observability.
It also handles two production-critical edge cases out of the box: storage collision resolution (so a renamed cluster does not accidentally bind to the wrong PVC) and reload-safe configuration changes that apply without restarting the pod.
Prerequisites
Before you install anything, make sure your cluster meets these requirements:
• Kubernetes v1.33+. The operator targets recent APIs
• kubectl v1.33+ configured against your cluster
• cert-manager installed. Required to issue the webhook serving certificate
• Cluster admin rights, or at least permission to create CRDs, ClusterRoles, and webhooks
If you are on EKS, GKE, or AKS, default node groups will work. Locally, kind or k3d is enough.
Install cert-manager first if you do not already have it:
helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \
-n cert-manager \
--create-namespace \
--set crds.enabled=true
Wait until all cert-manager pods are Running before continuing. The operator’s webhook installation depends on it.
Installing the Operator
You have three installation methods. Pick one:
Method 1: kubectl (Recommended for a Quick Start)
The fastest path. One command pulls the latest release manifest and applies everything: namespace, CRDs, RBAC, controller Deployment, webhook configuration, and metrics service.
kubectl apply -f https://github.com/ClickHouse/clickhouse-operator/releases/latest/download/clickhouse-operator.yaml
This creates the clickhouse-operator-system namespace and runs the controller manager inside it. After about 30 seconds, verify the pod is healthy:
kubectl get pods -n clickhouse-operator-system
Expected output:
clickhouse-operator-controller-manager-xxxxxxxxxx 1/1 Running 0 1m
Method 2: Helm (Recommended for Customization)
If you want to override values: image tags, resource limits, replica count of the controller Helm is the way to go:
helm install clickhouse-operator \
oci://ghcr.io/clickhouse/clickhouse-operator-helm \
--create-namespace \
-n clickhouse-operator-system
In an air-gapped environment without cert-manager, you can disable the webhook (you lose admission validation but gain the ability to install at all):
helm install clickhouse-operator \
oci://ghcr.io/clickhouse/clickhouse-operator-helm \
--create-namespace \
-n clickhouse-operator-system \
--set webhook.enable=false \
--set certManager.enable=false
Method 3: OLM (For OpenShift / OperatorHub Users)
If your platform uses Operator Lifecycle Manager, most commonly OpenShift install via a CatalogSource and Subscription. See https://clickhouse.com/docs/clickhouse-operator/install/olm in the repo for the full manifests.
Verifying the Install
Regardless of method, confirm that both CRDs registered:
kubectl get crd | grep clickhouse.com
You should see:
clickhouseclusters.clickhouse.com 2026–05–27T00:00:00Z
keeperclusters.clickhouse.com 2026–05–27T00:00:00Z
If they are missing, the manifest did not apply. Check
kubectl logs -n clickhouse-operator-system deploy/clickhouse-operator-controller-manager
for errors.
Deploying Your First Cluster
The repo ships a minimal example that is a perfect starting point. It defines a 3-node Keeper cluster and a 2-replica ClickHouse cluster that uses it for coordination.
kubectl apply -f - <<EOF
apiVersion: clickhouse.com/v1alpha1
kind: KeeperCluster
metadata:
name: sample
spec:
replicas: 3
dataVolumeClaimSpec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: clickhouse.com/v1alpha1
kind: ClickHouseCluster
metadata:
name: sample
spec:
replicas: 2
dataVolumeClaimSpec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
keeperClusterRef:
name: sample
EOF
Two resources, two intents: a KeeperCluster named sample with 3 replicas (the minimum for quorum) and 1 GiB of storage per node, and a ClickHouseCluster named sample with 2 replicas, 1 GiB of storage each, pointing to the Keeper cluster via keeperClusterRef.name.
The keeperClusterRef is the glue. It tells the ClickHouse controller which coordination cluster to wire up. Without it, the ClickHouse pods would start but replication would not work. You would be running two unsynchronized standalone nodes.
Inside the CRDs
Both CRDs follow a similar shape. The spec is small and focused — the controller fills in everything else.
**ClickHouseCluster key fields:**
- `replicas` — number of replicas in the shard
- `dataVolumeClaimSpec` — the PVC template, including storage class and size
- `keeperClusterRef` — reference to the KeeperCluster used for coordination
**KeeperCluster key fields:**
- `replicas` — number of Keeper nodes (3 or 5 for a healthy quorum)
- `dataVolumeClaimSpec` — PVC template for Keeper's transaction log and snapshots
Both resources expose rich status subresources: conditions (Ready, ConfigurationInSync, ReplicaStartupSucceeded, Healthy), observedGeneration (the latest spec generation seen by the controller), currentRevision and updateRevision for tracking rollout progress, and readyReplicas for replicas serving requests right now. These are exactly the signals you would query from a CI/CD pipeline or a GitOps controller to know whether a rollout actually finished.
Verifying the Deployment
Once the controller picks up the manifest, watch the pods come up:
kubectl get pods -w
In a few minutes you should see something like:
NAME READY STATUS RESTARTS AGE
sample-keeper-0 1/1 Running 0 90s
sample-keeper-1 1/1 Running 0 90s
sample-keeper-2 1/1 Running 0 90s
sample-0 1/1 Running 0 60s
sample-1 1/1 Running 0 60s
Keeper comes up first because ClickHouse depends on it. Check the CR status to confirm:
kubectl get clickhousecluster sample -o yaml | grep -A 20 status:
If conditions.Ready is True and readyReplicas is 2, you are in business.
Summary
In this first part, I covered the fundamentals: why the official ClickHouse operator matters, how to install it, deploy a minimal production-style cluster, verify health, scale replicas, and enable core operational features like monitoring and TLS.
That gets you from zero to a working ClickHouse deployment on Kubernetes, but it is only the starting point.
In the next parts, we’ll go deeper into production-focused topics: advanced cluster topology design (sharding vs replication), backup and disaster recovery strategies, performance tuning for analytical workloads, storage optimization on cloud platforms, secure multi-tenant configurations, GitOps workflows, and real-world operational troubleshooting.
메타데이터
- post_id
- 0a3c9f4d2e08
- slug
- mastering-clickhouse-on-kubernetes-a-hands-on-guide-to-the-official-clickhouse-operator-0a3c9f4d2e08
- url
- https://medium.com/@arekborucki/mastering-clickhouse-on-kubernetes-a-hands-on-guide-to-the-official-clickhouse-operator-0a3c9f4d2e08
- canonical_url
- https://medium.com/@arekborucki/mastering-clickhouse-on-kubernetes-a-hands-on-guide-to-the-official-clickhouse-operator-0a3c9f4d2e08
- author_url
- https://medium.com/@arekborucki
- status
- ok
- fetched_at
- 2026-06-11 10:13:20