ClickHouse w Docker: jak przestałem się bać i uruchomiłem analitykę w 2 minuty
Pamiętam, jak pierwszy raz stawiałem ClickHouse na produkcji. Cztery godziny zajęły uprawnienia, limity, ręczna edycja konfigów, restartowanie systemd. Po miesiącu przyszedł nowy programista, próbowaliśmy odtworzyć środowisko na jego maszynie — i znowu te same grabie.
Docker rozwiązał wszystko. Teraz mam jeden folder z docker-compose.yml, który przenoszę między projektami. Podniosłem klaster analityczny w minutę, a gdy trzeba usunąć — docker-compose down -v i czysto. Żadnego śmiecia w systemie.
Poniżej — trzy gotowe scenariusze, których używam w realnych projektach (od startupowego pet-projektu po analitykę bukmacherską). Wszystkie konfigi sprawdzone na Docker Engine 24+.
Scenariusz 1. Szybki start: jedna komenda do sprawdzenia hipotezy
Do lokalnego rozwoju i szybkiego prototypowania wystarczy jedna linia. Ale nie tylko docker run clickhouse/clickhouse-server — dodajmy to, bez czego ClickHouse jest bezużyteczny: trwałe przechowywanie i przekierowanie portów.
docker run -d \
--name clickhouse-dev \
--restart unless-stopped \
-p 8123:8123 \
-p 9000:9000 \
-v clickhouse-data:/var/lib/clickhouse \
-v clickhouse-logs:/var/log/clickhouse-server \
-e CLICKHOUSE_DB=analytics \
-e CLICKHOUSE_USER=developer \
-e CLICKHOUSE_PASSWORD=devpass123 \
-e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \
clickhouse/clickhouse-server:latest
Co tu jest ważne:
-v clickhouse-data— nazwany wolumin, a nie bind mount. Różnica: woluminem zarządza Docker, nie ginie przy restarcie, działa szybciej na macOS (ważne, jeśli masz MacBooka — bind mounty spowalniają przez synchronizację).CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1— włącza zarządzanie dostępem. Bez tej zmiennej użytkownikdeveloperjest tworzony, ale nie może tworzyć nowych kont. Oparzyliśmy się na tym: na produkcji musieliśmy wchodzić do kontenera i edytowaćusers.xml.- Porty 9000 (protokół natywny) i 8123 (HTTP) — zawsze otwieram oba, bo połowa klientów (DBeaver, TablePlus) działa przez HTTP, a aplikacje przez natywny sterownik.
Sprawdzamy, czy wystartowało:
# Interfejs HTTP — najprostszy test
curl "http://localhost:8123/?query=SELECT+version()"
# Wynik: 24.8.2.3
Scenariusz 2. Docker Compose do rozwoju (jedna nody, healthcheck)
Gdy projekt jest nieco bardziej złożony, od razu przechodzę na docker-compose.yml. Tego pliku używam na swoich laptopach i serwerach deweloperskich:
version: '3.8'
services:
clickhouse:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-dev
hostname: clickhouse
ports:
- "8123:8123"
- "9000:9000"
- "9009:9009"
volumes:
- clickhouse-data:/var/lib/clickhouse
- clickhouse-logs:/var/log/clickhouse-server
- ./config/config.d:/etc/clickhouse-server/config.d
- ./config/users.d:/etc/clickhouse-server/users.d
environment:
CLICKHOUSE_DB: betting_analytics
CLICKHOUSE_USER: analyst
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-analyst123}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
ulimits:
nofile:
soft: 262144
hard: 262144
nproc:
soft: 32768
hard: 32768
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks:
- analytics-net
networks:
analytics-net:
driver: bridge
volumes:
clickhouse-data:
clickhouse-logs:
Dlaczego dodałem ulimits: na produkcji ClickHouse zużywa do 262144 otwartych deskryptorów plików. Bez tego przy dużym obciążeniu wyleci z błędem Too many open files. Te grabie kosztowały mnie kiedyś trzy godziny, gdy serwer zaczął padać po 2 mln wierszy.
Healthcheck przez /ping: ClickHouse ma wbudowany endpoint /ping (zwraca "Ok." jeśli żyje). To lepsze niż sprawdzanie przez SELECT 1, bo nie wymaga uwierzytelniania i nie zapisuje w logach.
Zmienna z domyślną wartością: ${CLICKHOUSE_PASSWORD:-analyst123} — jeśli nie ustawisz w .env, hasło będzie analyst123. Nie zapomnij o pliku .env dla prawdziwych sekretów.
Scenariusz 3. Środowisko produkcyjne: ClickHouse + Zookeeper do replikacji
Replikacja tabel ClickHouse wymaga ZooKeepera (lub ClickHouse Keeper, ale na początek stawiam klasykę). Ten compose używam do testowania odporności na awarie:
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
volumes:
- zookeeper-data:/var/lib/zookeeper
networks:
- ch-cluster
clickhouse-1:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-1
hostname: clickhouse-1
ports:
- "8123:8123"
- "9000:9000"
volumes:
- ch1-data:/var/lib/clickhouse
- ./config/replicated.xml:/etc/clickhouse-server/config.d/replicated.xml
environment:
CLICKHOUSE_DB: bets
CLICKHOUSE_USER: replicator
CLICKHOUSE_PASSWORD: rep_pass
CLICKHOUSE_SHARD: 1
CLICKHOUSE_REPLICA: 1
depends_on:
- zookeeper
ulimits:
nofile:
soft: 262144
hard: 262144
networks:
- ch-cluster
clickhouse-2:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-2
hostname: clickhouse-2
ports:
- "8124:8123" # druga instancja na innym porcie
- "9001:9000"
volumes:
- ch2-data:/var/lib/clickhouse
- ./config/replicated.xml:/etc/clickhouse-server/config.d/replicated.xml
environment:
CLICKHOUSE_DB: bets
CLICKHOUSE_USER: replicator
CLICKHOUSE_PASSWORD: rep_pass
CLICKHOUSE_SHARD: 1
CLICKHOUSE_REPLICA: 2
depends_on:
- zookeeper
ulimits:
nofile:
soft: 262144
hard: 262144
networks:
- ch-cluster
networks:
ch-cluster:
driver: bridge
volumes:
zookeeper-data:
ch1-data:
ch2-data:
A oto zawartość config/replicated.xml (montujemy w obu kontenerach):
<clickhouse>
<zookeeper>
<node>
<host>zookeeper</host>
<port>2181</port>
</node>
</zookeeper>
<remote_servers>
<replicated_cluster>
<shard>
<replica>
<host>clickhouse-1</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse-2</host>
<port>9000</port>
</replica>
</shard>
</replicated_cluster>
</remote_servers>
<macros>
<shard>1</shard>
<replica>${CLICKHOUSE_REPLICA}</replica>
</macros>
</clickhouse>
Kiedy to jest naprawdę potrzebne: W projekcie produkcyjnym z kasynem straciliśmy dane, bo trzymaliśmy wszystko na jednej nodzie. Potem zawsze podnoszę co najmniej dwa replikowane kontenery do testów. Różnica w cenie — dwa kontenery zamiast jednego, ale spokojny sen jest droższy.
Scenariusz 4. Pełne środowisko gamingowe: ClickHouse + Kafka + Redis
Do analizy zakładów w czasie rzeczywistym potrzebuję streamingu (Kafka) i cache (Redis). Ten compose używam do lokalnego debugowania potoku:
version: '3.8'
services:
zookeeper-kafka:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper-kafka
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper-kafka:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
ports:
- "9092:9092"
redis:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-cachepass}
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
clickhouse:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-betting
ports:
- "8123:8123"
- "9000:9000"
volumes:
- clickhouse-betting-data:/var/lib/clickhouse
- ./clickhouse-kafka.xml:/etc/clickhouse-server/config.d/kafka.xml
environment:
CLICKHOUSE_DB: betting
CLICKHOUSE_USER: streamer
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PW:-stream123}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
ulimits:
nofile:
soft: 262144
hard: 262144
depends_on:
- kafka
- redis
kafka-connector:
image: clickhouse/clickhouse-kafka-connect:latest
container_name: kafka-connector
depends_on:
- kafka
- clickhouse
environment:
CONNECT_BOOTSTRAP_SERVERS: kafka:9092
CONNECT_GROUP_ID: clickhouse-group
CONNECT_CONFIG_STORAGE_TOPIC: connect-configs
CONNECT_OFFSET_STORAGE_TOPIC: connect-offsets
CONNECT_STATUS_STORAGE_TOPIC: connect-status
CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter
CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
ports:
- "8083:8083"
volumes:
redis-data:
clickhouse-betting-data:
Jak to wykorzystać w kodzie aplikacji:
# Przykład w Python: czytamy zakład z Redis (cache), piszemy do ClickHouse
import redis
from kafka import KafkaProducer
import json
r = redis.Redis(host='localhost', port=6379, password='cachepass', decode_responses=True)
producer = KafkaProducer(bootstrap_servers='localhost:9092', value_serializer=lambda v: json.dumps(v).encode())
# Sprawdzenie duplikatu (wykrywanie oszustw)
bet_id = "bet_12345"
if r.setnx(bet_id, "processed"):
bet_event = {"user_id": 101, "amount": 500, "odds": 2.1}
producer.send('bets-stream', bet_event)
else:
print(f"Zablokowano duplikat zakładu {bet_id}")
Jak zamontować własny config.xml i nie zepsuć wszystkiego
Błąd, który popełniłem pięć razy: montowałem pełny config.xml, a w nowej wersji ClickHouse dodali obowiązkowe sekcje. Kontener padał z Config has no <logger>.
Prawidłowe podejście: umieszczać tylko nadpisywania w config.d/. Oto struktura, która działa:
docker-clickhouse/
├── docker-compose.yml
├── .env
├── config/
│ ├── config.d/
│ │ ├── memory.xml
│ │ ├── networks.xml
│ │ └── query-log.xml
│ └── users.d/
│ └── profiles.xml
Przykład config/config.d/memory.xml:
<clickhouse>
<max_server_memory_usage>0.75</max_server_memory_usage>
<max_memory_usage_for_all_queries>0</max_memory_usage_for_all_queries>
<background_pool_size>16</background_pool_size>
</clickhouse>
Przykład config/users.d/profiles.xml:
<clickhouse>
<profiles>
<default>
<max_memory_usage>10000000000</max_memory_usage>
<timeout_before_checking_execution_speed>0</timeout_before_checking_execution_speed>
</default>
<analyst>
<readonly>1</readonly>
<max_execution_time>300</max_execution_time>
</analyst>
</profiles>
</clickhouse>
Praca z clickhouse-client wewnątrz kontenera
Wchodzenie do kontenera w celu szybkich zapytań jest normalne. Ale nie przez docker exec -it bash, tylko bezpośrednio:
# Wykonanie zapytania
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM system.tables"
# Tryb interaktywny
docker exec -it clickhouse-dev clickhouse-client
# Z hasłem
docker exec -it clickhouse-dev clickhouse-client --password devpass123
Mój lifehack: Dodaję alias w ~/.bashrc:
alias ch-cli='docker exec -it clickhouse-dev clickhouse-client'
Potem po prostu piszę ch-cli i pracuję jak z lokalną bazą.
Zmienne środowiskowe: co naprawdę działa
Oficjalny obraz nie obsługuje wszystkich zmiennych, które obiecują fora. Oto sprawdzone:
| Zmienna | Przeznaczenie | Przykład |
|---|---|---|
CLICKHOUSE_DB |
Nazwa domyślnej bazy danych | analytics |
CLICKHOUSE_USER |
Użytkownik-admin | prod_user |
CLICKHOUSE_PASSWORD |
Hasło | strongpass |
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT |
Włączenie RBAC (1/0) | 1 |
Co NIE DZIAŁA: CLICKHOUSE_HTTP_PORT, CLICKHOUSE_TCP_PORT — są ignorowane przez entrypoint. Zmieniaj porty przez ports: w compose lub przez montowanie konfigu.
Sprawdzenie działania: mój pełny checklist
Po uruchomieniu dowolnego compose'a wykonuję:
# 1. HTTP ping (powinien zwrócić "Ok.")
curl http://localhost:8123/ping
# 2. Wersja przez HTTP
curl "http://localhost:8123/?query=SELECT+version()"
# 3. Utworzenie tabeli testowej
docker exec -it clickhouse-dev clickhouse-client --query "CREATE TABLE test.t (id UInt64) ENGINE = MergeTree ORDER BY id"
# 4. Wstawienie i odczyt
docker exec -it clickhouse-dev clickhouse-client --query "INSERT INTO test.t SELECT number FROM numbers(1000)"
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM test.t"
# 5. HTTP z uwierzytelnianiem (jeśli ustawiono hasło)
curl -u developer:devpass123 "http://localhost:8123/?query=SELECT+user()"
Co zrobić, jeśli nie działa — typowe błędy w Docker
Błąd: Code: 210. DB::NetException: Connection refused
Rozwiązanie: kontener nie zdążył wystartować. Dodaj depends_on i healthcheck, albo zrób sleep 5 w skryptach.
Błąd: Cannot create directory /var/lib/clickhouse: Permission denied
Rozwiązanie: na hoście z SELinux dodaj :Z do woluminu: -v ./data:/var/lib/clickhouse:Z. Lub użyj nazwanych woluminów.
Błąd: Max connections limit reached
Rozwiązanie: zwiększ w konfigu: <max_connections>4096</max_connections> i zrestartuj.
Pamięć kontenera zżera host Rozwiązanie: ogranicz przez Docker:
docker update --memory=4g --memory-swap=4g clickhouse-dev
Lub w compose:
deploy:
resources:
limits:
memory: 4G
Podsumowanie: kiedy Docker — a kiedy nie
Docker dla ClickHouse jest idealny w dev, stagingu i małych produkcjach. Ale jeśli masz klaster z 10+ nodami i 100 TB danych — lepiej natywne pakiety bez dodatkowych warstw.
A na razie — weź mój compose do środowiska gamingowego, zmień hasła i zacznij liczyć zakłady w czasie rzeczywistym.
Wszystkie konfigi pochodzą z realnych projektów. Nazwy zmienione, grabie pozostały.
← Poprzedni: Instalacja ClickHouse na Ubuntu/Debian: instrukcja krok po kroku od kogoś, kto sparzył się na nieprawidłowych uprawnieniach
→ Następny: ClickHouse client: jak zaprzyjaźniłem się z konsolą i HTTP API w projekcie gamblingowym
— Editorial Team
Brak komentarzy.