返回首页

ClickHouse in Docker: 2分钟内启动分析

在Docker中运行ClickHouse的指南,包含四个现成方案:基本的docker run、带健康检查的用于开发的docker-compose、用于复制的ZooKeeper集群、用于游戏分析的完整ClickHouse + Kafka + Redis堆栈。解释了环境变量、挂载自定义配置、典型错误及其解决方案。

ClickHouse in Docker: 4个带compose文件的现成方案
Advertisement 728x90

Docker中的ClickHouse:如何不再担忧,两分钟启动分析

为什么是Docker——其他一切随之而来

我记得第一次在生产环境部署ClickHouse的情景。花了四个小时配置权限、限制、手动编辑配置文件,然后重启systemd。一个月后,新同事加入,我们试图在他的机器上复现环境——又踩了同样的坑。

Docker解决了一切。现在我有一个包含docker-compose.yml的文件夹,可以在项目间携带。一分钟内启动分析集群,需要拆除时执行docker-compose down -v,干干净净,不留系统垃圾。

下面是我在真实项目中使用的三个现成场景(从初创公司副项目到博彩分析)。所有配置均在Docker Engine 24+上测试通过。

Google AdInline article slot

场景1. 快速启动:一条命令验证假设

对于本地开发和快速原型,一行命令就够了。但不仅仅是docker run clickhouse/clickhouse-server——让我们添加让ClickHouse有用的功能:持久化存储和端口映射。

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

重点说明:

  • -v clickhouse-data — 命名卷,而非绑定挂载。区别在于:卷由Docker管理,重启不会丢失,且在macOS上性能更好(如果你用MacBook,绑定挂载因同步问题会很慢)。
  • CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 — 启用访问控制。没有这个变量,developer用户会被创建但无法创建新账户。我们吃过亏:在生产环境中,不得不进入容器编辑users.xml
  • 端口9000(原生协议)和8123(HTTP)——我总是同时开放,因为一半的客户端(DBeaver、TablePlus)通过HTTP工作,而应用程序使用原生驱动。

检查是否启动:

Google AdInline article slot
# HTTP接口——最简单的测试
curl "http://localhost:8123/?query=SELECT+version()"
# 输出:24.8.2.3

场景2. 用于开发的Docker Compose(单节点,健康检查)

当项目变得稍微复杂时,我立即切换到docker-compose.yml。这个文件我在笔记本电脑和开发服务器上使用:

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:

为什么添加ulimits 在生产环境中,ClickHouse会消耗多达262144个打开文件描述符。没有这个限制,高负载下会因Too many open files而崩溃。我曾因此浪费三个小时,当时服务器在插入200万行后开始失败。

通过/ping进行健康检查: ClickHouse内置了/ping端点(存活时返回"Ok.")。这比通过SELECT 1检查更好,因为它不需要认证且不写入日志。

Google AdInline article slot

默认变量: ${CLICKHOUSE_PASSWORD:-analyst123} — 如果.env中未设置,密码将为analyst123。对于真正的密钥,别忘了.env文件。

场景3. 类生产环境:ClickHouse + Zookeeper实现复制

ClickHouse表复制需要ZooKeeper(或ClickHouse Keeper,但我从经典方案开始)。我用这个compose测试容错性:

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"  # 第二个实例使用不同端口
      - "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:

以下是config/replicated.xml的内容(挂载到两个容器中):

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

何时真正需要: 在一个生产环境的赌场项目中,由于所有数据都放在单个节点上,我们丢失了数据。此后,我总是至少启动两个复制容器进行测试。成本差异只是两个容器而非一个,但睡个好觉是无价的。

场景4. 完整博彩环境:ClickHouse + Kafka + Redis

对于实时博彩分析,我需要流处理(Kafka)和缓存(Redis)。我用这个compose进行本地管道调试:

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:

如何在应用程序代码中使用:

# Python示例:从Redis(缓存)读取投注,写入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())

# 重复检查(欺诈检测)
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"重复投注 {bet_id} 已阻止")

如何挂载自己的config.xml而不破坏一切

我犯过五次的错误:挂载完整的config.xml,结果发现新版本的ClickHouse添加了强制部分。容器会因Config has no <logger>而崩溃。

正确做法: 只在config.d/中放置覆盖项。以下是一个可用的结构:

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

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

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

在容器内使用clickhouse-client

跳进容器执行快速查询是正常的。但不要通过docker exec -it bash——直接这样做:

# 执行查询
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM system.tables"

# 交互模式
docker exec -it clickhouse-dev clickhouse-client

# 带密码
docker exec -it clickhouse-dev clickhouse-client --password devpass123

我的小技巧:~/.bashrc中添加别名:

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

之后,只需输入ch-cli,就像使用本地数据库一样工作。

环境变量:哪些真正有效

官方镜像并不支持论坛上承诺的所有变量。以下是经过测试的变量:

变量 用途 示例
CLICKHOUSE_DB 默认数据库名 analytics
CLICKHOUSE_USER 管理员用户 prod_user
CLICKHOUSE_PASSWORD 密码 strongpass
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT 启用RBAC(1/0) 1

哪些不起作用: CLICKHOUSE_HTTP_PORTCLICKHOUSE_TCP_PORT——入口点忽略它们。通过compose中的ports:或挂载配置来更改端口。

健康检查:我的完整清单

启动任何compose后,我运行:

# 1. HTTP ping(应返回"Ok.")
curl http://localhost:8123/ping

# 2. 通过HTTP查看版本
curl "http://localhost:8123/?query=SELECT+version()"

# 3. 创建测试表
docker exec -it clickhouse-dev clickhouse-client --query "CREATE TABLE test.t (id UInt64) ENGINE = MergeTree ORDER BY id"

# 4. 插入并查询
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(如果设置了密码)
curl -u developer:devpass123 "http://localhost:8123/?query=SELECT+user()"

如果不起作用怎么办——常见Docker错误

错误:Code: 210. DB::NetException: Connection refused
解决方案:容器尚未启动。添加depends_onhealthcheck,或在脚本中执行sleep 5

错误:Cannot create directory /var/lib/clickhouse: Permission denied
解决方案:在启用SELinux的主机上,向卷添加:Z-v ./data:/var/lib/clickhouse:Z。或使用命名卷。

错误:Max connections limit reached
解决方案:在配置中增加:<max_connections>4096</max_connections>并重启。

容器内存耗尽主机内存
解决方案:通过Docker限制:

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

或在compose中:

deploy:
  resources:
    limits:
      memory: 4G

结论:何时使用Docker,何时不使用

Docker非常适合开发、预发布和小型生产环境中的ClickHouse。但如果你有一个10个以上节点、100TB数据的集群——最好使用原生包,避免额外层。

现在,拿我的博彩环境compose,修改密码,开始实时统计投注吧。

所有配置均来自真实项目。名称已更改,坑点保留。


上一篇:
下一篇: ClickHouse 客户端:我是如何在博彩项目中与控制台和 HTTP API 交上朋友的

— Editorial Team

Advertisement 728x90

继续阅读