Kubernetes: Longhorn, установка
Небольшое how-to по базовой настройке Cloud Native Distributed Block Storage for Kubernetes.
Допущения и ограничения
- У вас уже есть готовый кластер K8s.
- Вы уже установили необходимые зависимости — Installation Requirements.
- Либо воспользовались готовой ролью — ansible-all (Longhorn dependencies).
- У вас отключен multipathd — https://longhorn.io/kb/troubleshooting-volume-with-multipath/
ВАЖНО: установить open-iscsi
и NFS
можно и через DaemonSet. Однако это не самый оптимальный способ, так как отладка будет сложнее — не только на уровне ОС, но и дополнительно через DaemonSet.
Немного о зависимостях
Longhorn требует в зависимостях NFS
и open-iscsi
, которые выполняют разные задачи.
open-iscsi
Когда нужен:
- Longhorn использует iSCSI для подключения к виртуальным дискам (longhorn-volumes) с нод, на которых запущены поды.
- Требуется, если ты используешь Longhorn в качестве хранилища для блоковых устройств, например, для подов с ReadWriteOnce (RWO) томами.
- Позволяет подам монтировать диски, даже если они физически находятся на другой ноде.
NFS
Когда нужен:
- Если у тебя есть приложения, которым требуется ReadWriteMany (RWX), то есть одновременно монтируемые тома (например, для WordPress, Nextcloud, shared data storage).
- Если ты используешь Longhorn со встроенным NFS-гейтом (
longhorn-nfs-provisioner
), который позволяет использовать Longhorn-тома как NFS-ресурсы. - Если часть приложений не поддерживает 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
- Longhorn хранит каждую volume в двух копиях (репликах) на разных нодах.
- Если нода падает, у тебя остаётся только одна реплика, и том становится degraded (деградированным), но остаётся доступным.
Для большей надёжности можно использовать replicaCount=3
, если нужно защититься от второго сбоя.
Degraded ( деградированный том )
- Это состояние, когда потеряна одна или несколько реплик, но остаётся достаточно копий для работы.
- Том остаётся доступным и продолжает работать.
- Longhorn автоматически создаст новую реплику, если есть место на оставшихся нодах.
Faulted ( отказавший том )
- Это критическое состояние, при котором все доступные реплики утеряны.
- Данные в томе становятся недоступными (он не может быть примонтирован).
- Если в Longhorn нет активной реплики тома, он не сможет восстановиться автоматически.
Главное отличие Degraded от Faulted
- Degraded = том ещё работает, но данных меньше, чем
replicaCount
. - Faulted = том полностью вышел из строя, данных нет.
- Если том degraded, но не faulted, Longhorn автоматически восстановится при вводе новых нод.
- Если том 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
) для StorageClass
— longhorn
, после чего развернул 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 минут, это время можно сократить правильными настройками, но для данного теста это не имеет значения
- 5 минут — пока Kubernetes посчитает, что нода мертва.
- 5 минут — дополнительная задержка перед удалением пода.
$ 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:
- Longhorn требует минимум 2 реплики для работы тома, если у него
replicaCount=3
. - Если остаётся только одна реплика, Longhorn не считает данные достаточно надёжными → том переходит в faulted.
- Это сделано для защиты от silent data corruption и случаев, когда последняя оставшаяся реплика может быть повреждена.
Как сделать так, чтобы том не переходил в faulted ?
Настроить Replica Auto Salvage: defaultSettings.autoSalvage=true
В целом, лучше придерживаться схемы: количество нод должно быть replicaCount + 2.
Полезные команды
Проверка состояния PVC
kubectl get volumes.longhorn.io -n longhorn-system