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

от автора

Всем привет! В этой статье я расскажу свой опыт установки БД Clickhouse на пару с zookeeper`ом.

Установка

Для начала скачаем пакеты для установки

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

Список версий можно посмотреть здесь.

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

Скрипт имеет следующий вид:

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"

Важное напоминание, если вы проводите установку на нескольких ВМ, не забудьте указан одинаковый пароль для default user, как это сделал я и пришлось запускать установку заново)

После окончания установки вы увидите, что сервис стартанул и можно зайти под дефолтным юзером в систему:

clickhouse-client --password
select 1

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

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

По мимо вышеупомянутого, zookeeper также нужен для:

  • Хранение метаданных о таблицах и кластере.

    • В ClickHouse есть типы таблиц ReplicatedMergeTree, ReplicatedReplacingMergeTree и др.

    • Все они используют ZooKeeper для хранения:

      • списка реплик,

      • информации о партициях

      • состояния мерджей,

      • флагов «эта партиция уже загружена» и т.д.

  • Координация реплик.

    • Когда одна реплика получает новые данные, она должна синхронизироваться с другими

    • ZooKeeper фиксирует, что данные загружены, и указывает другим репликам подтянуть изменения.

  • Распределение задач (scheduling).

    • Например, две реплики могут попытаться одновременно сделать merge одного и того же куска данных.

    • Через ZooKeeper они «договариваются», кто будет делать merge, а кто потом просто скопирует результат.

  • Failover и согласованность.

    • ZooKeeper хранит список активных реплик.

    • Если одна нода падает, через ZooKeeper к ней перестаёт идти трафик, и другие реплики продолжают работать.

    • При восстановлении — новая реплика подтягивает актуальные данные, сверяясь через ZooKeeper.

В новых версия клика добавили встроенный аналог — Clickhouse Keepeer.

Итак, мы пробежались по функциям кипера, давайте теперь его установим.

Деплой в кластер Kubernetes

Да, тут всё серьезно, поэтому я создал отдельный namespace и беру следующие манифесты, тут ничего нового, конфигурация везде похожа:

service.yaml для clickhouse-client
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
service.yaml headless service для 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
bdb.yaml
apiVersion: policy/v1 kind: PodDisruptionBudget metadata:   name: zk-pdb spec:   selector:     matchLabels:       app: zk   maxUnavailable: 1
sts.yaml самого zookeeper`a
apiVersion: apps/v1 kind: StatefulSet metadata:   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           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 &&               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        securityContext:         runAsUser: 1000         fsGroup: 1000

Я не буду вдаваться в подробности, что именно происходит в манифестах. Скажу лишь, что это дефолтная настройка сервиса. Из интересного, у statefulset`a кипера, начиная со 101 строчки, прописана функция, которая означает, что StatefulSet сам динамически подставит правильные DNS-имена подов (zookeeper-0, zookeeper-1, zookeeper-2), и не нужно вручную жёстко хардкодить IP.

После я создаю дополнительный сервис для zookeeper, в эндпоинтах которого буду присутствовать ноды clickhouse.

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

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

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

Ноды клика мы прописываем на 7-й строке

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

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

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

Скрытый текст
apiVersion: v1 data:   "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

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

На описание установки ingress-nginx я не буду останавливаться. Познакомиться с инструментом можно здесь.

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

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

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

Для начала нам нужно задать в разделе zookeeper его хост и порты, которые мы открывали до этого. В зависимости от кол-ва реплик клика мы заполняем поля с хостом кипера и открытыми портами. В моём случае три раза.

 Блок с zookeeper

Блок с zookeeper

Перезапустим сервис

#Полный перезапуск сервиса sudo service clickhouse-server restart #           vs #Перезагрузка конфигурации sudo service clickhouse-server reload

Проверка

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

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

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

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

drop table test on cluster main sync;

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

Создание и работа и с пользователями

Для начала нам необходимо нашему default пользователю включить User Mode. Конфигурация лежит в папке /etc/clickhouse-server/users.d

<clickhouse>     <users>         <default>             <password remove='1' />             <password_sha256_hex>daaad6e5604e8e17bd9f108d91e26afe6281dac8fda0091040a7a6d7bd9b43b5</password_sha256_hex>             <access_management>1</access_management>             <named_collection_control>1</named_collection_control>             <show_named_collections>1</show_named_collections>             <show_named_collections_secrets>1</show_named_collections_secrets>         </default>     </users> </clickhouse> 

Перезапускаем сервер

sudo service clickhouse-server restart

Создадим админа БД

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

create user clickhouse-admin identified by 'password123'

Выдадим права на все таблицы в кластере

grant all on *.* to clickhouse_admin with grant option;

Больше про работу с правами пользователей тут.

Отключим default user

<clickhouse>     <users>         <default remove="remove">             <password remove='1' />             <password_sha256_hex>daaad6e5604e8e17bd9f108d91e26afe6281dac8fda0091040a7a6d7bd9b43b5</password_sha256_hex>             <!--             <access_management>1</access_management>             <named_collection_control>1</named_collection_control>             <show_named_collections>1</show_named_collections>             <show_named_collections_secrets>1</show_named_collections_secrets>             -->         </default>     </users> </clickhouse>

Заключение

В этой статье я описал свою настройку кликхауса на пару с zookeeper, немного затронув ingress-nginx.

Clickhouse достаточно мощный инструмент для работы с большим объёмом данных, который при правильном использовании приносит свои плоды.

Надеюсь, вам было интересно. Это моя первая работа на этом ресурсе, скоро начну выкладывать ряд статей о работе над одним стартапом, так что ожидайте, будет очень интересно.


ссылка на оригинал статьи https://habr.com/ru/articles/942146/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *