Volver al inicio

ClickHouse en Docker: lanza análisis en 2 minutos

Guía para ejecutar ClickHouse en Docker con cuatro escenarios listos: docker run básico, docker-compose para desarrollo con healthcheck, clúster con ZooKeeper para replicación, stack completo ClickHouse + Kafka + Redis para análisis de juegos. Se explican variables de entorno, montaje de configuraciones personalizadas, errores típicos y sus soluciones.

ClickHouse en Docker: 4 escenarios listos con archivos compose
Advertisement 728x90

ClickHouse en Docker: Cómo dejé de preocuparme y lancé análisis en 2 minutos

Por qué Docker — y todo lo demás viene después

Recuerdo la primera vez que configuré ClickHouse en producción. Me llevó cuatro horas configurar permisos, límites, editar configuraciones manualmente y reiniciar systemd. Un mes después, se unió un nuevo desarrollador e intentamos replicar el entorno en su máquina — los mismos problemas de siempre.

Docker lo solucionó todo. Ahora tengo una sola carpeta con docker-compose.yml que llevo entre proyectos. Levanto un clúster de análisis en un minuto, y cuando necesito eliminarlo — docker-compose down -v y queda limpio. Sin desorden en el sistema.

A continuación, tres escenarios listos que uso en proyectos reales (desde un proyecto paralelo de startup hasta análisis de apuestas). Todas las configuraciones están probadas en Docker Engine 24+.

Google AdInline article slot

Escenario 1. Inicio rápido: Un comando para probar una hipótesis

Para desarrollo local y prototipado rápido, una sola línea basta. Pero no solo docker run clickhouse/clickhouse-server — agreguemos lo que hace útil a ClickHouse: almacenamiento persistente y mapeo de puertos.

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

Lo importante aquí:

  • -v clickhouse-data — un volumen con nombre, no un bind mount. La diferencia: los volúmenes son gestionados por Docker, no se pierden al reiniciar y rinden mejor en macOS (importante si usas un MacBook — los bind mounts son lentos por la sincronización).
  • CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 — habilita el control de acceso. Sin esta variable, el usuario developer se crea pero no puede crear nuevas cuentas. Lo aprendimos por las malas: en producción, tuvimos que meternos al contenedor y editar users.xml.
  • Puertos 9000 (protocolo nativo) y 8123 (HTTP) — siempre abro ambos porque la mitad de los clientes (DBeaver, TablePlus) funcionan sobre HTTP, mientras que las aplicaciones usan el driver nativo.

Verifica que funciona:

Google AdInline article slot
# Interfaz HTTP — la prueba más simple
curl "http://localhost:8123/?query=SELECT+version()"
# Salida: 24.8.2.3

Escenario 2. Docker Compose para desarrollo (Nodo único, Healthcheck)

Cuando el proyecto se vuelve un poco más complejo, cambio inmediatamente a docker-compose.yml. Este archivo lo uso en mis laptops y servidores de desarrollo:

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:

Por qué agregué ulimits: En producción, ClickHouse consume hasta 262144 descriptores de archivo abiertos. Sin esto, bajo carga pesada fallará con Too many open files. Una vez perdí tres horas en esto cuando el servidor empezó a fallar después de 2 millones de filas.

Healthcheck mediante /ping: ClickHouse tiene un endpoint /ping incorporado (devuelve "Ok." si está vivo). Esto es mejor que verificar con SELECT 1 porque no requiere autenticación y no escribe en los logs.

Google AdInline article slot

Variable por defecto: ${CLICKHOUSE_PASSWORD:-analyst123} — si no se establece en .env, la contraseña será analyst123. No olvides el archivo .env para secretos reales.

Escenario 3. Configuración similar a producción: ClickHouse + Zookeeper para replicación

La replicación de tablas en ClickHouse requiere ZooKeeper (o ClickHouse Keeper, pero empiezo con el clásico). Uso este compose para probar tolerancia a fallos:

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"  # segunda instancia en un puerto diferente
      - "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:

Y aquí está el contenido de config/replicated.xml (montado en ambos contenedores):

<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>

Cuándo es realmente necesario: En un proyecto de casino en producción, perdimos datos porque manteníamos todo en un solo nodo. Después de eso, siempre levanto al menos dos contenedores replicados para pruebas. La diferencia de precio son dos contenedores en lugar de uno, pero dormir bien no tiene precio.

Escenario 4. Configuración completa de apuestas: ClickHouse + Kafka + Redis

Para análisis de apuestas en tiempo real, necesito streaming (Kafka) y caché (Redis). Uso este compose para depuración local del pipeline:

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:

Cómo usar esto en código de aplicación:

# Ejemplo en Python: leer una apuesta desde Redis (caché), escribir en 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())

# Verificación de duplicados (detección de fraude)
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"Apuesta duplicada {bet_id} bloqueada")

Cómo montar tu propio config.xml sin romper todo

Un error que cometí cinco veces: montar un config.xml completo, solo para descubrir que una nueva versión de ClickHouse agregó secciones obligatorias. El contenedor fallaba con Config has no <logger>.

El enfoque correcto: colocar solo sobrescrituras en config.d/. Aquí hay una estructura que funciona:

docker-clickhouse/
├── docker-compose.yml
├── .env
├── config/
│   ├── config.d/
│   │   ├── memory.xml
│   │   ├── networks.xml
│   │   └── query-log.xml
│   └── users.d/
│       └── profiles.xml

Ejemplo 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>

Ejemplo 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>

Trabajar con clickhouse-client dentro de un contenedor

Saltar a un contenedor para consultas rápidas es normal. Pero no mediante docker exec -it bash — hazlo directamente:

# Ejecutar una consulta
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM system.tables"

# Modo interactivo
docker exec -it clickhouse-dev clickhouse-client

# Con contraseña
docker exec -it clickhouse-dev clickhouse-client --password devpass123

Mi truco: Agrega un alias a ~/.bashrc:

alias ch-cli='docker exec -it clickhouse-dev clickhouse-client'

Después de eso, solo escribe ch-cli y trabaja como si fuera una base de datos local.

Variables de entorno: lo que realmente funciona

La imagen oficial no soporta todas las variables prometidas en foros. Aquí están las probadas:

Variable Propósito Ejemplo
CLICKHOUSE_DB Nombre de base de datos por defecto analytics
CLICKHOUSE_USER Usuario administrador prod_user
CLICKHOUSE_PASSWORD Contraseña strongpass
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT Habilitar RBAC (1/0) 1

Lo que NO funciona: CLICKHOUSE_HTTP_PORT, CLICKHOUSE_TCP_PORT — el entrypoint los ignora. Cambia los puertos mediante ports: en compose o montando una configuración.

Health Check: mi lista de verificación completa

Después de iniciar cualquier compose, ejecuto:

# 1. HTTP ping (debe devolver "Ok.")
curl http://localhost:8123/ping

# 2. Versión mediante HTTP
curl "http://localhost:8123/?query=SELECT+version()"

# 3. Crear tabla de prueba
docker exec -it clickhouse-dev clickhouse-client --query "CREATE TABLE test.t (id UInt64) ENGINE = MergeTree ORDER BY id"

# 4. Insertar y seleccionar
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 con autenticación (si se estableció contraseña)
curl -u developer:devpass123 "http://localhost:8123/?query=SELECT+user()"

Qué hacer si no funciona — errores comunes de Docker

Error: Code: 210. DB::NetException: Connection refused
Solución: El contenedor aún no ha iniciado. Agrega depends_on y healthcheck, o haz sleep 5 en scripts.

Error: Cannot create directory /var/lib/clickhouse: Permission denied
Solución: En un host con SELinux, agrega :Z al volumen: -v ./data:/var/lib/clickhouse:Z. O usa volúmenes con nombre.

Error: Max connections limit reached
Solución: Aumenta en la configuración: <max_connections>4096</max_connections> y reinicia.

El contenedor consume toda la memoria del host
Solución: Limita mediante Docker:

docker update --memory=4g --memory-swap=4g clickhouse-dev

O en compose:

deploy:
  resources:
    limits:
      memory: 4G

Conclusión: Cuándo usar Docker y cuándo no

Docker es ideal para ClickHouse en desarrollo, staging y entornos de producción pequeños. Pero si tienes un clúster de 10+ nodos con 100 TB de datos — los paquetes nativos sin capas adicionales son mejores.

Por ahora, toma mi compose de configuración de apuestas, cambia las contraseñas y empieza a contar apuestas en tiempo real.

Todas las configuraciones tomadas de proyectos reales. Nombres cambiados, problemas permanecen.


Anterior:
Siguiente: Cliente de ClickHouse: Cómo me hice amigo de la consola y la API HTTP en un proyecto de apuestas

— Editorial Team

Advertisement 728x90

Leer después