- Update Longhorn Node API versions from v1beta1 to v1beta2 in disk management tasks - Add venv_path configuration and set ansible_python_interpreter to use virtual environment - Introduce python3.14-venv.yml task and python314_venv role for Python virtual environment setup |
||
|---|---|---|
| .. | ||
| defaults | ||
| handlers | ||
| meta | ||
| tasks | ||
| README.md | ||
Longhorn — стандарт постоянного хранилища в кластере
Этот документ описывает стандарт работы с Longhorn в нашем кластере:
как подключать хранилище к приложениям, добавлять диски, управлять репликами и резервными копиями.
Является эталоном для разработчиков и DevOps.
Содержание
- Архитектура: как Longhorn работает в кластере
- Два этапа деплоя: longhorn_prereqs и longhorn
- Конфигурация дисков
- Пример 1: PVC для одного Pod (ReadWriteOnce)
- Пример 2: StatefulSet с несколькими репликами
- Пример 3: база данных (PostgreSQL) с Longhorn
- Пример 4: несколько компонентов с общим хранилищем
- Реплики: сколько и когда менять
- Резервное копирование и восстановление
- Расширение тома
- Добавление нового worker-узла с дисками
- Диагностика и типичные ошибки
- Чеклист перед использованием хранилища
1. Архитектура: как Longhorn работает в кластере
Схема
┌─────────────────────────────────────────────────────────────────┐
│ k8s-worker-01 (10.203.0.96) │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ /dev/sdb │ │ /dev/sdc │ │
│ │ XFS │ │ XFS │ │
│ │ /mnt/longhorn-│ │ /mnt/longhorn-│ │
│ │ disk1 │ │ disk2 │ │
│ └───────┬────────┘ └───────┬────────┘ │
│ │ │ │
│ Longhorn Manager Pod ─────────┘ │
│ Longhorn Engine Pod (управляет репликами) │
│ Instance Manager Pod (iSCSI-таргет для kubelet) │
│ │
│ kubelet → iSCSI → Longhorn Engine → Longhorn Replica │
│ (файлы на диске) │
└─────────────────────────────────────────────────────────────────┘
PersistentVolumeClaim (ns: myapp) ──► StorageClass: longhorn
│
▼
PersistentVolume
(Longhorn Volume)
│
Replicas: 1 (сейчас)
→ расположены на worker-нодах
Компоненты
| Компонент | Назначение |
|---|---|
| longhorn-manager | Оркестрирует тома, реплики, бэкапы; CRD-контроллер |
| longhorn-engine | Процесс ввода-вывода для каждого тома |
| instance-manager | Управляет engine и replica инстансами |
| longhorn-ui | Web-интерфейс (доступен через Traefik на порту 10002) |
| CSI driver | Kubernetes CSI-интерфейс; обрабатывает PVC → PV |
StorageClass
Longhorn создаёт StorageClass longhorn при установке. Это StorageClass по умолчанию в кластере.
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ...
# longhorn (default) driver.longhorn.io Delete Immediate ...
ReclaimPolicy: Delete — при удалении PVC том удаляется. Если нужно сохранить данные — делайте снапшот или бэкап перед удалением PVC.
2. Два этапа деплоя: longhorn_prereqs и longhorn
Longhorn разворачивается через два плейбука, которые нужно запускать в порядке:
1. setup_longhorn.yml
├── role: longhorn_prereqs (runs on: workers)
│ ├── packages.yml — iscsi-initiator-utils, nfs-utils, cryptsetup
│ ├── kernel.yml — iscsi_tcp, dm_crypt модули
│ ├── firewall.yml — NFS (2049/tcp), portmapper (111/tcp, 111/udp)
│ ├── selinux.yml — CIL-политика для iscsid (Rocky Linux 9)
│ └── disks.yml — форматирование XFS, монтирование по UUID
│
└── role: longhorn (runs on: manager_nodes)
├── annotate.yml — kubectl annotate node: disk config JSON
└── helm.yml — helm upgrade --install longhorn/longhorn
Когда перезапускать плейбук
| Ситуация | Что запускать |
|---|---|
| Первичная установка | setup_longhorn.yml полностью |
| Добавление нового worker-узла | setup_longhorn.yml с --limit k8s-worker-NN для prereqs, затем полностью для annotate |
| Добавление диска к существующей ноде | setup_longhorn.yml (prereqs: disks.yml пропустится для существующих; annotate обновит аннотацию) |
| Обновление версии Longhorn | Изменить longhorn_chart_version, запустить только longhorn role |
| Изменение параметров Helm | Изменить переменные, запустить только longhorn role |
3. Конфигурация дисков
inventory/prod/group_vars/workers.yml — глобальный дефолт
longhorn_disks:
- device: /dev/sdb
mountpoint: /mnt/longhorn-disk1
- device: /dev/sdc
mountpoint: /mnt/longhorn-disk2
Это применяется ко всем worker-нодам.
Если у разных нод разные диски — переопределить в hosts.yml (host_vars).
inventory/prod/hosts.yml — переопределение для конкретной ноды
workers:
hosts:
k8s-worker-01:
ansible_host: 10.203.0.96
longhorn_disks: # переопределяет group_vars для этой ноды
- device: /dev/sdb
mountpoint: /mnt/longhorn-disk1
- device: /dev/nvme0n1
mountpoint: /mnt/longhorn-nvme # SSD на этой ноде
k8s-worker-02:
ansible_host: 10.203.0.X
longhorn_disks:
- device: /dev/sdb
mountpoint: /mnt/longhorn-disk1 # только один диск
Что происходит с дисками при деплое
filesystemформатирует устройство в XFS (идемпотентно — пропускает уже отформатированные)- Создаётся директория mountpoint
blkidполучает UUID устройства/etc/fstabдобавляется записьUUID=... /mnt/... xfs defaults,nofail- Диск монтируется немедленно
- Аннотация ноды:
node.longhorn.io/default-disks-config=[{"path":"/mnt/longhorn-disk1",...}] - Longhorn читает аннотацию при старте и регистрирует диски
nofailв fstab критически важен: если диск недоступен при загрузке — нода загрузится, а не зависнет.
Проверка дисков в Longhorn
# Посмотреть ноды и их диски в Longhorn
kubectl get nodes.longhorn.io -n longhorn-system
# Детали ноды
kubectl describe node.longhorn.io k8s-worker-01 -n longhorn-system
# Через UI: http://10.203.0.96:10002 → Node
4. Пример 1: PVC для одного Pod (ReadWriteOnce)
Сценарий: одиночный Pod с постоянным хранилищем (файловый кэш, локальные данные).
PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: my-app
spec:
accessModes:
- ReadWriteOnce # один узел в режиме чтения-записи
storageClassName: longhorn
resources:
requests:
storage: 5Gi
Deployment с PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-app
spec:
replicas: 1 # ВАЖНО: ReadWriteOnce — только 1 реплика!
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: registry.gigacoms.info/myteam/my-app:v1.0.0
volumeMounts:
- name: data
mountPath: /app/data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
ReadWriteOnce означает: том примонтирован к одному узлу одновременно.
Несколько Pod могут использовать один RWO-том, только если все они на одном узле.
Сreplicas: >1и DaemonSet это создаст проблемы при планировании на разные ноды.
Проверить что PVC создан и привязан
kubectl get pvc -n my-app
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# app-data Bound pvc-abc12345 5Gi RWO longhorn 30s
# Посмотреть на том в Longhorn
kubectl get volumes.longhorn.io -n longhorn-system | grep app-data
5. Пример 2: StatefulSet с несколькими репликами
Сценарий: очередь сообщений или key-value хранилище (Redis, RabbitMQ) с несколькими репликами.
Каждая реплика StatefulSet получает свой отдельный PVC.
StatefulSet с volumeClaimTemplates
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: cache
spec:
serviceName: redis
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
command:
- redis-server
- --appendonly yes
- --dir /data
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumeClaimTemplates: # Kubernetes создаёт PVC для каждой реплики
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"] # каждая реплика = свой том
storageClassName: longhorn
resources:
requests:
storage: 10Gi
При replicas: 3 Kubernetes создаст три PVC:
data-redis-0→ 10Gidata-redis-1→ 10Gidata-redis-2→ 10Gi
kubectl get pvc -n cache
# NAME STATUS VOLUME CAPACITY ACCESS MODES
# data-redis-0 Bound pvc-aaa11111 10Gi RWO
# data-redis-1 Bound pvc-bbb22222 10Gi RWO
# data-redis-2 Bound pvc-ccc33333 10Gi RWO
Важно: при уменьшении
replicasPVC не удаляются автоматически. Это намеренное поведение Kubernetes — защита от потери данных. Удалять PVC вручную после проверки.
6. Пример 3: база данных (PostgreSQL) с Longhorn
Сценарий: продакшн PostgreSQL с выделенным Longhorn-томом.
Объекты
---
# PVC отдельно от StatefulSet — для явного управления жизненным циклом
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: myapp
annotations:
# Документировать содержимое тома для оператора
longhorn.io/description: "PostgreSQL data for myapp — created 2026-01-01"
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 50Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: myapp
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
securityContext:
fsGroup: 999 # postgres GID — Longhorn создаст том с нужными правами
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata # подпапка, не корень тома
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
readinessProbe:
exec:
command: [pg_isready, -U, myuser, -d, myapp]
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
Переменная
PGDATA=/var/lib/postgresql/data/pgdata— подпапка внутри mountPath.
PostgreSQL требует пустую директорию при инициализации. Если смонтировать том прямо в/var/lib/postgresql/data, Longhorn создаёт тамlost+foundи PostgreSQL падает с ошибкой.
Резервное копирование PostgreSQL
Два уровня резервирования:
- Longhorn snapshot — снапшот файловой системы (быстрый, crash-consistent)
- pg_dump — логический дамп (переносимый, consistent)
# Логический дамп из Pod
kubectl exec -n myapp postgres-0 -- \
pg_dump -U myuser myapp | gzip > backup-$(date +%Y%m%d).sql.gz
# Восстановление
kubectl exec -i -n myapp postgres-0 -- \
psql -U myuser myapp < backup-20260101.sql
7. Пример 4: несколько компонентов с общим хранилищем
Сценарий: несколько Pod должны читать один и тот же набор файлов (например, общий медиа-каталог между CMS и CDN-прокси).
Проблема: ReadWriteOnce не подходит
ReadWriteOnce разрешает запись только с одного узла. При нескольких Pod на разных узлах — ошибка монтирования.
Решение A: все Pod на одном узле (nodeSelector)
# Принудительно разместить все Pod на одной ноде
spec:
nodeSelector:
kubernetes.io/hostname: k8s-worker-01
Тогда PVC типа ReadWriteOnce работает с несколькими Pod на одном узле.
Решение B: NFS через Longhorn (ReadWriteMany)
Longhorn поддерживает ReadWriteMany через встроенный NFS-шлюз начиная с версии 1.5.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-media
namespace: cms
spec:
accessModes:
- ReadWriteMany # несколько узлов одновременно
storageClassName: longhorn
resources:
requests:
storage: 100Gi
---
# CMS Pod — пишет медиафайлы
apiVersion: apps/v1
kind: Deployment
metadata:
name: cms
namespace: cms
spec:
replicas: 1
template:
spec:
containers:
- name: cms
image: registry.gigacoms.info/myteam/cms:v2.0.0
volumeMounts:
- name: media
mountPath: /app/media
volumes:
- name: media
persistentVolumeClaim:
claimName: shared-media
---
# CDN-прокси Pod — читает медиафайлы с того же тома
apiVersion: apps/v1
kind: Deployment
metadata:
name: cdn-proxy
namespace: cms
spec:
replicas: 3 # несколько реплик, разные ноды — RWX позволяет
template:
spec:
containers:
- name: cdn-proxy
image: nginx:1.27-alpine
volumeMounts:
- name: media
mountPath: /usr/share/nginx/html/media
readOnly: true # прокси только читает
volumes:
- name: media
persistentVolumeClaim:
claimName: shared-media
Ограничение RWX в Longhorn: производительность ниже, чем RWO (идёт через NFS). Для баз данных использовать только RWO. RWX — для файлов с редкой записью и частым чтением.
8. Реплики: сколько и когда менять
Текущая конфигурация кластера
Кластер: 1 worker-нода (k8s-worker-01)
Реплики: 1 (longhorn_default_replica_count: 1)
Одна реплика означает: данные не реплицируются. При потере ноды — данные недоступны до восстановления узла.
Когда увеличивать количество реплик
| Число worker-нод | Рекомендуемые реплики | Причина |
|---|---|---|
| 1 | 1 | Некуда реплицировать |
| 2 | 2 | Реплика на каждой ноде; потеря одной ноды — нет прерывания |
| 3+ | 3 | Quorum; потеря одной ноды — данные доступны |
Как изменить количество реплик
Глобально (для новых томов):
# inventory/prod/group_vars/longhorn.yml или group_vars/k8s_cluster.yml
longhorn_default_replica_count: 2 # изменить при добавлении worker-нод
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
# Применит только Helm-апгрейд, не затронет prereqs и диски
Для конкретного тома (через UI или kubectl):
# Через Longhorn API
kubectl patch volume.longhorn.io pvc-abc12345 -n longhorn-system \
--type=merge -p '{"spec":{"numberOfReplicas":2}}'
Для конкретного PVC через StorageClass:
# Создать отдельный StorageClass с другим количеством реплик
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-replicated
provisioner: driver.longhorn.io
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "30"
dataLocality: "disabled"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
# PVC с кастомным StorageClass
spec:
storageClassName: longhorn-replicated # вместо longhorn
soft anti-affinity (при числе нод < числа реплик)
Если numberOfReplicas: 2, но доступна только 1 нода — Longhorn не создаст том (нет места для реплики).
Для обхода в dev-окружении включить replicaNodeLevelSoftAntiAffinity:
longhorn_replica_node_soft_anti_affinity: "true" # НЕ использовать в production
9. Резервное копирование и восстановление
Longhorn Snapshot (мгновенный снапшот тома)
Снапшот — point-in-time копия тома на том же диске. Защищает от случайного удаления данных, но не от потери диска.
# Создать снапшот через kubectl
kubectl create -f - <<EOF
apiVersion: longhorn.io/v1beta2
kind: Snapshot
metadata:
name: myapp-snapshot-$(date +%Y%m%d-%H%M)
namespace: longhorn-system
spec:
volume: pvc-abc12345 # имя Longhorn-тома (не PVC)
EOF
# Список снапшотов
kubectl get snapshots.longhorn.io -n longhorn-system | grep pvc-abc12345
Через UI: Longhorn → Volumes → → Create Snapshot.
Backup в S3-совместимое хранилище
Для полноценного backup необходимо настроить внешнее хранилище (S3, NFS):
# Настройка backup target через Longhorn Settings
# UI: http://10.203.0.96:10002 → Settings → Backup Target
# Значение: s3://bucket-name@region/prefix (для S3)
# nfs://server/path (для NFS)
# Создать backup существующего снапшота
kubectl create -f - <<EOF
apiVersion: longhorn.io/v1beta2
kind: BackupVolume
metadata:
name: pvc-abc12345
namespace: longhorn-system
EOF
Восстановление из снапшота (rollback)
# Через UI: Longhorn → Volumes → <volume> → Snapshots → Revert
# Или через kubectl (том должен быть detached)
# 1. Масштабировать Deployment в 0 (отмонтировать том)
kubectl scale deployment my-app -n my-app --replicas=0
# 2. Revert через UI или API
# 3. Вернуть Deployment
kubectl scale deployment my-app -n my-app --replicas=1
10. Расширение тома
Longhorn поддерживает расширение тома без остановки Pod (online expansion).
Расширить PVC
# Отредактировать PVC — увеличить storage (уменьшение не поддерживается)
kubectl patch pvc app-data -n my-app \
--type=merge -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
# Проверить прогресс
kubectl get pvc app-data -n my-app
# STATUS: пока идёт расширение, будет Bound с условием FileSystemResizePending
# После завершения
kubectl describe pvc app-data -n my-app | grep -A5 Conditions
Расширение на уровне файловой системы происходит автоматически при следующем монтировании Pod (или сразу, если allowVolumeExpansion: true в StorageClass — а у Longhorn это включено по умолчанию).
Проверить размер тома внутри Pod
kubectl exec -n my-app $(kubectl get pod -n my-app -l app=my-app -o name) \
-- df -h /app/data
11. Добавление нового worker-узла с дисками
При добавлении k8s-worker-02:
Шаг 1: добавить в инвентарь
# inventory/prod/hosts.yml
workers:
hosts:
k8s-worker-01:
ansible_host: 10.203.0.96
k8s-worker-02: # новый узел
ansible_host: 10.203.0.XX
longhorn_disks: # если диски отличаются от дефолта
- device: /dev/sdb
mountpoint: /mnt/longhorn-disk1
Шаг 2: запустить prereqs только для нового узла
# Подготовка ОС только для новой ноды
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml \
--limit k8s-worker-02 \
--tags longhorn_prereqs
Шаг 3: запустить join нового воркера в кластер
ansible-playbook -i inventory/prod playbooks/setup_worker_plane.yml \
--limit k8s-worker-02
Шаг 4: аннотировать ноду и обновить Longhorn
# Полный плейбук обновит аннотации для всех нод включая новую
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
Шаг 5: увеличить количество реплик
# group_vars/k8s_cluster.yml или longhorn.yml
longhorn_default_replica_count: 2 # было 1
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
12. Диагностика и типичные ошибки
Основные команды
# Состояние всех томов
kubectl get volumes.longhorn.io -n longhorn-system
# Детали тома
kubectl describe volume.longhorn.io <volume-name> -n longhorn-system
# Состояние реплик
kubectl get replicas.longhorn.io -n longhorn-system | grep <volume-name>
# Состояние нод Longhorn
kubectl get nodes.longhorn.io -n longhorn-system
# Логи manager
kubectl logs -n longhorn-system -l app=longhorn-manager --tail=100
# Логи CSI driver
kubectl logs -n longhorn-system -l app=longhorn-csi-plugin --tail=50
# PVC не привязывается — посмотреть события
kubectl describe pvc <pvc-name> -n <namespace>
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
# Посмотреть все PVC в кластере
kubectl get pvc -A
Таблица ошибок
| Симптом | Причина | Решение |
|---|---|---|
PVC в статусе Pending |
Нет доступных дисков / нода не готова | kubectl get nodes.longhorn.io -n longhorn-system — проверить schedulable |
PVC Pending: no schedulable node |
Включён soft anti-affinity=false, только 1 нода, replicas=2 | Уменьшить реплики до 1 или включить soft anti-affinity |
Pod не стартует: volume not found |
Том деградировал или удалён | Проверить kubectl get volumes.longhorn.io -n longhorn-system |
Том в статусе Degraded |
Нода с репликой недоступна | Подождать восстановления ноды или rebuild replica |
Том в статусе Faulted |
Все реплики потеряны | Восстановить из бэкапа |
iscsiadm ошибки в логах |
SELinux блокирует iscsid | Перепроверить CIL-политику: semodule -l | grep local_longhorn |
| Медленная запись | Все реплики на одном диске | Разнести реплики по дискам (Disk Tags) |
lost+found в PostgreSQL data dir |
PGDATA смонтирован прямо на корень тома | Установить PGDATA=/var/lib/postgresql/data/pgdata (подпапка) |
| Диск не виден в Longhorn | Аннотация ноды устарела | ansible-playbook setup_longhorn.yml — обновит annotate |
Проверка iSCSI на ноде
# Выполнить через SSH на worker-ноде или kubectl exec
# Статус iscsid
systemctl status iscsid
# Список активных iSCSI-сессий (примонтированные тома)
iscsiadm -m session
# Загружены ли нужные модули ядра
lsmod | grep iscsi_tcp
lsmod | grep dm_crypt
Принудительный detach застрявшего тома
Если Pod удалён, но том остался в статусе attached:
# Через UI: Longhorn → Volumes → <volume> → Detach
# Или через kubectl
kubectl patch volume.longhorn.io <volume-name> -n longhorn-system \
--type=merge -p '{"spec":{"nodeID":""}}'
13. Чеклист перед использованием хранилища
Первое использование Longhorn в кластере
setup_longhorn.ymlвыполнен успешно (prereqs + helm install)- Все worker-ноды показывают статус
Readyв UI Longhorn (http://10.203.0.96:10002) - Диски видны в UI Longhorn: Node → Disks
- StorageClass
longhornсуществует:kubectl get storageclass - Тестовый PVC создаётся и переходит в
Bound:kubectl apply -f test-pvc.yaml
Перед каждым новым PVC / StatefulSet
- Выбран правильный
accessMode:ReadWriteOnce— один узел, обычные Pod и StatefulSetReadWriteMany— несколько узлов, только через Longhorn NFS
storageClassName: longhornуказан явно (не полагаться на default при нескольких StorageClass)- Указан разумный размер тома (расширить можно, уменьшить — нет)
- Для PostgreSQL:
PGDATAустановлен в подпапку,fsGroup: 999в securityContext - Для StatefulSet: использован
volumeClaimTemplates, неvolumes + PVC resources.requestsиlimitsзаданы для контейнеров
После деплоя
- PVC в статусе
Bound:kubectl get pvc -n <namespace> - Pod в статусе
Running:kubectl get pods -n <namespace> - Том виден в Longhorn UI со статусом
Healthy - Данные доступны внутри Pod:
kubectl exec -n <ns> <pod> -- ls /mountpath - (для продакшн-данных) Настроен регулярный snapshot или backup