Перейти к содержанию

Развёртывание кластера через Kubernetes Operator

Kubernetes Operator — рекомендуемый способ управления кластером Picodata в Kubernetes для production-окружений. Оператор реализует reconcile-loop на основе CRD PicoclusterDB и автоматически поддерживает желаемое состояние кластера: создаёт и обновляет StatefulSet-ы, Service-ы, ConfigMap-ы, следит за установкой и обновлением плагинов.

Требования

  • Kubernetes 1.30+
  • kubectl 1.28+

Установка оператора

Установка выполняется в два шага: сначала регистрируются CRD, затем разворачивается сам оператор.

Шаг 1. Установите CRD:

kubectl apply -f https://git.picodata.io/core/picodata-operator/-/raw/master/picodata-operator-deploy/crd.yaml

Шаг 2. Установите оператор:

kubectl apply -f https://git.picodata.io/core/picodata-operator/-/raw/master/picodata-operator-deploy/operator.yaml

Дождитесь готовности пода оператора:

kubectl rollout status deployment/picodata-operator-controller-manager \
  -n picodata-operator-system

Создание namespace и секрета

Создайте namespace для кластера и секрет с паролем пользователя admin:

kubectl create namespace picodata

kubectl create secret generic picodata-admin-secret \
  --namespace picodata \
  --from-literal=password=<ваш-пароль>

Внимание!

Пароль должен соответствовать политике безопасности Picodata: не менее 8 символов, латинские буквы в верхнем и нижнем регистрах, цифры.

Описание ресурса PicoclusterDB

Кластер описывается ресурсом PicoclusterDB. Основные поля:

apiVersion: picodata.picodata.io/v1
kind: PicoclusterDB
metadata:
  name: <имя-кластера>
  namespace: <namespace>
spec:
  image:
    repository: docker.binary.picodata.io  # реестр образов
    tag: "picodata:26.1.2"                 # образ и версия Picodata
    pullPolicy: IfNotPresent

  clusterName: <имя-кластера>              # имя кластера внутри Picodata

  adminPassword:
    secretName: picodata-admin-secret      # Secret с паролем admin
    key: password

  cluster:
    defaultReplicationFactor: 1  # фактор репликации по умолчанию для всех тиров
    defaultBucketCount: 3000     # количество бакетов (шардов)

  tiers:                         # список тиров кластера
    - name: <имя-тира>
      replicas: 1                # количество репликасетов в тире
      replicationFactor: 1       # количество инстансов в каждом репликасете
      canVote: true              # участвует ли тир в Raft-голосовании
      storage:
        size: 1Gi
      memtx:
        memory: "128M"
      pg:
        enabled: true            # включить протокол PostgreSQL
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"

Общее количество подов тира равно replicas × replicationFactor.
Имена подов строятся по шаблону: {тир}-{кластер}-{номер-репликасета}-{номер-инстанса}.

Развёртывание кластера

Минимальный кластер

Кластер из двух тиров: арбитр для Raft и основной тир для хранения данных. Пример минимальной конфигурации:

picodata-cluster.yaml
apiVersion: picodata.picodata.io/v1
kind: PicoclusterDB
metadata:
  name: picodata
  namespace: picodata
spec:
  image:
    repository: docker.binary.picodata.io
    tag: "picodata:26.1.2"
    pullPolicy: IfNotPresent

  clusterName: picodata

  adminPassword:
    secretName: picodata-admin-secret
    key: password

  cluster:
    defaultReplicationFactor: 1
    defaultBucketCount: 3000

  tiers:
    - name: arbiter
      replicas: 1
      replicationFactor: 1
      canVote: true
      storage:
        size: 1Gi
      memtx:
        memory: "64M"
      pg:
        enabled: false
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"

    - name: default
      replicas: 1
      replicationFactor: 2
      canVote: false
      storage:
        size: 10Gi
      memtx:
        memory: "512M"
      pg:
        enabled: true
      resources:
        requests:
          cpu: "500m"
          memory: "512Mi"

Примените манифест:

kubectl apply -f picodata-cluster.yaml

Наблюдение за запуском

Следите за готовностью подов:

kubectl get pods -n picodata -w

Ожидаемый результат (все поды 1/1 Running):

NAME                  READY   STATUS    RESTARTS   AGE
arbiter-picodata-1-0  1/1     Running   0          3m
default-picodata-1-0  1/1     Running   0          3m
default-picodata-1-1  1/1     Running   0          2m

Проверьте состояние CR:

kubectl get picoclusterdb -n picodata
NAME       READY   AGE
picodata   true    5m

Подключение к кластеру

Протокол PostgreSQL (psql)

Пробросьте pgproto-порт одного из подов тира default:

kubectl port-forward -n picodata pod/default-picodata-1-0 5432:5432

Подключитесь через psql:

psql "host=localhost port=5432 user=admin password=<пароль> dbname=picodata sslmode=disable"

Проверьте состояние инстансов через системную таблицу _pico_instance:

SELECT name, replicaset_name, current_state, tier
FROM _pico_instance;

Веб-интерфейс (Web UI)

Пробросьте HTTP-порт:

kubectl port-forward -n picodata svc/default-picodata 8081:8081

Откройте http://localhost:8081 для просмотра состояния кластера в веб-интерфейсе.

Внешний доступ

Ingress

Ingress обеспечивает доступ к веб-интерфейсу и метрикам (HTTP, порт 8081) через доменное имя. Требует установленного Ingress-контроллера (например, nginx).

Включите Ingress в манифесте тира:

tiers:
  - name: default
    ingress:
      enabled: true
      host: picodata.example.com
      ingressClassName: nginx
      annotations:
        nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
      tls:
        - secretName: picodata-tls
          hosts:
            - picodata.example.com

После применения манифеста веб-интерфейс будет доступен по адресу https://picodata.example.com.

Примечание

Ingress даёт доступ только к HTTP-интерфейсу (порт 8081). Для доступа по протоколу PostgreSQL (порт 5432) используйте externalService.

LoadBalancer / NodePort

Для доступа к PostgreSQL-интерфейсу (pgproto) снаружи кластера используйте поле externalService:

tiers:
  - name: default
    externalService:
      enabled: true
      type: LoadBalancer   # или NodePort

При type: LoadBalancer Kubernetes создаёт отдельный Service с внешним IP. При type: NodePort pgproto будет доступен на фиксированном порту каждой ноды.

Получите внешний адрес:

kubectl get svc -n picodata -l picodata.io/service-type=external

Управление плагинами

Плагины объявляются на двух уровнях: кластера и тира.

На кластерном уровне задаётся версия плагина и параметры первичной миграции:

spec:
  cluster:
    shareDir: /usr/share/picodata  # путь к директории с .so-файлами плагинов
    plugins:
      - name: radix
        version: "1.0.0"
        migrationContext:
          tier_for_db_0: "default"
          # ... остальные параметры

На уровне тира задаётся привязка сервисов плагина к тиру и listener-порт:

spec:
  tiers:
    - name: default
      plugins:
        - name: radix
          services:
            - name: radix
              listenerPort: 8082

После применения манифеста оператор автоматически выполнит полный жизненный цикл установки: CREATE PLUGIN → ADD SERVICE TO TIER → SET migration_context → MIGRATE TO → ENABLE. Установка запускается только когда все поды тира готовы.

Статус установки плагина:

kubectl get picoclusterdb picodata -n picodata \
  -o jsonpath='{.status.tiers[*].plugins}'

Подробнее: Управление плагинами

Обновление версии Picodata

Для обновления версии Picodata измените поле spec.image.tag в манифесте и примените его:

spec:
  image:
    tag: "picodata:26.2.0"
kubectl apply -f picodata-cluster.yaml

Оператор выполнит безопасное обновление:

  1. Отключит (DISABLE) все включённые плагины, несовместимые с новым образом
  2. Дождётся фиксации изменения в Raft-кластере
  3. Обновит образ во всех StatefulSet-ах (rolling update)
  4. Установит и включит плагины для новой версии

Примечание

PVC (данные) при обновлении образа не затрагиваются.

Ребутстрап инстанса

Ребутстрап применяется когда инстанс не может вернуться в кластер после потери данных или повреждения тома. Оператор исключает инстанс из кластера (expel), стирает его данные и позволяет ему заново присоединиться как новому члену репликасета.

Внимание!

Ребутстрап возможен только если репликасет содержит более одного инстанса (replicationFactor ≥ 2) и хотя бы один из оставшихся инстансов доступен.

Создайте ресурс PicoclusterInstanceRebootstrap:

rebootstrap.yaml
apiVersion: picodata.picodata.io/v1
kind: PicoclusterInstanceRebootstrap
metadata:
  name: rebootstrap-1
  namespace: picodata
spec:
  clusterRef:
    name: picodata           # имя PicoclusterDB
  instanceName: default-picodata-1-0  # имя пода (инстанса)
kubectl apply -f rebootstrap.yaml

Следите за фазами выполнения:

kubectl get picoclusterinstancerebootstrap -n picodata -w
NAME            INSTANCE               PHASE       AGE
rebootstrap-1   default-picodata-1-0   Expelling   10s
rebootstrap-1   default-picodata-1-0   Wiping      30s
rebootstrap-1   default-picodata-1-0   Rejoining   45s
rebootstrap-1   default-picodata-1-0   Succeeded   2m

После завершения ресурс PicoclusterInstanceRebootstrap можно удалить.

Резервное копирование

Экспериментальная функция

Резервное копирование и восстановление находятся в стадии эксперимента. Не используйте их в production без предварительного тестирования.

Настройка хранилища резервных копий

Добавьте в манифест кластера секцию backup с описанием тома для хранения резервных копий:

spec:
  backup:
    mountPath: /pico/backup   # путь внутри пода, куда монтируется том
    volume:
      # Для production используйте NFS или другой тип тома с ReadWriteMany.
      # hostPath подходит только для одноузловых кластеров (minikube).
      nfs:
        server: nfs.example.com
        path: /exports/picodata-backup

Ограничения резервного копирования

  • Резервная копия запускается только если все поды кластера готовы (1/1 Running). При недоступности хотя бы одного пода бэкап перейдёт в фазу Failed
  • Для одного кластера одновременно может выполняться только один PicoclusterBackup.
  • Хранилище резервных копий должно быть доступно на запись со всех нод кластера. Для multi-node окружений используйте NFS или другой том с поддержкой ReadWriteMany. Оператор проверяет доступность тома перед запуском бэкапа (preflight-проверка)
  • При перезапуске пода в ходе бэкапа консистентность резервной копии не гарантируется. Факт перезапуска фиксируется в status.podSnapshots для последующей диагностики
  • По истечении spec.backupTimeout оператор прерывает операцию через pico.abort_ddl(). Прерванный бэкап не пригоден для восстановления

Создание резервной копии

Создайте ресурс PicoclusterBackup:

backup.yaml
apiVersion: picodata.picodata.io/v1
kind: PicoclusterBackup
metadata:
  name: backup-1
  namespace: picodata
spec:
  clusterRef:
    name: picodata           # имя PicoclusterDB
  backupTimeout: 3600        # максимальное время ожидания (секунды)
kubectl apply -f backup.yaml

Следите за статусом:

kubectl get picoclusterbackup -n picodata -w
NAME       CLUSTER    STATUS      AGE
backup-1   picodata   Succeeded   5m

Путь к сохранённой резервной копии:

kubectl get picoclusterbackup backup-1 -n picodata \
  -o jsonpath='{.status.backupDir}'

Ограничения восстановления

  • Восстановление — деструктивная операция: оператор останавливает весь кластер (scale до 0), стирает данные каждого инстанса и заменяет их содержимым снапшота
  • Для одного кластера одновременно может выполняться только одна операция восстановления
  • При backupRef ссылаемый PicoclusterBackup должен быть в фазе Succeeded.
  • Если восстановление завершилось с ошибкой, кластер остаётся остановленным (все StatefulSet масштабированы до 0) для ручного разбора причин. Автоматического отката нет.
  • Хранилище резервных копий должно быть смонтировано в те же поды, что и при создании бэкапа: оператор ожидает структуру <mountPath>/<instanceName>/<backupDir>/

Восстановление из резервной копии

Создайте ресурс PicoclusterRestore:

restore.yaml
apiVersion: picodata.picodata.io/v1
kind: PicoclusterRestore
metadata:
  name: restore-1
  namespace: picodata
spec:
  clusterRef:
    name: picodata           # имя PicoclusterDB для восстановления
  backupRef:
    name: backup-1           # ссылка на ресурс PicoclusterBackup

Если ресурс PicoclusterBackup был удалён, укажите директорию напрямую:

spec:
  clusterRef:
    name: picodata
  backupDir: "20260508T143720"  # имя поддиректории в томе резервных копий
kubectl apply -f restore.yaml

Anti-affinity

По умолчанию оператор автоматически добавляет правило podAntiAffinity, которое запрещает размещение двух подов одного репликасета на одном узле Kubernetes. Это гарантирует отказоустойчивость при потере узла.

Для развёртывания на одноузловом кластере (minikube, dev-окружение) отключите anti-affinity для нужного тира:

tiers:
  - name: default
    disableAutoAntiAffinity: true

Внимание!

Не отключайте anti-affinity в production: при потере узла могут упасть сразу несколько инстансов одного репликасета.

PodDisruptionBudget

Оператор автоматически создаёт PodDisruptionBudget для тиров с фактором репликации ≥ 3. PDB ограничивает количество одновременно недоступных подов при плановых операциях (drain ноды, обновление кластера Kubernetes), сохраняя кворум кластера.

При необходимости выполнить drain на кластере без свободных нод — когда Kubernetes не может переместить поды — временно отключите PDB:

tiers:
  - name: default
    disablePDB: true

После завершения drain верните значение в false (или удалите поле).

Внимание!

Не оставляйте PDB отключённым в production. Без PDB плановое обслуживание узлов может нарушить кворум кластера.

Удаление кластера

kubectl delete picoclusterdb picodata -n picodata

Удаление CR автоматически удаляет все связанные ресурсы (StatefulSet, Service, ConfigMap) через механизм ownerReference.

Внимание!

PVC (постоянные тома с данными) при удалении CR не удаляются — это защита от случайной потери данных. Для полного удаления данных выполните:

kubectl delete pvc -n picodata --all

Мониторинг

Оператор поддерживает сбор метрик через Prometheus Operator. Включите ServiceMonitor в манифесте кластера:

spec:
  serviceMonitor:
    enabled: true
    interval: "30s"

При наличии Prometheus Operator в кластере метрики каждого инстанса Picodata будут автоматически обнаружены и собираться с эндпоинта /metrics.

Читайте далее: