Skip to content

Clickhouse. Развертывание.

Установка

Пакеты для установки:

bash
wget curl https://packages.clickhouse.com/tgz/stable/clickhouse-client-22.9.3.18-amd64.tgz && \
wget curl https://packages.clickhouse.com/tgz/stable/clickhouse-common-static-22.9.3.18-amd64.tgz && \
wget curl https://packages.clickhouse.com/tgz/stable/clickhouse-common-static-dbg-22.9.3.18-amd64.tgz && \
wget curl https://packages.clickhouse.com/tgz/stable/clickhouse-server-22.9.3.18-amd64.tgz

Скрип для распаковки и последующей установки:

bash
LATEST_VERSION=22.9.3.18
   export LATEST_VERSION

case $(uname -m) in
  x86_64) ARCH=amd64 ;;
  aarch64) ARCH=arm64 ;;
  *) echo "Unknown architecture $(uname -m)"; exit 1 ;;
esac

tar -xzvf "clickhouse-common-static-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-common-static-$LATEST_VERSION.tgz"
sudo "clickhouse-common-static-$LATEST_VERSION/install/doinst.sh"

tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION.tgz"
sudo "clickhouse-common-static-dbg-$LATEST_VERSION/install/doinst.sh"

tar -xzvf "clickhouse-server-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-server-$LATEST_VERSION.tgz"
sudo "clickhouse-server-$LATEST_VERSION/install/doinst.sh" configure
sudo /etc/init.d/clickhouse-server start

tar -xzvf "clickhouse-client-$LATEST_VERSION-${ARCH}.tgz" \
  || tar -xzvf "clickhouse-client-$LATEST_VERSION.tgz"
sudo "clickhouse-client-$LATEST_VERSION/install/doinst.sh"

INFO

Обратите внимание, что при установке клик попросит задать пароль для default user, он должен быть одинаковым на всех машинах.

Настройка zookeer и ingress-inginx

В первую очередь необходимо поднять Zookeeper в нужном кластере:

1. Создаем отдельный NS в кубе, zk;

2. Приминяем манифест для zookeeper:

yaml
# Setup Service to provide access to Zookeeper for clients
apiVersion: v1
kind: Service
metadata:
  # DNS would be like zookeeper.zoons
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
    - port: 2181
      name: client
  selector:
    app: zk
    what: node
---
# Setup Headless Service for StatefulSet
apiVersion: v1
kind: Service
metadata:
  # DNS would be like zookeeper-0.zookeepers.etc
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
    - port: 2181
      name: client
    - port: 2888
      name: server
    - port: 3888
      name: leader-election
  clusterIP: None
  selector:
    app: zk
    what: node
---
# Setup max number of unavailable pods in StatefulSet
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  selector:
    matchLabels:
      app: zk
  maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  # nodes would be named as zookeeper-0, zookeeper-1, zookeeper-2
  name: zk
spec:
  selector:
    matchLabels:
      app: zk
  serviceName: zk-hs
  replicas: 2
  updateStrategy:
    type: RollingUpdate
  podManagementPolicy: Parallel
  template:
    metadata:
      labels:
        app: zk
        what: node
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                      - zk
              topologyKey: "kubernetes.io/hostname"
      containers:
        - name: kubernetes-zookeeper
          imagePullPolicy: IfNotPresent
          image: "docker.io/zookeeper:3.8.0-temurin"
          resources:
            requests:
              memory: "1Gi"
              cpu: "0.5"
          ports:
            - containerPort: 2181
              name: client
            - containerPort: 2888
              name: server
            - containerPort: 3888
              name: leader-election
# See those links for proper startup settings:
# https://github.com/kow3ns/kubernetes-zookeeper/blob/master/docker/scripts/start-zookeeper
# https://clickhouse.yandex/docs/en/operations/tips/#zookeeper
          command:
            - bash
            - -x
            - -c
            - |
              SERVERS=3 &&
              HOST=`hostname -s` &&
              DOMAIN=`hostname -d` &&
              CLIENT_PORT=2181 &&
              SERVER_PORT=2888 &&
              ELECTION_PORT=3888 &&
              ZOO_DATA_DIR=/var/lib/zookeeper/data &&
              ZOO_DATA_LOG_DIR=/var/lib/zookeeper/datalog &&
              {
                echo "clientPort=${CLIENT_PORT}"
                echo 'tickTime=2000'
                echo 'initLimit=30000'
                echo 'syncLimit=10'
                echo 'maxClientCnxns=2000'
                echo 'maxSessionTimeout=60000000'
                echo "dataDir=${ZOO_DATA_DIR}"
                echo "dataLogDir=${ZOO_DATA_LOG_DIR}"
                echo 'autopurge.snapRetainCount=3'
                echo 'autopurge.purgeInterval=2'
                echo 'preAllocSize=131072'
                echo 'snapCount=3000000'
                echo 'leaderServes=yes'
                echo 'standaloneEnabled=true'
                echo '4lw.commands.whitelist=stat, ruok, conf, isro'
              } > /conf/zoo.cfg &&
              {
                echo "zookeeper.root.logger=CONSOLE"
                echo "zookeeper.console.threshold=INFO"
                echo "log4j.rootLogger=\${zookeeper.root.logger}"
                echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender"
                echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}"
                echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout"
                echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n"
              } > /conf/log4j.properties &&
              echo 'JVMFLAGS="-Xms128M -Xmx1G -XX:+UseG1GC"' > /conf/java.env &&
              if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then
                  NAME=${BASH_REMATCH[1]}
                  ORD=${BASH_REMATCH[2]}
              else
                  echo "Failed to parse name and ordinal of Pod"
                  exit 1
              fi &&
              mkdir -p ${ZOO_DATA_DIR} &&
              mkdir -p ${ZOO_DATA_LOG_DIR} &&
              export MY_ID=$((ORD+1)) &&
              #echo 2 > $ZOO_DATA_DIR/myid &&
              echo $MY_ID > $ZOO_DATA_DIR/myid &&
               if [[ $SERVERS -gt 1 ]]; then
                 for (( i=1; i<=$SERVERS; i++ )); do
                     echo "server.$i=$NAME-$((i-1)).$DOMAIN:$SERVER_PORT:$ELECTION_PORT" >> /conf/zoo.cfg;
                 done
               fi &&
              #echo "server.1=192.168.233.230:2888:3888" >> /conf/zoo.cfg &&
              #echo "server.2=$NAME-0.$DOMAIN:$SERVER_PORT:$ELECTION_PORT" >> /conf/zoo.cfg &&
              chown -Rv zookeeper "$ZOO_DATA_DIR" "$ZOO_DATA_LOG_DIR" "$ZOO_LOG_DIR" "$ZOO_CONF_DIR" &&
              zkServer.sh start-foreground
          readinessProbe:
            exec:
              command:
                - bash
                - -c
                - "OK=$(echo ruok | nc 127.0.0.1 2181); if [[ \"$OK\" == \"imok\" ]]; then exit 0; else exit 1; fi"
            initialDelaySeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            exec:
              command:
                - bash
                - -c
                - "OK=$(echo ruok | nc 127.0.0.1 2181); if [[ \"$OK\" == \"imok\" ]]; then exit 0; else exit 1; fi"
            initialDelaySeconds: 10
            timeoutSeconds: 5
          volumeMounts:
            - name: datadir
              mountPath: /var/lib/zookeeper

      # Run as a non-privileged user
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      storageClassName: longhorn
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi

3. Далее нам необходимо подключить дополнительно сервис для zookeeper, в эндпоинтах которого буду присутсвовать ноды клика:

Для этого применим манифест для сервиса:

yaml
apiVersion: v1
kind: Service
metadata:
  name: ch-svc
  namespace: zk
spec:
  clusterIP: None
  type: ClusterIP
  sessionAffinity: None
  ports:
  - name: http
    port: 8123
    protocol: TCP
    targetPort: 8123
  - name: tcp
    port: 9000
    protocol: TCP
    targetPort: 9000

И манифест для эндпоинтов:

yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: ch-svc
  namespace: zk
subsets:
  - addresses:
      - ip: 00.00.00.00 #Ноды
      - ip: 00.00.00.00 #клика
    ports:
      - name: tcp
        port: 9000
        protocol: TCP
      - name: http
        port: 8123
        protocol: TCP

3.1. Также нам необходимо закрепить за каждым подом кипера свой сервис, каждый из которых будет смотреть на один из подов соответственно:

yaml
apiVersion: v1
kind: Service
metadata:
  name: zk-0 #В зависимости от номера пода
  namespace: zk
spec:
  internalTrafficPolicy: Cluster
  ports:
  - port: 2181
    protocol: TCP
    targetPort: 2181
  selector:
    apps.kubernetes.io/pod-index: "0" #Указываем номер пода
  sessionAffinity: None
  type: ClusterIP

4. После того, как мы закончили с кипером, необходимо внести данные об ранее созданных соединений в configmap ingress-nginx-tcp:

4.1 В раздел data мы вносим наши открытые порты как от кипера, так и от клика:

yaml
apiVersion: v1
data:
  "31010": dremio/dremio-client:31010 # Присутствующие ранее порты мы оставляем!
  "2181": zk/zk-0:2181 # +
  "2182": zk/zk-1:2182 # +
  "8123": zk/ch-svc:8123 # +
  "9000": zk/ch-svc:9000 # +
kind: ConfigMap
metadata:
  annotations:
    meta.helm.sh/release-name: ingress-controller
    meta.helm.sh/release-namespace: nginx-ingress
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-controller
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.2.1
    helm.sh/chart: ingress-nginx-4.1.4
    k8slens-edit-resource-version: v1
  name: ingress-controller-ingress-nginx-tcp
  namespace: nginx-ingress

4.2 В эндопинты ingress-controller-ingress-nginx-controller также добавляем наши порты, после чего вносим их в деплоймент nginx, раздел портов для контейнеров и перезагружаем сервис. Внешняя настройка завершена.

Внутренняя настройка clickhouse.

В первую очередь необходимо по пути /etc/clickhouse-server/config.d/ создать файл config.xml на всех узлах, его можно найти на любых машинах действующего клика. Заполняем его по примеру.

Также ниже в файле, в настройке Zookeeper, установливаем IP балансировщика в <port></port>, по которому в дальнейшем будет выполняться вход в базу.

INFO

IP балансироващика указывается в колличестве, зависящим от колличества сервисов кипера под каждый под.

Проверка

Для проверки работоспособности зайдите в базу клика либо через СУБД по IP балансировщика, либо через ноду клика и выполните команду по созданию тестовой таблице:

sql
CREATE TABLE test on cluster 'main' (id int)  ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/test', '{replica}', id) order by id;

insert into test values(1);

После выполнения скрипта должны отобразится рабочие ноды, которые сихнронизировано выполнили скрипт. Дополнительно можно зайти на другую ноду и выполнить селект по таблице.

Удалить тестовую талицу следующем скриптом:

sql
Drop table test on cluster main sync;

sync в данном случае очень важно проставлять, чтобы команда отработала на всех нодах.

Настройка путей для файловой системы Clickhouse.

Для этого нам понадобиться пустой новый диск, созданный специально для клика.

Для начала убедимся, что диск присутствует в системе, введя команду:

bash
dh -h

Вы сразу увидите его. Размер сильно отличается от других доступных, и он почти пустой.

Далее нам нужно его имя, получим командой:

bash
fdisk -l #Пример /dev/sdb

Для монтирования диска нам необходимо выполнить следующие команды, исполльзуя утилиту LVM:

bash
pvcreate /dev/sdb; #Помечаем диск, что он будет использоваться для LVM

vgcreate data /dev/sdb; #Создаем группу томов для диска 

lvcreate -l 100%FREE -n lv01 data; #Выделяем под эту группу всё доступное место

mkfs.ext4 /dev/data/lv01; #Устанавливаем нужную файловую систему

vi /etc/fstab #Конфигурируем файл, в котором узываем папку, к которой будет относиться диск, по образцу работающий машины

mount /opt #Создаем директорию, которую указали в пункте выше

После проделанных шагов, копируем все данные из дефолтных директорий клика в новую, придерживаясь пути /opt/clickhouse/[копируемая директория]/. Их должны быть две - lib и log. В конечном результате всё должно выглядить так:

INFO

Успользуйте cp -r для копирования папок с вложенными папками

Далее подчищаем дефолтные пути и всю новую систему передаем в пользование клика:

bash
chown -R clickhouse:clickhouse /opt/clickhouse

После нам необходимо задать правильные пути в файле /etc/clickhouse-server/config.xml относительно того, как это было ранее.

Перезагружаем кликхаус на узлах и проверяем его работоспособность.