Содержание

Kubernetes: Longhorn, установка

Небольшое how-to по базовой настройке Cloud Native Distributed Block Storage for Kubernetes.

Допущения и ограничения

ВАЖНО: установить open-iscsi и NFS можно и через DaemonSet. Однако это не самый оптимальный способ, так как отладка будет сложнее — не только на уровне ОС, но и дополнительно через DaemonSet.

Немного о зависимостях

Longhorn требует в зависимостях NFS и open-iscsi, которые выполняют разные задачи.

open-iscsi

Когда нужен:

NFS

Когда нужен:

Таким образом, в большинстве случаев требуются оба компонента.

Валидация установки

Чтобы убедится, что ваши воркер ноды корректно настроены воспользуемся https://longhorn.io/docs/1.8.0/deploy/install/#using-the-longhorn-command-line-tool

Создайте конфиг с подключениями к кубу admin.conf, после тестовой проверки можно будет удалить

curl -sSfL -o longhornctl https://github.com/longhorn/cli/releases/download/v1.8.0/longhornctl-linux-amd64
chmod +x longhornctl
export KUBECONFIG=/etc/kubernetes/admin.conf
./longhornctl check preflight

Либо используя скрипт

sudo apt -y install jq
curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.8.0/scripts/environment_check.sh | bash

О стенде

Состоит из трёх мастеров и четырёх рабочих нод.

[k8s_master]
k8s-01.beta-82.win
k8s-02.beta-82.win
k8s-03.beta-82.win
 
[k8s_worker]
w-01.beta-82.win
w-02.beta-82.win
w-03.beta-82.win
w-04.beta-82.win

О replicaCount , degraded и faulted

replicaCount=2

Для большей надёжности можно использовать replicaCount=3, если нужно защититься от второго сбоя.

Degraded ( деградированный том )

Faulted ( отказавший том )

Главное отличие Degraded от Faulted

Установка Longhorn с использованием Helm

Для демонстрации faulted при отказе 2 из 4 нод я буду использовать replicaCount=3. Однако вы должны выбирать его самостоятельно, в зависимости от ваших задач.

В примере ниже будем устанавливать самую актуальную на данный момент версию 1.8.0. Подробнее об установке можно почитать здесь: Официальная документация Longhorn

Также может быть полезен файл longhorn.yml: Longhorn YAML

Приступим

helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn \
  --namespace longhorn-system --create-namespace --version 1.8.0 \
  --set defaultSettings.replicaCount=3 \
  --set defaultSettings.storageOverProvisioningPercentage=100 \
  --set defaultSettings.storageMinimalAvailablePercentage=15 \
  --set defaultSettings.nodeDownPodDeletionPolicy="delete-both-statefulset-and-deployment-pod" \
  --set defaultSettings.replicaAutoBalance="best-effort" \
  --set defaultSettings.dataLocality="best-effort" \
  --set defaultSettings.rebalanceDisksWhenLowSpace=true

Описание параметров

Параметр Значение Описание
defaultSettings.replicaCount 3 Количество реплик каждого тома. Чем больше, тем выше отказоустойчивость, но выше нагрузка на диск. При 3 том остаётся доступным при отказе 1 ноды.
defaultSettings.storageOverProvisioningPercentage 100 Указывает, на сколько процентов Longhorn может «переподписывать» (overprovision) дисковое пространство. Значение 100 означает, что выделять можно в 2 раза больше, чем фактический объём диска.
defaultSettings.storageMinimalAvailablePercentage 15 Минимальный порог свободного дискового пространства перед тем, как Longhorn перестанет создавать новые реплики. 15% означает, что если остаётся менее 15% места, новые реплики не создаются.
defaultSettings.nodeDownPodDeletionPolicy «delete-both-statefulset-and-deployment-pod» Определяет, что делать с подами, привязанными к сбойным нодам. Данное значение удаляет поды как из StatefulSet, так и из Deployment, если нода ушла в NotReady.
defaultSettings.replicaAutoBalance «best-effort» Автоматический баланс реплик между нодами. best-effort означает, что Longhorn старается равномерно распределять реплики при изменениях в кластере.
defaultSettings.dataLocality «best-effort» Определяет, будет ли Longhorn пытаться хранить реплику тома на той же ноде, где работает под. best-effort создаёт локальную копию, если это возможно, но не требует её наличия.
defaultSettings.rebalanceDisksWhenLowSpace true Включает автоматическое перемещение реплик с дисков, на которых заканчивается место, на менее загруженные диски или ноды.

Проверяем наши поды, как видим они поднялись

$ k get pods
NAME                                                READY   STATUS    RESTARTS        AGE
csi-attacher-79866cdcf8-grgs9                       1/1     Running   0               2m7s
csi-attacher-79866cdcf8-jvg8z                       1/1     Running   0               2m7s
csi-attacher-79866cdcf8-qknmr                       1/1     Running   0               2m7s
csi-provisioner-664cb5bdd5-dxpnt                    1/1     Running   0               2m7s
csi-provisioner-664cb5bdd5-qc85t                    1/1     Running   0               2m7s
csi-provisioner-664cb5bdd5-r2vfj                    1/1     Running   0               2m7s
csi-resizer-64f6fb4459-98qtn                        1/1     Running   0               2m7s
csi-resizer-64f6fb4459-l9v27                        1/1     Running   0               2m7s
csi-resizer-64f6fb4459-tvqr6                        1/1     Running   0               2m7s
csi-snapshotter-7b7db78f9-6gpj4                     1/1     Running   0               2m7s
csi-snapshotter-7b7db78f9-7k52m                     1/1     Running   0               2m7s
csi-snapshotter-7b7db78f9-jnc4k                     1/1     Running   0               2m7s
engine-image-ei-c2d50bcc-5jcgl                      1/1     Running   0               3m
engine-image-ei-c2d50bcc-8fb7v                      1/1     Running   0               3m
engine-image-ei-c2d50bcc-gmvhc                      1/1     Running   0               3m
engine-image-ei-c2d50bcc-jnhd4                      1/1     Running   0               3m
instance-manager-79fbdba3783ea7f6d1ea35496adae27f   1/1     Running   0               2m25s
instance-manager-93ee330ab4dd110c09b6c0dba65a6184   1/1     Running   0               2m19s
instance-manager-b565b7b70c365483ceeab00de81eb424   1/1     Running   0               2m29s
instance-manager-d5b24c31c174dd52fdabdad19038bbff   1/1     Running   0               2m19s
longhorn-csi-plugin-6995m                           3/3     Running   0               2m7s
longhorn-csi-plugin-9fhk5                           3/3     Running   0               2m7s
longhorn-csi-plugin-sb42r                           3/3     Running   0               2m7s
longhorn-csi-plugin-zxcpm                           3/3     Running   0               2m7s
longhorn-driver-deployer-7fc7ffcb7f-vbft2           1/1     Running   2 (2m57s ago)   3m24s
longhorn-manager-gfr55                              2/2     Running   0               3m24s
longhorn-manager-kswnm                              2/2     Running   0               3m24s
longhorn-manager-rsnw7                              2/2     Running   0               3m24s
longhorn-manager-wvjvk                              2/2     Running   0               3m24s
longhorn-ui-5fcc4bcfc7-4wl6s                        1/1     Running   0               3m24s
longhorn-ui-5fcc4bcfc7-lmrg9                        1/1     Running   0               3m24s

Longhorn UI

Для удобства может потребоваться доступ к Longhorn UI. Получить его можно с помощью следующей команды:

$ kubectl port-forward svc/longhorn-frontend -n longhorn-system 8080:80

после чего в браузере станет доступно http://127.0.0.1:8080/#/dashboard

Запуск тестового приложения

Сейчас я запущу небольшое тестовое приложение, на котором протестируем:

Я запустил небольшой манифест, в котором сначала создаётся PersistentVolumeClaim (longhorn-pvc) для StorageClasslonghorn, после чего развернул Deployment с подом, который использует наш PVC

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: longhorn-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: longhorn
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-app-label
  template:
    metadata:
      labels:
        app: go-app-label
    spec:
      containers:
      - image: denisitpro/go-example:latest
        name: go-app
        env:
          - name: AI_NAME
            value: "Ivan"
        ports:
        - containerPort: 8082
        readinessProbe:
          failureThreshold: 5
          httpGet:
            path: /
            port: 8082
          periodSeconds: 10
          successThreshold: 2
          timeoutSeconds: 3
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 8082
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
          initialDelaySeconds: 10
        resources:
          requests:
            cpu: 100m
            memory: 64Mi
          limits:
            cpu: 300m
            memory: 512Mi
        volumeMounts:
        - mountPath: /opt/example
          name: data-volume
      volumes:
      - name: data-volume
        persistentVolumeClaim:
          claimName: "longhorn-pvc"

Далее проверим текущее состояние Pod, PVC и PV в кластере.

$ k get pods
NAME                    READY   STATUS    RESTARTS   AGE
go-app-fd8f8d4c-4s4wg   1/1     Running   0          59s
$ k get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
longhorn-pvc   Bound    pvc-0b7a49a6-352c-4c0a-84af-e2b014dd75c5   1Gi        RWO            longhorn       <unset>                 62s
$ k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-0b7a49a6-352c-4c0a-84af-e2b014dd75c5   1Gi        RWO            Delete           Bound    go/longhorn-pvc   longhorn       <unset>                          58s

Имеет смысл также проверить, что наш StorageClass стал дефолтным.

$ k get sc
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn (default)   driver.longhorn.io   Delete          Immediate           true                   4m31s
longhorn-static      driver.longhorn.io   Delete          Immediate           true                   4m29s

Тестирование отказоустойчивости при потере одной рабочей ноды

Убеждаемся, что Pod действительно запущен на w-01.

$k get pv
$kubectl -n longhorn-system get volumes pvc-0b7a49a6-352c-4c0a-84af-e2b014dd75c5 -o jsonpath='{.status.ownerID}'
### output
w-01

Сначала создадим небольшой тестовый файл, прежде чем отключать рабочие ноды кластера.

$ kubectl exec -it go-app-fd8f8d4c-4s4wg -- bash
$ echo "42">  /opt/example/42.txt
$ cat /opt/example/42.txt
42

Убеждаемся, что Pod действительно запущен на w-01.

$ k get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP           NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-4s4wg   1/1     Running   0          9m10s   10.48.6.85   w-01   <none>           <none>

Отключаем рабочую ноду и проверяем, что кластер зафиксировал её как нерабочую.

Важно:

Под не завершится сразу — процесс займёт примерно 10 минут, это время можно сократить правильными настройками, но для данного теста это не имеет значения

$ k get nodes
NAME     STATUS     ROLES           AGE   VERSION
k8s-01   Ready      control-plane   43m   v1.31.1
k8s-02   Ready      control-plane   42m   v1.31.1
k8s-03   Ready      control-plane   42m   v1.31.1
w-01     NotReady   <none>          40m   v1.31.1
w-02     Ready      <none>          40m   v1.31.1
w-03     Ready      <none>          40m   v1.31.1
w-04     Ready      <none>          40m   v1.31.1
 
$k get pods -o wide
NAME                    READY   STATUS              RESTARTS   AGE   IP           NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-4s4wg   1/1     Terminating         0          16m   10.48.6.85   w-01   <none>           <none>
go-app-fd8f8d4c-c4trm   0/1     ContainerCreating   0          26s   <none>       w-03   <none>           <none>

Теперь проверим, что происходит с данными внутри пода.

$ k get pod
NAME                    READY   STATUS    RESTARTS   AGE
go-app-fd8f8d4c-5xq2n   1/1     Running   0          4m39s
$ kubectl exec -it go-app-fd8f8d4c-5xq2n -- bash
$root@go-app-fd8f8d4c-5xq2n:/opt/app# cat /opt/example/42.txt
42

Тестирование при потере двух рабочих нод

Продолжим эксперимент — отключим вторую рабочую ноду, предварительно добавив ещё одну строку в файл.

$ kubectl exec -it go-app-fd8f8d4c-5xq2n -- bash
$ echo "43">> /opt/example/42.txt
$ cat /opt/example/42.txt
42
43

Отключаем w-02, так как под сейчас находится на этой ноде.

$ k get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-5xq2n   1/1     Running   0          8m18s   10.48.4.213   w-02   <none>           <none>
$ kubectl -n longhorn-system get volumes pvc-0b7a49a6-352c-4c0a-84af-e2b014dd75c5 -o jsonpath='{.status.ownerID}'
w-02%

Теперь проверяем текущее состояние кластера и на какой ноде запущен под.

$ k get nodes
NAME     STATUS     ROLES           AGE   VERSION
k8s-01   Ready      control-plane   61m   v1.31.1
k8s-02   Ready      control-plane   61m   v1.31.1
k8s-03   Ready      control-plane   61m   v1.31.1
w-01     NotReady   <none>          59m   v1.31.1
w-02     NotReady   <none>          59m   v1.31.1
w-03     Ready      <none>          59m   v1.31.1
w-04     Ready      <none>          59m   v1.31.1
 
$ k get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP           NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-fcwc2   1/1     Running   0          4m39s   10.48.4.23   w-04   <none>           <none>

Проверка состояния тома после миграции пода, как мы видим, под переместился на w-04. Теперь проверим состояние volume.

k get volume -n longhorn-system
NAME                                       DATA ENGINE   STATE      ROBUSTNESS   SCHEDULED   SIZE         NODE   AGE
pvc-0b7a49a6-352c-4c0a-84af-e2b014dd75c5   v1            attached   degraded                 1073741824   w-04   36m

Теперь проверим, сохранились ли данные после миграции пода.

$ k get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP           NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-fcwc2   1/1     Running   0          8m10s   10.48.4.23   w-04   <none>           <none>
$ kubectl exec -it go-app-fd8f8d4c-fcwc2 -- bash
$ cat /opt/example/42.txt
42
43

Включение выключенных нод и отключение активной

Сейчас добавим данные, включим ранее выключенные ноды, затем отключим активную ноду и проверим состояние данных.

Это финальная проверка

$ kubectl exec -it go-app-fd8f8d4c-fcwc2 -- bash
$ echo "44">> /opt/example/42.txt
$ cat /opt/app# cat /opt/example/42.txt
42
43
44

Пришло время провести завершающую проверку

$ k get nodes
NAME     STATUS     ROLES           AGE   VERSION
k8s-01   Ready      control-plane   79m   v1.31.1
k8s-02   Ready      control-plane   78m   v1.31.1
k8s-03   Ready      control-plane   78m   v1.31.1
w-01     Ready      <none>          76m   v1.31.1
w-02     Ready      <none>          76m   v1.31.1
w-03     Ready      <none>          76m   v1.31.1
w-04     NotReady   <none>          76m   v1.31.1
 
$ k get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP           NODE   NOMINATED NODE   READINESS GATES
go-app-fd8f8d4c-fpfp4   1/1     Running   0          32s   10.48.6.94   w-01   <none>           <none>
 
$ kubectl exec -it go-app-fd8f8d4c-fpfp4 -- bash
$ cat /opt/example/42.txt
42
43
44

Нюансы replicaCount=3

ВАЖНО: при установке через Helm параметр auto-salvage уже установлен в true, но я решил оставить эту информацию для понимания логики работы.


В сценарии, когда у нас есть 4 рабочие ноды, а replicaCount=3, при потере двух нод мы получаем не совсем то, что ожидали — том станет faulted. Печально.

Причина в дизайне высокой доступности ( HA ) Longhorn:

  1. Longhorn требует минимум 2 реплики для работы тома, если у него replicaCount=3.
  2. Если остаётся только одна реплика, Longhorn не считает данные достаточно надёжными → том переходит в faulted.
  3. Это сделано для защиты от silent data corruption и случаев, когда последняя оставшаяся реплика может быть повреждена.

Как сделать так, чтобы том не переходил в faulted ?

Настроить Replica Auto Salvage: defaultSettings.autoSalvage=true

В целом, лучше придерживаться схемы: количество нод должно быть replicaCount + 2.

Полезные команды

Проверка состояния PVC

kubectl get volumes.longhorn.io -n longhorn-system