Volumes для баз данных

Быстродействие любых БД упирается в скорость работы дисковой подсистемы. В kubernetes принято использовать PV и PVC, которые являются не самым быстрым по быстродействию решением (за некоторыми исключениями). Быстрые диски — это обычно локальные диски серверов, для доступа к которым используется volumes типа hostPath. С другой стороны, как нам говорят гуру ИБ: «использование hostPath не безопасно«. Скорее всего из-за этого утверждения в большинстве известных мне helm charts и операторах диски определяются только при помощи PVC.

Ещё одна специфика PVC/PV — нет привязки диска к конкретной ноде кластера kubernetes (есть исключения из этого утверждения). Т.е. если под переедет на другую ноду кластера, то он сможет подключить существующий PV без создания нового физического диска и копирования (восстановления из бекапа).

Под капотом у PVC/PV в подавляющем большинстве случаев лежит какая-либо сетевая файловая система. Или файловая система с репликацией данных по сети (например longhorn).

В итоге нам предлагают использовать универсальное, но не самое быстрое решение. «Не самое быстрое» — это весьма условно. Для многих задач хватит быстродействия PVC/PV. Но есть задачи, где использования PVC/PV будет тормозом. И нам придётся «выкручиваться» подставляя hostPath в чарты (пример minio на hostPath).

Итого:

  • В простейшем случае для volumes используем сетевую файловую систему. Например, nfs и nfs-client-provisioner.
  • Если хочется использовать локальные диски нод кластера, с репликацией и вменяемой системой управления — смотрим в сторону longhorn.
  • Если нужна скорость — тогда используем локальные диски нод напрямую — hostPath или local-path-provisioner.

local-path-provisioner

В local-path-provisioner:v0.0.24 ограничение по объему PV не поддерживается!

Поскольку специалисты ИБ не очень любят hostPath. Сделаем «ход конём», «оденем» локальные диски в PVC при помощи local-path-provisioner.

Небольшое замечание. Раньше, для запрета использования hostPath администраторы кластера могли определять PSP. Но, начиная с kubernetes v1.25.0 PSP переведён в статус deprecated. 🙁 Я пока не думал как в новых кластерах ввести ограничение на hostPath. Но скорее всего придётся пользоваться внешними инструментами, типа kyverno.

Манифест для установки приложения 00-local-path-storage.yaml.

Конфигурация приложения находится в файле config.json в configMap local-path-config:

{
  "nodePathMap":[
    {
      "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
      "paths":["/data/local-path-provisioner"]
    }
  ]
}

В конфигурации по умолчанию, на всех нодах кластера, если потребуется разместить volume. Директория этого тома будет создаваться в /data/local-path-provisioner. Для каждого volume отдельная директория.

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

{
  "nodePathMap":[
    {
      "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
      "paths":["/data/local-path-provisioner"]
    },
    {
      "node":"ws1.kryukov.local",
      "paths":["/data", "/data2"]
    },
    {
      "node":"ws2.kryukov.local",
      "paths":[]
    }
  ]
}

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

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

Для работы с provisioner необходимо определить StorageClass. Простейший вариант:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete

Если на нодах кластера для разных приложений требуется давать различные файловые системы (точки монтирования). Точки монтирования необходимо определить в файле config.

{
  "nodePathMap":[
    {
      "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
      "paths":["/data/app1","/data/app1"]
    }
  ]
}

А так же создать отдельные StorageClass для каждой файловой системы, с явным указанием параметра nodePath.

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path-app1
provisioner: rancher.io/local-path
parameters:
  nodePath: /data/app1
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path-app2
provisioner: rancher.io/local-path
parameters:
  nodePath: /data/app2
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete 

Установка local-path-provisioner

Предварительно отредактируйте файл манифеста 00-local-path-storage.yaml.

kubectl apply -f 00-local-path-storage.yaml

Тестирование PVC

В файле 01-example-pvc.yaml показан пример PVC.

Обычно в качестве локальных файловых систем нод кластера используются не кластерные файловые системы. Поэтому в PVC указываем accessModes ReadWriteOnce.

Добавляем PVC в кластер.

kubectl apply -f 01-example-pvc.yaml

Проверяем состояние PVC.

kubectl get pvc
NAME              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
volume-test-pvc   Pending                                      local-path     17s

Мы видим, что PVC находится в состоянии Pending. Он будет находиться в таком состоянии, пока им не воспользуется какой-либо под. Такое поведение определено в StorageClass при помощи параметра volumeBindingMode: WaitForFirstConsumer.

Задеплоим простейшее приложение. В манифесте пода приложения явным образом определим ноду кластера, куда оно будет «приземляться».

  nodeSelector:
    kubernetes.io/hostname: ws1.kryukov.local
kubectl apply -f 02-example-pod.yaml

Проверяем состояние PVC.

kubectl get pvc

После того как приложение начало использовать PVC, provisioner сразу создал PV.

На ноде кластера можно посмотреть содержимое директории /data/local-path-provisioner. В ней мы увидим поддиректорию конкретного PVC.

Удалим приложение, не удаляя PVC

kubectl delete -f 02-example-pod.yaml

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

kubectl apply -f 02-example-pod.yaml

Посмотрим состояние приложения.

kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
volume-test   0/1     Pending   0          16s

Приложение находится в состоянии Pending. Это происходит потому, что PV для PVC уже выделен и находится на другой ноде. Т.е. в дальнейшем наше приложение можно будет запускать только на ноде, где создан PV для используемого в приложении PVC.

Удалим приложение, не удаляя PVC.

kubectl delete -f 02-example-pod.yaml

В файле манифеста изменим имя ноды кластера, куда приземляется приложение, на ноду где находится PV. И снова запустим приложение.

kubectl apply -f manifests/02-example-pod.yaml

Использование в StatefulSet

Как мы поняли из предыдущего примера, при использовании StorageClass связанных с local-path-provisioner мы должны внимательно следить где будут запускаться приложения.

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

При определении StatefulSet мы должны будем учесть следующие особенности:

  1. Явным образом определить ноды, на которых будут запускаться поды StatefulSet-та.
  2. Позаботиться о том, что бы на одной ноде запускался один под StatefulSet-та.

Пример манифеста StatefulSet 03-sts.yaml. В нём при помощи affinity определяются соответствующие условия размещения приложения.

      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: In
                    values:
                      - ws3.kryukov.local
                      - ws4.kryukov.local
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - test-sts
              topologyKey: "kubernetes.io/hostname"

Так же, особенностью StatefulSet-та является возможность непосредственно в манифесте указать volumeClaimTemplates.

  volumeClaimTemplates:
    - spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: local-path
        resources:
          requests:
            storage: 200Mi
      metadata:
        name: sts-volume

Задеплоим StatefulSet.

kubectl apply -f 03-sts.yaml

Попробуем изменить количество подов в StatefulSet

kubectl scale --replicas=1 sts/sts-volume-test
kubectl get pods
kubectl scale --replicas=3 sts/sts-volume-test
kubectl get pods
kubectl scale --replicas=2 sts/sts-volume-test
kubectl get pods

Посмотрите список PVC. Удалите не нужные.


<- Оглавление.