Доступно в редакциях: Ultimate/EE
Входит в расширения: Продвинутое хранение данных
Стадия жизненного цикла модуля: Экспериментальная версия
Перед включением модуля необходимо разрешить использование экспериментальных модулей в ModuleConfig deckhouse. Для этого установите параметр allowExperimentalModules в true.
Модуль находится в стадии Experimental. API и поведение могут меняться без предупреждения; не используйте его для продуктивных нагрузок.
Модуль sds-nfs превращает один том ReadWriteOnce в хранилище ReadWriteMany. Для ресурса NFSServer он заказывает том выбранного StorageClass, форматирует его в XFS, раздаёт по NFS из одного пода и публикует NFSStorageClass модуля csi-nfs с именем ресурса. Каждый PVC этого класса получает на томе свой каталог, ограниченный размером PVC проектной квотой XFS; том модуль увеличивает сам, когда PVC просят больше.
Модуль доступен в редакциях EE и FE. Нужен включённый модуль csi-nfs версии 0.4.14 или новее: она выпускает клиентский сертификат mTLS для узлов и публикует его CA в NFSStorageClass (см. «Шифрование» в разделе «Как это работает»). Ему принадлежит ресурс NFSStorageClass, и он монтирует тома этих классов. NFSStorageClass каждого NFSServer создаёт и поддерживает sds-nfs: вручную его не меняйте — изменения перезапишутся, а удаляется он вместе с NFSServer. NFSServer нельзя создать с именем существующего StorageClass или NFSStorageClass: запрос будет отклонён, потому что класс сервера получил бы это имя.
Ресурсы NFSServer читают пользователи с ролью viewer подсистемы storage или уровнем доступа User в user-authz; создают, изменяют и удаляют — с ролью manager подсистемы storage или уровнем ClusterEditor и выше.
Пример
apiVersion: storage.deckhouse.io/v1alpha1
kind: NFSServer
metadata:
name: shared
spec:
backend:
size: 50GiТом бэкенда получает класс кластера по умолчанию, если spec.backend.storageClassName не задаёт другой, а сервер работает на системных узлах, если spec.nodeSelector не задаёт другие (см. «Размещение» в разделе «Как это работает»). spec.backend.storageClassName и spec.storageClass.volumeBindingMode после создания сервера не меняются: класс тома не меняется, а режим привязки опубликованного NFSStorageClass csi-nfs изменить не может. spec.backend.size можно только увеличить.
PVC затем используют класс shared:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: [ReadWriteMany]
storageClassName: shared
resources:
requests:
storage: 5Gikubectl get nfsserver shared показывает размер тома бэкенда, сколько просят PVC вместе, узел сервера и условие Ready; остальные условия говорят, что не готово и почему.
Как это работает
- Отказоустойчивость задаёт StorageClass бэкенда. Подходит любой класс, который выдаёт тома ReadWriteOnce с
volumeMode: Block. С реплицируемым классом сервер переезжает на другой узел, когда его узел потерян; с локальным диском тома класса ждут возвращения узла. - Размещение. Без
spec.nodeSelectorсервер работает на системных узлах (node-role.deckhouse.io/system) и допускает их taintdedicated.deckhouse.io=system, как системные компоненты Deckhouse; в кластере без системных узлов — на любом узле, кроме control plane.spec.nodeSelectorзаменяет этот выбор. Когда системные узлы появляются или уходит последний из них, сервер переезжает — с паузой переезда. - Для переезда нужен фенсинг. Под сервера уходит с потерянного узла только после фенсинга узла, поэтому фенсинг должен быть включён на NodeGroup, где может работать сервер (
spec.fencing.mode: Watchdog); условиеFencingReadyперечисляет группы без него и узлы, на которых агент фенсинга не взвёл watchdog. - Потеря узла сервера. Когда узел сервера NotReady, модуль ждёт, пока смерть узла будет доказана:
- Node удалён фенсингом платформы или узел пробыл NotReady без продления lease дольше таймаута watchdog своей NodeGroup плюс 30 секунд при взведённом watchdog: на узле работает агент фенсинга платформы в режиме
Watchdog, и узел не на обслуживании (update.node.deckhouse.io/approved,update.node.deckhouse.io/disruption-approvedилиnode-manager.deckhouse.io/fencing-disable— с ними агент снимает watchdog). - После этого модуль удаляет под сервера, освобождает том бэкенда от узла, и сервер запускается на другом узле; клиенты мёртвого узла, найденные по адресу, с которого они монтировали том, забываются, чтобы остальные не ждали, пока те восстановят блокировки. Узел, который просто недоступен, сервер не теряет.
status.interruptionпоказывает, чего ждёт сервер (WaitingForNodeFencing,ReleasingVolume,Scheduling,Starting,Recovering— сервер работает, а клиенты восстанавливают состояние в grace period NFSv4, это тоже часть паузы), аstatus.lastInterruption— сколько длилось последнее прерывание. Прерывание начинается, когда платформа помечает узел NotReady, примерно через 40 секунд после потери узла, поэтому пауза у клиентов на столько же длиннее.- Переезд может ещё до шести минут ждать на шаге
Starting: контроллер подключения томов Kubernetes (attach-detach controller) держит том бэкенда подключённым к потерянному узлу, пока не отключит его принудительно (maxWaitForUnmountDuration), если на узле нет taintnode.kubernetes.io/out-of-service, а фенсинг платформы удаляет Node, не ставя его. - В тестах модуля
status.lastInterruptionпереезда с потерянного узла составлял 110–160 секунд, когда том отпускался сразу, и около 430 секунд, когда нет.
- Node удалён фенсингом платформы или узел пробыл NotReady без продления lease дольше таймаута watchdog своей NodeGroup плюс 30 секунд при взведённом watchdog: на узле работает агент фенсинга платформы в режиме
- Один сервер на все тома класса. Перезапуск или переезд сервера приостанавливает ввод-вывод всех томов класса, пока сервер не вернётся: около 15–30 секунд при перезапуске на месте, дольше при переезде. Приложения не получают ошибок ввода-вывода (монтирование
hard): их запись ждёт. - Обновление модуля. Обновление, которое меняет образ сервера, перезапускает серверы всех классов одновременно, а не по очереди: каждый класс приостанавливает свои тома на время перезапуска, около 15–30 секунд. Планируйте обновление модуля как такую паузу всех NFS-томов кластера.
- Обслуживание узла. Перед обслуживанием узла сервера освободите его от нагрузки (
kubectl drainили прерывающее обновление Deckhouse, которое по умолчанию делает drain само). Под сервера вытесняется, StatefulSet запускает его на другом узле размещения, а тома класса ждут, как при любом переезде;status.interruptionэто показывает. Освобождённый узел остаётся закрытым для планирования (cordon), пока его не вернут командой uncordon, поэтому сервер на него не возвращается и после обслуживания остаётся там, куда переехал. - Квоты.
dfв поде показывает размер PVC. Запись сверх него завершается ошибкойENOSPC(XFS сообщает о заполненном проекте как о заполненной ФС). Сервер отклоняет запись сверх лимита, поэтому приложение должно проверять результатwrite,fsyncиclose. - Синхронная запись медленная. Запись, которую клиент отправляет стабильной —
O_DIRECT,O_SYNCилиfsyncпосле каждой мелкой записи, как делают базы данных, — заставляет сервер синхронизировать файловую систему и том бэкенда ради одной этой записи. В нагрузочном тесте модуля случайная запись блоками 4 КиБ сdirect=1давала около 250–1600 IOPS на сервер при p99 200–800 мс, а та же запись с буферизацией на клиенте — около 2800 IOPS. Модуль подходит для общих файлов и умеренной записи, а не для баз данных. Сервер в разы быстрее, когда на его узле лежит полная копия тома бэкенда. - Рост. Модуль заказывает для тома бэкенда сумму размеров PVC с запасом и расширяет ФС на ходу. Когда PVC просят больше, чем есть у бэкенда, условие
CapacityReadyравноFalseи объясняет причину; PVC к этому моменту уже показывает новый размер. - Доступ. У каждого PersistentVolume класса свой экспорт, и узел может смонтировать его, только пока на нём работает под, который использует этот том; узлам контроллера
csi-nfsдоступны все тома и корень экспорта, потому что они создают и удаляют каталоги томов. Остальным узлам корень доступен только для обхода, на чтение и без данных: тома, которые им смонтировать нельзя, от них скрыты. Под из сети подов ничего смонтировать не может, а inline-тома серверов кластера csi-nfs отклоняет.- Узел получает том через несколько секунд после того, как на него запланирован под этого тома, и теряет его, когда последний такой под удалён; kubelet всё это время повторяет монтирование. Каталог получает экспорт в момент, когда csi-nfs его создаёт, ещё до появления тома, и теряет его, когда удалён PVC тома, — чтобы csi-nfs мог удалить каталог.
- Тома монтируются на узлах с
nosuidиnodev, поэтому бинарь с set-user-ID или файл устройства на томе не действует на узле. В под среда выполнения контейнеров подключает том bind-монтированием без этих флагов; внутри подов повысить привилегии бинарю с set-user-ID не даётsecurityContext.allowPrivilegeEscalation: false, которого требует стандарт Pod SecurityRestricted. - Root на узле или привилегированный под на нём может прочитать тома подов, которые на этом узле работают. Это предел любого хранилища, которое монтирует узел.
- Шифрование и клиентский сертификат. Сервер принимает только соединения RPC-with-TLS (RFC 9289) и только от клиента, который предъявил клиентский сертификат узлов (mTLS): трафик между узлами и сервером всегда зашифрован, узел проверяет, что говорит именно с сервером, а сервер — что говорит именно с узлом. Под в сети узла делит с ним адрес, но без ключа узла, который может прочитать только root или под с hostPath, сервер его не пускает. Настраивать ничего не нужно.
- Модуль хранит свой CA в Secret
d8-sds-nfs/sds-nfs-caи выпускает каждому серверу сертификат на адрес его Service. CA передаётся узлам вspec.tlsParameters.caNFSStorageClass сервера;tlsParametersмодуляcsi-nfsдля этого не нужны. - Клиентский сертификат выпускает
csi-nfsдля узлов; его CAcsi-nfsпубликует вstatus.clientCANFSStorageClass сервера, и сервер доверяет этому CA. Узел предъявляет один и тот же клиентский сертификат всем NFS-серверам, которые монтирует с mTLS, поэтому клиентский сертификат, заданный вtlsParameters.mtlsмодуляcsi-nfs, заменяет выпущенный и для серверов этого модуля, и ему нуженtlsParameters.mtls.clientCA; без него условиеTLSReadyравноFalseи говорит, что сервер ждёт клиентский CA. - Модуль доступен в редакциях EE и FE и требует
csi-nfsсspec.tlsParameters.ca,status.clientCAиspec.mountOptions.nosuidу NFSStorageClass; со старымcsi-nfsусловиеTLSReadyравноFalseи говорит, что его надо обновить. - Каждому узлу, который монтирует тома, нужно ядро с RPC-with-TLS: 6.5 и новее, с
CONFIG_TLSиCONFIG_NET_HANDSHAKE. УсловиеClientNodesReadyперечисляет узлы со старым ядром: поды на них тома не смонтируют. На таких узлах (например, Astra и Debian с ядром 6.1) не запускаются и подыcsi-nfs, как только в кластере появляется CA. - Сертификаты действуют десять лет и не обновляются по расписанию: ganesha читает новый сертификат только при перезапуске, а перезапуск ставит на паузу все тома класса. Новый сертификат выпускается, когда меняется адрес Service, и сервер перезапускается с ним.
- Сервер, созданный версией модуля без mTLS, после обновления не обслуживает свои тома: NFSStorageClass такого сервера опубликован без mTLS, его подключение изменить нельзя, и условие
NFSStorageClassReadyговорит, что сервер надо создать заново.
- Модуль хранит свой CA в Secret
- Удаление.
NFSServerне удаляется, пока существуют PersistentVolume его класса: до удаления последнего из них сервер продолжает их обслуживать и переезжает с потерянного узла, аReady=Falseс причинойDeletionBlocked. Удаление ресурса удаляет том бэкенда и данные на нём. - Мониторинг. Дашборд SDS-NFS / NFS servers в Grafana показывает состояние серверов, их прерывания и тома бэкенда, операции, задержки и пропускную способность NFS по метрикам ganesha и заполнение каждого тома (
sds_nfs_volume_used_bytesпротивsds_nfs_volume_limit_bytes, по неймспейсу и PVC). АлертыSdsNfsServerInterrupted,SdsNfsServerNotReady,SdsNfsServerCapacityNotReadyиSdsNfsServerFencingNotReadyсрабатывают по условиям серверов.