홈으로 돌아가기

ClickHouse in Docker: 2분 안에 분석 시작하기

Docker에서 ClickHouse 실행 가이드: 기본 docker run, 헬스체크가 포함된 개발용 docker-compose, 복제를 위한 ZooKeeper 클러스터, 게임 분석용 전체 ClickHouse + Kafka + Redis 스택 등 네 가지 준비 시나리오. 환경 변수, 사용자 정의 설정 파일 마운트, 일반적인 오류 및 해결 방법 설명.

ClickHouse in Docker: compose 파일이 포함된 4가지 준비 시나리오
Advertisement 728x90

Docker에서 ClickHouse: 걱정을 접고 2분 만에 분석 시작하기

Docker — 그리고 나머지는 그 다음이다

처음 프로덕션에 ClickHouse를 설정했을 때를 기억합니다. 권한, 제한 설정, 수동으로 설정 파일 편집, systemd 재시작까지 4시간이 걸렸습니다. 한 달 후 새 개발자가 합류했고, 그의 머신에 환경을 복제하려 했지만 똑같은 함정에 빠졌습니다.

Docker가 모든 것을 해결했습니다. 이제 저는 프로젝트 간에 가져다 쓸 수 있는 docker-compose.yml 파일 하나만 있으면 됩니다. 1분 만에 분석 클러스터를 띄우고, 필요 없으면 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만 행 이후 서버가 다운되기 시작해서 3시간을 허비했습니다.

/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_PORT, CLICKHOUSE_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

다음 읽기