Triển khai ClickHouse trên Kubernetes với StatefulSet
Chúng ta sẽ sử dụng StatefulSet để triển khai ClickHouse vì tính chất của nó cần lưu trữ trạng thái (stateful) và đảm bảo thứ tự khởi động, gán tên host cố định cho từng pod. Đây là phương pháp ổn định nhất để xây dựng cluster nội bộ mà không cần phụ thuộc vào operator bên thứ ba.
Cấu hình PersistentVolumeClaim và ConfigMap
Đầu tiên, ta cần tạo ConfigMap chứa file cấu hình config.xml và users.xml tùy chỉnh, sau đó tạo StatefulSet để khởi tạo 3 node (replicas) cho cluster.
Tạo file clickhouse-config.yaml với nội dung hoàn chỉnh dưới đây. File này định nghĩa Storage, Resource Limits và cấu hình mạng cho từng pod.
apiVersion: v1
kind: ConfigMap
metadata:
name: clickhouse-config
namespace: data-platform
data:
config.xml: |
information
/var/log/clickhouse-server/clickhouse-server.log
/var/log/clickhouse-server/clickhouse-server.err.log
100M
3
0.9
16
100
0.0.0.0
8123
9000
9009
/var/lib/clickhouse/
/etc/clickhouse-server/users.d/
/var/lib/clickhouse/tmp/
/var/lib/clickhouse/user_files/
5368709120
5368709120
users.xml: |
10000000000
1
::/0
default
default
3600
0
0
0
0
0
0
0
0
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: clickhouse-cluster
namespace: data-platform
spec:
serviceName: clickhouse-headless
replicas: 3
selector:
matchLabels:
app: clickhouse
template:
metadata:
labels:
app: clickhouse
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- clickhouse
topologyKey: kubernetes.io/hostname
containers:
- name: clickhouse
image: clickhouse/clickhouse-server:24.3
ports:
- containerPort: 8123
name: http
- containerPort: 9000
name: native
- containerPort: 9009
name: interserver
env:
- name: CLICKHOUSE_HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CLICKHOUSE_USER
value: "default"
- name: CLICKHOUSE_PASSWORD
value: "clickhouse_password"
- name: CLICKHOUSE_DB
value: "analytics"
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "8Gi"
cpu: "4000m"
volumeMounts:
- name: data
mountPath: /var/lib/clickhouse
- name: config
mountPath: /etc/clickhouse-server/config.xml
subPath: config.xml
- name: config
mountPath: /etc/clickhouse-server/users.xml
subPath: users.xml
livenessProbe:
httpGet:
path: /ping
port: 8123
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ping
port: 8123
initialDelaySeconds: 30
periodSeconds: 10
volumes:
- name: config
configMap:
name: clickhouse-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
name: clickhouse-headless
namespace: data-platform
labels:
app: clickhouse
spec:
clusterIP: None
ports:
- name: http
port: 8123
targetPort: 8123
- name: native
port: 9000
targetPort: 9000
- name: interserver
port: 9009
targetPort: 9009
selector:
app: clickhouse
---
apiVersion: v1
kind: Service
metadata:
name: clickhouse-cluster
namespace: data-platform
labels:
app: clickhouse
spec:
ports:
- name: http
port: 8123
targetPort: 8123
selector:
app: clickhouse
Kết quả mong đợi: Kubernetes sẽ tạo 3 pod với tên clickhouse-cluster-0, clickhouse-cluster-1, clickhouse-cluster-2 cùng với 2 Service (Headless cho DNS nội bộ và ClusterIP cho truy cập ngoài). Các pod sẽ mount PersistentVolume và load config từ ConfigMap.
Chạy lệnh sau để áp dụng cấu hình:
kubectl apply -f clickhouse-config.yaml
Verify kết quả bằng cách kiểm tra trạng thái của các pod:
kubectl get pods -n data-platform -l app=clickhouse
Bạn sẽ thấy 3 pod ở trạng thái Running sau khoảng 2-3 phút khởi động.
Cấu hình Cluster và ReplicatedMergeTree
Để ClickHouse hoạt động như một cụm phân tán thực sự, chúng ta cần cấu hình remote_servers để các node nhận biết nhau và sử dụng ReplicatedMergeTree làm engine lưu trữ dữ liệu. Engine này đảm bảo dữ liệu được đồng bộ (replication) giữa các node.
Tạo macro cho Cluster
Chúng ta cần cập nhật file config.xml để định nghĩa cluster logic. Thay vì sửa lại ConfigMap cũ, ta sẽ tạo một ConfigMap bổ sung hoặc sửa lại phần macros trong config.xml thông qua môi trường hoặc file cấu hình.
Tạo file clickhouse-cluster-macros.yaml để định nghĩa macro cluster_name và shard_id cho từng pod dựa trên index của StatefulSet.
apiVersion: v1
kind: ConfigMap
metadata:
name: clickhouse-cluster-macros
namespace: data-platform
data:
macros.xml: |
analytics_cluster
__POD_INDEX__
__POD_INDEX__
Tuy nhiên, cách tốt nhất trong Kubernetes là dùng initContainer hoặc script khởi động để tự động sinh macro dựa trên tên pod. Ta sẽ thêm volume mount cho macros và sửa lại StatefulSet ở trên để mount file này.
Sửa lại phần volumes và volumeMounts trong StatefulSet cũ (bỏ qua việc viết lại toàn bộ file, chỉ thêm phần này vào config cũ):
# Thêm vào phần volumes của StatefulSet
- name: macros
configMap:
name: clickhouse-cluster-macros
# Thêm vào phần volumeMounts của container clickhouse
- name: macros
mountPath: /etc/clickhouse-server/config.d/02-macros.xml
subPath: macros.xml
Để đơn giản hóa việc deploy, ta sẽ tạo một script bash trong container để tự động cấu hình macros.xml dựa trên biến môi trường CLICKHOUSE_HOSTNAME. Đây là cách chuẩn để làm việc với StatefulSet.
Thay đổi phần command trong container để chạy script khởi động trước khi vào clickhouse-server:
command:
- /bin/bash
- -c
- |
set -e
echo "Configuring macros for $(hostname)"
echo 'analytics_cluster00' > /etc/clickhouse-server/config.d/01-macros.xml
clickhouse-server
Để làm việc với 3 node thực sự, ta cần cấu hình remote_servers trong config.xml để trỏ đến 3 địa chỉ IP nội bộ của cluster. Ta sẽ dùng clickhouse-client để cấu hình cluster sau khi pod chạy, hoặc dùng clickhouse-local để tạo cấu hình ban đầu.
Tạo file clickhouse-cluster-config.yaml để cập nhật config.xml với cấu hình cluster thực tế:
apiVersion: v1
kind: ConfigMap
metadata:
name: clickhouse-config
namespace: data-platform
data:
config.xml: |
information
/var/log/clickhouse-server/clickhouse-server.log
/var/log/clickhouse-server/clickhouse-server.err.log
100M
3
0.9
16
100
0.0.0.0
8123
9000
9009
/var/lib/clickhouse/
/etc/clickhouse-server/users.d/
/var/lib/clickhouse/tmp/
/var/lib/clickhouse/user_files/
5368709120
5368709120
clickhouse-cluster-0.clickhouse-headless.data-platform.svc.cluster.local
9000
clickhouse-cluster-1.clickhouse-headless.data-platform.svc.cluster.local
9000
clickhouse-cluster-2.clickhouse-headless.data-platform.svc.cluster.local
9000
zookeeper-0.zookeeper.data-platform.svc.cluster.local
2181
zookeeper-1.zookeeper.data-platform.svc.cluster.local
2181
zookeeper-2.zookeeper.data-platform.svc.cluster.local
2181
users.xml: |
10000000000
1
::/0
default
default
3600
0
0
0
0
0
0
0
0
Lưu ý: Cấu hình trên yêu cầu Zookeeper đã được triển khai trước đó trong namespace data-platform. Nếu chưa có Zookeeper, hãy bỏ qua phần ... hoặc triển khai nó trước.
Khởi động lại cluster để áp dụng cấu hình mới:
kubectl delete pod -n data-platform -l app=clickhouse
Verify kết quả bằng cách vào một trong các pod và kiểm tra cấu hình cluster:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -q "SELECT * FROM system.clusters"
Kết quả mong đợi: Bạn sẽ thấy bảng liệt kê 3 host thuộc cluster analytics_cluster với trạng thái 1 (active).
Tối ưu hóa tham số bộ nhớ và Disk I/O
Để đạt hiệu suất cao cho việc phân tích thời gian thực (real-time), ClickHouse cần được tối ưu hóa về bộ nhớ đệm (cache) và I/O disk. Chúng ta sẽ điều chỉnh các tham số quan trọng trong config.xml.
Cấu hình Memory và Merge Tree
Tham số max_insert_block_size và max_insert_threads ảnh hưởng trực tiếp đến tốc độ ghi dữ liệu. Đối với stream processing từ Kafka, ta cần tăng kích thước block để giảm overhead khi merge.
Cập nhật lại file config.xml (trong ConfigMap) với các tham số tối ưu sau:
1048576
4
10
10000
1000
17179869184
17179869184
107374182400
300
3600
60
60
4
4
1000
1800
3600
100
100
16
0.2
16
0.2
0.2
0.2
1
0
1
Các tham số trên giúp ClickHouse sử dụng mmap và io_uring (nếu kernel hỗ trợ) để giảm chi phí context switch, đồng thời điều chỉnh ngưỡng merge để tránh tắc nghẽn disk khi dữ liệu đến ồ ạt.
Áp dụng lại ConfigMap và restart pod:
kubectl apply -f clickhouse-config.yaml
kubectl delete pod -n data-platform -l app=clickhouse
Verify kết quả bằng cách kiểm tra các tham số trong runtime:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -q "SELECT name, value FROM system.settings WHERE name LIKE '%merge%' OR name LIKE '%io%' LIMIT 20"
Bạn sẽ thấy các giá trị đã được cập nhật đúng như cấu hình.
Tạo bảng và chỉ mục (Index) cho phân tích
Giờ là lúc tạo bảng dữ liệu thực tế. Chúng ta sẽ sử dụng engine ReplicatedMergeTree để đảm bảo dữ liệu được phân tán và dự phòng. Cấu trúc bảng cần tối ưu cho query phân tích (aggregation, filtering).
Thiết kế bảng ReplicatedMergeTree
Chúng ta sẽ tạo một bảng analytics_events để lưu dữ liệu sự kiện từ Kafka. Bảng này sẽ có ORDER BY theo event_time và user_id để tối ưu hóa truy vấn thời gian và người dùng.
Chạy lệnh SQL trực tiếp vào pod đầu tiên của cluster để tạo bảng:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "
CREATE TABLE IF NOT EXISTS analytics_events (
event_time DateTime64(3),
user_id UInt64,
event_type String,
session_id String,
ip_address String,
user_agent String,
payload String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/analytics_events', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (user_id, event_time)
TTL event_time + INTERVAL 90 DAY
SETTINGS index_granularity = 8192;
"
Giải thích các thành phần:
ReplicatedMergeTree: Engine phân tán, sử dụng Zookeeper để quản lý metadata.
PATH: Đường dẫn logic trong Zookeeper, tự động thay thế {shard} và {replica} dựa trên macro đã cấu hình.
ORDER BY: Quan trọng nhất. Dữ liệu sẽ được sắp xếp theo user_id rồi đến event_time. Query lọc theo user_id hoặc range event_time sẽ rất nhanh.
TTL: Tự động xóa dữ liệu sau 90 ngày để quản lý disk.
index_granularity: Kích thước granule mặc định là 8192 hàng, phù hợp cho phần cứng hiện đại.
Verify kết quả bằng cách liệt kê bảng trong database:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "SHOW CREATE TABLE analytics_events"
Bạn sẽ thấy định nghĩa bảng đầy đủ cùng với cấu trúc ReplicatedMergeTree.
Tối ưu Index (Skip Index)
ClickHouse sử dụng Skip Index (BloomFilter, MinMax, Ngram) để tăng tốc độ query trên các cột không nằm trong ORDER BY. Ta sẽ thêm index cho event_type vì đây là cột thường xuyên dùng để lọc.
Chạy lệnh thêm index:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "
ALTER TABLE analytics_events ADD INDEX event_type_idx event_type TYPE bloom_filter GRANULARITY 4;
"
Index bloom_filter giúp loại bỏ nhanh các granule không chứa giá trị event_type cụ thể, giảm đáng kể lượng I/O khi query.
Verify index đã được tạo:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "SHOW CREATE TABLE analytics_events"
Trong kết quả, bạn sẽ thấy dòng INDEX event_type_idx....
Chèn dữ liệu mẫu và kiểm tra phân tán
Để đảm bảo dữ liệu được phân tán đều và replication hoạt động, ta sẽ chèn một lượng dữ liệu mẫu và kiểm tra số lượng phần tử trên từng node.
Chạy lệnh chèn dữ liệu (sử dụng generateRandom để tạo 1 triệu dòng nhanh chóng):
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "
INSERT INTO analytics_events
SELECT
now() - toIntervalSecond(number % 3600) as event_time,
number % 1000000 as user_id,
concat('event_', toString(number % 10)) as event_type,
concat('session_', toString(number)) as session_id,
'192.168.1.1' as ip_address,
'Mozilla/5.0' as user_agent,
'{}' as payload
FROM numbers(1000000);
"
Verify kết quả bằng cách đếm số dòng trên toàn cluster:
kubectl exec -it clickhouse-cluster-0 -n data-platform -- clickhouse-client -d analytics -q "
SELECT count() FROM analytics_events ON analytics_cluster;
"
Kết quả mong đợi: Số lượng dòng phải xấp xỉ 1.000.000. Lệnh ON analytics_cluster đảm bảo query được chạy song song trên tất cả các shard và tổng hợp kết quả.
Để kiểm tra sự phân tán đều, hãy kiểm tra số lượng dòng trên từng pod:
for i in 0 1 2; do echo "Pod $i:"; kubectl exec -it clickhouse-cluster-$i -n data-platform -- clickhouse-client -d analytics -q "SELECT count() FROM analytics_events"; done
Bạn sẽ thấy số lượng dòng trên 3 pod xấp xỉ nhau (khoảng 333.333 mỗi pod), chứng tỏ dữ liệu đã được phân tán (sharding) đúng theo ORDER BY user_id.
Điều hướng series:
Mục lục: Series: Series: Xây dựng hệ thống Real-time Analytics và Stream Processing với Apache Kafka, Flink và ClickHouse trên Kubernetes
« Phần 2: Triển khai Apache Kafka trên Kubernetes với Strimzi Operator
Phần 4: Cài đặt Apache Flink trên Kubernetes với Kubernetes Operator »