833 lines
32 KiB
Markdown
833 lines
32 KiB
Markdown
# Longhorn — стандарт постоянного хранилища в кластере
|
||
|
||
Этот документ описывает стандарт работы с Longhorn в нашем кластере:
|
||
как подключать хранилище к приложениям, добавлять диски, управлять репликами и резервными копиями.
|
||
Является эталоном для разработчиков и DevOps.
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Архитектура: как Longhorn работает в кластере](#1-архитектура-как-longhorn-работает-в-кластере)
|
||
2. [Два этапа деплоя: longhorn_prereqs и longhorn](#2-два-этапа-деплоя-longhorn_prereqs-и-longhorn)
|
||
3. [Конфигурация дисков](#3-конфигурация-дисков)
|
||
4. [Пример 1: PVC для одного Pod (ReadWriteOnce)](#4-пример-1-pvc-для-одного-pod-readwriteonce)
|
||
5. [Пример 2: StatefulSet с несколькими репликами](#5-пример-2-statefulset-с-несколькими-репликами)
|
||
6. [Пример 3: база данных (PostgreSQL) с Longhorn](#6-пример-3-база-данных-postgresql-с-longhorn)
|
||
7. [Пример 4: несколько компонентов с общим хранилищем](#7-пример-4-несколько-компонентов-с-общим-хранилищем)
|
||
8. [Реплики: сколько и когда менять](#8-реплики-сколько-и-когда-менять)
|
||
9. [Резервное копирование и восстановление](#9-резервное-копирование-и-восстановление)
|
||
10. [Расширение тома](#10-расширение-тома)
|
||
11. [Добавление нового worker-узла с дисками](#11-добавление-нового-worker-узла-с-дисками)
|
||
12. [Диагностика и типичные ошибки](#12-диагностика-и-типичные-ошибки)
|
||
13. [Чеклист перед использованием хранилища](#13-чеклист-перед-использованием-хранилища)
|
||
|
||
---
|
||
|
||
## 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 по умолчанию** в кластере.
|
||
|
||
```bash
|
||
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 — глобальный дефолт
|
||
|
||
```yaml
|
||
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 — переопределение для конкретной ноды
|
||
|
||
```yaml
|
||
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 # только один диск
|
||
```
|
||
|
||
### Что происходит с дисками при деплое
|
||
|
||
1. `filesystem` форматирует устройство в XFS (идемпотентно — пропускает уже отформатированные)
|
||
2. Создаётся директория mountpoint
|
||
3. `blkid` получает UUID устройства
|
||
4. `/etc/fstab` добавляется запись `UUID=... /mnt/... xfs defaults,nofail`
|
||
5. Диск монтируется немедленно
|
||
6. Аннотация ноды: `node.longhorn.io/default-disks-config=[{"path":"/mnt/longhorn-disk1",...}]`
|
||
7. Longhorn читает аннотацию при старте и регистрирует диски
|
||
|
||
> `nofail` в fstab критически важен: если диск недоступен при загрузке — нода загрузится, а не зависнет.
|
||
|
||
### Проверка дисков в Longhorn
|
||
|
||
```bash
|
||
# Посмотреть ноды и их диски в 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
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: app-data
|
||
namespace: my-app
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteOnce # один узел в режиме чтения-записи
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 5Gi
|
||
```
|
||
|
||
### Deployment с PVC
|
||
|
||
```yaml
|
||
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 создан и привязан
|
||
|
||
```bash
|
||
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
|
||
|
||
```yaml
|
||
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` → 10Gi
|
||
- `data-redis-1` → 10Gi
|
||
- `data-redis-2` → 10Gi
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
> **Важно:** при уменьшении `replicas` PVC **не удаляются автоматически**. Это намеренное поведение Kubernetes — защита от потери данных. Удалять PVC вручную после проверки.
|
||
|
||
---
|
||
|
||
## 6. Пример 3: база данных (PostgreSQL) с Longhorn
|
||
|
||
**Сценарий:** продакшн PostgreSQL с выделенным Longhorn-томом.
|
||
|
||
### Объекты
|
||
|
||
```yaml
|
||
---
|
||
# 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
|
||
|
||
Два уровня резервирования:
|
||
|
||
1. **Longhorn snapshot** — снапшот файловой системы (быстрый, crash-consistent)
|
||
2. **pg_dump** — логический дамп (переносимый, consistent)
|
||
|
||
```bash
|
||
# Логический дамп из 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)
|
||
|
||
```yaml
|
||
# Принудительно разместить все Pod на одной ноде
|
||
spec:
|
||
nodeSelector:
|
||
kubernetes.io/hostname: k8s-worker-01
|
||
```
|
||
|
||
Тогда PVC типа `ReadWriteOnce` работает с несколькими Pod на одном узле.
|
||
|
||
### Решение B: NFS через Longhorn (ReadWriteMany)
|
||
|
||
Longhorn поддерживает `ReadWriteMany` через встроенный NFS-шлюз начиная с версии 1.5.
|
||
|
||
```yaml
|
||
---
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: shared-media
|
||
namespace: cms
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteMany # несколько узлов одновременно
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 100Gi
|
||
```
|
||
|
||
```yaml
|
||
---
|
||
# 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; потеря одной ноды — данные доступны |
|
||
|
||
### Как изменить количество реплик
|
||
|
||
**Глобально** (для новых томов):
|
||
|
||
```yaml
|
||
# inventory/prod/group_vars/longhorn.yml или group_vars/k8s_cluster.yml
|
||
longhorn_default_replica_count: 2 # изменить при добавлении worker-нод
|
||
```
|
||
|
||
```bash
|
||
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
|
||
# Применит только Helm-апгрейд, не затронет prereqs и диски
|
||
```
|
||
|
||
**Для конкретного тома** (через UI или kubectl):
|
||
|
||
```bash
|
||
# Через Longhorn API
|
||
kubectl patch volume.longhorn.io pvc-abc12345 -n longhorn-system \
|
||
--type=merge -p '{"spec":{"numberOfReplicas":2}}'
|
||
```
|
||
|
||
**Для конкретного PVC через StorageClass**:
|
||
|
||
```yaml
|
||
# Создать отдельный 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
|
||
```
|
||
|
||
```yaml
|
||
# PVC с кастомным StorageClass
|
||
spec:
|
||
storageClassName: longhorn-replicated # вместо longhorn
|
||
```
|
||
|
||
### soft anti-affinity (при числе нод < числа реплик)
|
||
|
||
Если `numberOfReplicas: 2`, но доступна только 1 нода — Longhorn не создаст том (нет места для реплики).
|
||
Для обхода в dev-окружении включить `replicaNodeLevelSoftAntiAffinity`:
|
||
|
||
```yaml
|
||
longhorn_replica_node_soft_anti_affinity: "true" # НЕ использовать в production
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Резервное копирование и восстановление
|
||
|
||
### Longhorn Snapshot (мгновенный снапшот тома)
|
||
|
||
Снапшот — point-in-time копия тома на том же диске. Защищает от случайного удаления данных, но не от потери диска.
|
||
|
||
```bash
|
||
# Создать снапшот через 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 → <volume> → Create Snapshot.
|
||
|
||
### Backup в S3-совместимое хранилище
|
||
|
||
Для полноценного backup необходимо настроить внешнее хранилище (S3, NFS):
|
||
|
||
```yaml
|
||
# Настройка backup target через Longhorn Settings
|
||
# UI: http://10.203.0.96:10002 → Settings → Backup Target
|
||
# Значение: s3://bucket-name@region/prefix (для S3)
|
||
# nfs://server/path (для NFS)
|
||
```
|
||
|
||
```bash
|
||
# Создать backup существующего снапшота
|
||
kubectl create -f - <<EOF
|
||
apiVersion: longhorn.io/v1beta2
|
||
kind: BackupVolume
|
||
metadata:
|
||
name: pvc-abc12345
|
||
namespace: longhorn-system
|
||
EOF
|
||
```
|
||
|
||
### Восстановление из снапшота (rollback)
|
||
|
||
```bash
|
||
# Через 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
|
||
|
||
```bash
|
||
# Отредактировать 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
|
||
|
||
```bash
|
||
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: добавить в инвентарь
|
||
|
||
```yaml
|
||
# 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 только для нового узла
|
||
|
||
```bash
|
||
# Подготовка ОС только для новой ноды
|
||
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml \
|
||
--limit k8s-worker-02 \
|
||
--tags longhorn_prereqs
|
||
```
|
||
|
||
### Шаг 3: запустить join нового воркера в кластер
|
||
|
||
```bash
|
||
ansible-playbook -i inventory/prod playbooks/setup_worker_plane.yml \
|
||
--limit k8s-worker-02
|
||
```
|
||
|
||
### Шаг 4: аннотировать ноду и обновить Longhorn
|
||
|
||
```bash
|
||
# Полный плейбук обновит аннотации для всех нод включая новую
|
||
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
|
||
```
|
||
|
||
### Шаг 5: увеличить количество реплик
|
||
|
||
```yaml
|
||
# group_vars/k8s_cluster.yml или longhorn.yml
|
||
longhorn_default_replica_count: 2 # было 1
|
||
```
|
||
|
||
```bash
|
||
ansible-playbook -i inventory/prod playbooks/setup_longhorn.yml
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Диагностика и типичные ошибки
|
||
|
||
### Основные команды
|
||
|
||
```bash
|
||
# Состояние всех томов
|
||
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 на ноде
|
||
|
||
```bash
|
||
# Выполнить через SSH на worker-ноде или kubectl exec
|
||
|
||
# Статус iscsid
|
||
systemctl status iscsid
|
||
|
||
# Список активных iSCSI-сессий (примонтированные тома)
|
||
iscsiadm -m session
|
||
|
||
# Загружены ли нужные модули ядра
|
||
lsmod | grep iscsi_tcp
|
||
lsmod | grep dm_crypt
|
||
```
|
||
|
||
### Принудительный detach застрявшего тома
|
||
|
||
Если Pod удалён, но том остался в статусе attached:
|
||
|
||
```bash
|
||
# Через 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 и StatefulSet
|
||
- `ReadWriteMany` — несколько узлов, только через 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
|