A small, anonymized reference for running ClickHouse on Amazon EKS with the Altinity ClickHouse Operator.
This repository is based on lessons from a real EKS deployment, but it is intentionally generic. Organization names, AWS accounts, regions, domains, namespaces, image registries, database names, secret paths, resource sizes, and workload-specific SQL have been removed or replaced.
ClickHouse is straightforward to start but requires deliberate decisions around storage, replication, credentials, backups, monitoring, and upgrades before it becomes a reliable Kubernetes service. This repository documents a practical starting point without exposing a private infrastructure repository.
It includes a small single-node demo for learning and a production-oriented architecture guide. The demo is not a production template.
ClickHouse is an open-source, column-oriented SQL database designed for fast analytical queries over large datasets. Unlike a transactional database used for individual application records, ClickHouse is optimized for reading and aggregating many rows at once.
Typical uses include:
- application, access, and audit-log analysis;
- product and visitor analytics;
- time-series events and operational metrics;
- dashboards and near-real-time reporting;
- high-volume event ingestion;
- long-running aggregations that would be expensive in an OLTP database.
ClickHouse is usually a poor primary database for workflows dominated by frequent single-row updates, strict multi-row transactions, or relational application state. It often complements PostgreSQL or MySQL rather than replacing them.
The included demo deploys one ClickHouse pod and one EBS-backed persistent volume behind an internal Kubernetes service.
flowchart LR
C[Applications and data collectors] -->|HTTP 8123 or native 9000| S[ClusterIP Service]
S --> CH[ClickHouse pod]
CH --> EBS[(Amazon EBS PVC)]
CH -->|Metrics| P[Prometheus]
SM[AWS Secrets Manager] -. optional External Secrets .-> K8S[Kubernetes Secret]
K8S --> CH
CH -. native BACKUP / RESTORE .-> S3[(Amazon S3)]
A common highly available starting point is:
- one shard with two ClickHouse replicas;
- three ClickHouse Keeper replicas for coordination and quorum;
- ClickHouse replicas spread across Availability Zones;
- one EBS volume per ClickHouse and Keeper pod;
- internal-only services;
- credentials synchronized from AWS Secrets Manager;
- Prometheus scraping ClickHouse and Keeper metrics;
- scheduled native ClickHouse backups to S3;
- restore tests performed regularly.
flowchart TB
APP[Writers and readers] --> LB[Internal Kubernetes Service]
LB --> CH1[ClickHouse replica 1]
LB --> CH2[ClickHouse replica 2]
CH1 --> V1[(EBS volume A)]
CH2 --> V2[(EBS volume B)]
CH1 <--> K[3-node ClickHouse Keeper quorum]
CH2 <--> K
CH1 -. backup .-> S3[(S3 backup bucket)]
Replicas improve availability and hold additional copies of data. Shards divide data and increase capacity. Do not add shards merely to obtain high availability.
.
├── manifests/
│ └── demo/
│ ├── clickhouse-installation.yaml
│ ├── kustomization.yaml
│ └── namespace.yaml
└── scripts/
├── deploy-demo.sh
├── install-operator.sh
├── uninstall-demo.sh
└── verify-demo.sh
The demo uses:
- a
ClickHouseInstallationcustom resource; - one shard and one replica;
- an internal
ClusterIPservice; - an EBS-backed
ReadWriteOncevolume; - a non-default administrator whose password comes from a Kubernetes Secret;
- the default ClickHouse user disabled;
- a Prometheus metrics endpoint;
- a retained volume policy to reduce accidental data loss.
You need:
- an existing EKS cluster with EC2 worker nodes;
- access to the cluster through
kubectl; - Helm 3;
- a working EBS CSI driver;
- a suitable EBS StorageClass, commonly a
gp3class; opensslfor local password generation;- permissions to install cluster-wide CRDs and operator RBAC.
The EBS CSI driver requires AWS IAM permissions. AWS recommends installing it as an EKS add-on. EKS Auto Mode uses a different EBS provisioner, and EBS volumes cannot be attached to Fargate pods.
Verify the basics:
kubectl cluster-info
kubectl get nodes
kubectl get storageclass
helm versionMake sure kubectl points to the intended cluster before running any script:
aws eks update-kubeconfig \
--name <cluster-name> \
--region <aws-region>
kubectl config current-context./scripts/install-operator.shThe script installs the official Altinity Helm chart in a dedicated namespace and waits for the ClickHouse CRD. For repeatable production builds, set and test a specific chart version:
OPERATOR_CHART_VERSION=<tested-version> \
./scripts/install-operator.shThe demo script creates a namespace-local Kubernetes Secret and applies the Kustomize manifests.
CLICKHOUSE_ADMIN_PASSWORD="$(openssl rand -base64 32)" \
./scripts/deploy-demo.shThe password is not written to this repository. Store it in a password manager for the duration of the demo.
Optional settings:
CLICKHOUSE_ADMIN_PASSWORD="<strong-password>" \
./scripts/deploy-demo.shIf your cluster uses another namespace or StorageClass, update the demo manifests before deployment. Keep the namespace value consistent across namespace.yaml, kustomization.yaml, and the helper scripts.
./scripts/verify-demo.shThe script waits for the pod, displays the generated services and PVCs, and executes a basic query from inside the ClickHouse pod.
Find the pod and open a temporary local tunnel:
POD="$(kubectl get pod \
--namespace clickhouse-demo \
--selector clickhouse.altinity.com/chi=clickhouse-demo \
--output jsonpath='{.items[0].metadata.name}')"
kubectl port-forward \
--namespace clickhouse-demo \
"pod/${POD}" \
8123:8123 9000:9000In another terminal, obtain the demo password and query the HTTP interface:
CLICKHOUSE_PASSWORD="$(kubectl get secret clickhouse-credentials \
--namespace clickhouse-demo \
--output jsonpath='{.data.admin-password}' | base64 --decode)"
curl --fail --silent --show-error \
--user "admin:${CLICKHOUSE_PASSWORD}" \
'http://127.0.0.1:8123/?query=SELECT%20version()'Do not expose ports 8123 or 9000 directly to the public internet.
Open a ClickHouse client in the pod:
kubectl exec --namespace clickhouse-demo --stdin --tty "${POD}" -- \
clickhouse-client --user admin --password "${CLICKHOUSE_PASSWORD}"Then create and query a simple events table:
CREATE DATABASE IF NOT EXISTS analytics;
CREATE TABLE IF NOT EXISTS analytics.events
(
occurred_at DateTime,
event_name LowCardinality(String),
account_id UInt64,
properties String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(occurred_at)
ORDER BY (event_name, occurred_at)
TTL occurred_at + INTERVAL 90 DAY;
INSERT INTO analytics.events VALUES
(now(), 'page_view', 1001, '{"path":"/docs"}'),
(now(), 'sign_in', 1001, '{}'),
(now(), 'page_view', 1002, '{"path":"/pricing"}');
SELECT
event_name,
count() AS events
FROM analytics.events
GROUP BY event_name
ORDER BY events DESC;The partition, ordering key, and TTL are part of the data model. Choose them from real query and retention requirements, not by copying this example.
The helper script wraps the operator's official Helm chart:
helm repo add clickhouse-operator https://helm.altinity.com
helm repo update clickhouse-operator
helm upgrade --install clickhouse-operator \
clickhouse-operator/altinity-clickhouse-operator \
--namespace clickhouse-operator \
--create-namespaceReview chart values and RBAC before using this in a controlled environment. Pin the chart version in GitOps or CI instead of silently following the latest release.
The operator also publishes an installation bundle. Pin a release tag rather than referencing the moving master branch:
kubectl apply --filename \
"https://raw.githubusercontent.com/Altinity/clickhouse-operator/<operator-version>/deploy/operator/clickhouse-operator-install-bundle.yaml"Use either Helm or the bundle, not both.
- Use the EBS CSI driver and an encrypted StorageClass.
- Use
volumeBindingMode: WaitForFirstConsumerso scheduling can select an appropriate Availability Zone. - EBS volumes are Availability Zone scoped. A pod cannot attach the same volume in another zone without recovery or migration.
- Spread replicas across zones; do not expect one EBS volume to provide multi-zone availability.
- Use a reclaim policy appropriate for the data. Production database volumes should normally be retained when a claim or workload is removed.
- Monitor free disk space, part counts, merge pressure, and volume limits.
For a busy production cluster, use a dedicated EKS node group with labels and taints. Configure node selectors, tolerations, and topology spread constraints in the ClickHouse pod template.
Do not add dedicated-node constraints to the demo until matching nodes exist, or the pods will remain Pending.
The demo creates a normal Kubernetes Secret for simplicity. For production, use AWS Secrets Manager with a controller such as External Secrets Operator and EKS Pod Identity or IRSA.
Use separate identities for:
- administration;
- application ingestion;
- read-only dashboards;
- metrics exporters;
- backups.
Grant each identity access only to the required databases and operations. Disable or secure the default user and limit allowed networks.
Never commit passwords, AWS keys, S3 credentials, internal hostnames, or rendered Secrets.
- Prefer
ClusterIPfor clients running in the same cluster. - If clients require VPC access, use an internal load balancer and restrictive security groups.
- Apply Kubernetes NetworkPolicies where the cluster networking plugin supports them.
- Use TLS for traffic that crosses trust boundaries.
- Limit access to HTTP
8123, native TCP9000, Keeper, and metrics ports separately.
Before adapting this example for production:
- Pin ClickHouse, Keeper, and operator versions.
- Read release notes and test upgrades with representative data.
- Use at least two replicas when availability requires it.
- Run an odd Keeper quorum, normally three nodes, for replicated tables.
- Spread ClickHouse and Keeper pods across failure domains.
- Set realistic CPU and memory requests and limits from load tests.
- Use encrypted, retained EBS volumes with sufficient IOPS and throughput.
- Keep services private and restrict network paths.
- Replace the demo Secret with AWS Secrets Manager integration.
- Remove or secure the default user and apply least-privilege grants.
- Configure table and system-log TTLs to prevent unbounded growth.
- Scrape ClickHouse and Keeper metrics and alert on capacity and replication health.
- Schedule native backups to S3 and test restoring them.
- Define disruption budgets and a controlled maintenance process.
- Document recovery from a lost pod, volume, zone, Keeper member, and cluster.
ClickHouse Keeper coordinates replication and distributed DDL. For a production replicated deployment, use an odd number of Keeper nodes so the cluster can maintain quorum; three is a common starting point.
The production pattern that informed this repository uses one shard with two replicas and a separate three-member Keeper quorum. That topology protects against a single ClickHouse replica failure without introducing sharding complexity.
Use replicated engines for replicated data, for example:
CREATE TABLE analytics.events_local
ON CLUSTER analytics_cluster
(
occurred_at DateTime,
event_name LowCardinality(String),
account_id UInt64
)
ENGINE = ReplicatedMergeTree
PARTITION BY toYYYYMM(occurred_at)
ORDER BY (event_name, occurred_at);Do not change replica counts or Keeper topology casually on a stateful production cluster. Test operator behavior, persistent-volume retention, schema engines, and recovery procedures first.
The demo enables ClickHouse's Prometheus endpoint. If Prometheus Operator is installed, create a ServiceMonitor matching the generated ClickHouse service labels and metrics port.
At minimum, alert on:
- pod restarts and unavailable replicas;
- Keeper quorum or session failures;
- free disk space and inode exhaustion;
- memory pressure and killed queries;
- delayed inserts and rejected connections;
- replication queue growth and replica delay;
- excessive active parts and failed merges;
- backup failures and missing restore tests;
- query latency and error-rate changes.
Application-specific SQL metrics can be exported separately, but protect exporter credentials with a read-only profile and short query timeouts.
Replication is not a backup. It can reproduce accidental deletions and bad writes across replicas.
ClickHouse supports native BACKUP and RESTORE to S3. A practical schedule may combine periodic full backups with more frequent incremental backups. Give the backup pod or ClickHouse workload only the S3 permissions it requires, preferably through EKS Pod Identity or IRSA rather than static AWS keys.
Example syntax:
BACKUP DATABASE analytics
TO S3('https://<bucket>.s3.<aws-region>.amazonaws.com/clickhouse/full-<timestamp>');
RESTORE DATABASE analytics AS analytics_restored
FROM S3('https://<bucket>.s3.<aws-region>.amazonaws.com/clickhouse/full-<timestamp>');Exact authentication and endpoint syntax depends on the AWS identity model and ClickHouse version. Review the official backup documentation before implementation.
Backups are complete only when you can restore them. Regularly restore into an isolated namespace and verify schemas, row counts, representative queries, permissions, and recovery time.
- Read the ClickHouse and operator release notes.
- Back up the databases and verify recent restore evidence.
- Test the exact version transition on non-production data.
- Render and review the manifests before applying them.
- Upgrade one controlled step at a time.
- Verify Keeper, replication queues, merges, queries, and ingestion before continuing.
- Keep a documented rollback or recovery plan.
Avoid unpinned latest image tags.
kubectl describe pod --namespace clickhouse-demo <pod-name>
kubectl get pvc --namespace clickhouse-demo
kubectl get storageclassCommon causes are a missing StorageClass, EBS CSI permissions, incompatible node selectors or taints, insufficient capacity, and Availability Zone constraints.
kubectl get secret clickhouse-credentials --namespace clickhouse-demoRun deploy-demo.sh with CLICKHOUSE_ADMIN_PASSWORD set. For External Secrets, inspect the ExternalSecret, SecretStore, controller logs, and AWS IAM permissions.
kubectl get crd clickhouseinstallations.clickhouse.altinity.com
kubectl get pods --namespace clickhouse-operator
kubectl get clickhouseinstallation --all-namespacesThen inspect the ClickHouseInstallation status and operator logs.
kubectl get service --namespace clickhouse-demo
kubectl get endpoints --namespace clickhouse-demo
kubectl get networkpolicy --namespace clickhouse-demoVerify the selected port, service type, security groups, NetworkPolicies, user network rules, and TLS settings.
./scripts/uninstall-demo.shThe script removes the demo custom resource, Secret, and namespace only after confirmation. Persistent volumes may remain because the example favors retention. Inspect and delete retained EBS volumes manually only after confirming the data is disposable.
The operator is shared infrastructure and is not removed by the demo uninstall script.
This repository contains no credentials and is not intended to identify or reproduce a specific private environment. Before publishing a fork, scan the entire Git history as well as the current files for:
- AWS account IDs and ARNs;
- private registry URLs;
- internal DNS names and domains;
- cluster, namespace, database, and customer names;
- Secrets Manager paths and Kubernetes Secret values;
- source comments that describe private systems;
- dashboards, queries, or schemas containing business identifiers.
- ClickHouse documentation
- ClickHouse replication
- ClickHouse backup and restore to S3
- Altinity ClickHouse Operator
- Altinity operator installation
- Amazon EBS CSI driver for EKS
- External Secrets with AWS Secrets Manager
This is an educational reference, not a drop-in production platform. Capacity, topology, durability, security, and recovery requirements vary by workload. Validate every change in a non-production EKS cluster and obtain an experienced review before storing important data.