Kubernetes для самых маленьких. Часть 24: Подключаем SMB через csi-driver-smb
Все части серии «Kubernetes для самых маленьких»В прошлой части кластер получил тома с NFS-сервера через csi-driver-nfs, а в части 22 мы закрывали этот сервер от всех, кроме узлов. Второй распространённый источник общих томов — файловый сервер Windows, Samba или NAS, который уже отдаёт шары пользователям по SMB. Для него есть csi-driver-smb: те же PV и StorageClass, что в части 21, но под ними протокол с аутентификацией, подписью и шифрованием, а значит — учётные данные, которые надо где-то хранить, и клиент cifs в ядре со своими опциями и особенностями. Сервер в этой части — files.example.com (192.168.10.31) на Linux с Samba; как то же самое делается на Windows Server, показано рядом по документации Microsoft. В конце — разбор реального случая, когда поды периодически получали Host is down из-за того, как клиент читал ответы NAS.
SMB коротко
SMB (Server Message Block) — протокол файлового доступа Windows. Первая версия (SMB1, она же CIFS) устарела и небезопасна: в Windows Server её не устанавливают по умолчанию с версии 1709, Samba с версии 4.11 не включает. Дальше речь о SMB2 и SMB3 — это один протокол с номерами диалектов: 2.0.2, 2.1, 3.0, 3.0.2, 3.1.1.
Транспорт. SMB2/3 ходит поверх TCP на порт 445 (TCP — в части 10 сетевой серии). Каждое сообщение в потоке предваряется четырёхбайтовым заголовком: нулевой байт типа и три байта длины. Это наследство NetBIOS-сессии из RFC 1002, поэтому в журналах клиента Linux он так и называется. Само сообщение SMB2 начинается с 64-байтового заголовка, первые четыре байта которого — сигнатура FE 53 4D 42 («\xFESMB»); зашифрованное сообщение SMB3 вместо него начинается с заголовка преобразования (transform header) с сигнатурой FD 53 4D 42. Эти факты пригодятся в разборе случая.
Три уровня состояния.
- Сессия — результат аутентификации (
SESSION_SETUP): клиент доказывает, кто он, по NTLM (протокол «вызов — ответ», где сервер проверяет знание пароля, не получая его) или по Kerberos (билет от контроллера домена, подробно — в части 6 серии о Windows Server), и обе стороны получают сеансовый ключ, из которого выводятся ключи подписи и шифрования. - Дерево (
TREE_CONNECT) — подключение к конкретной шаре внутри сессии:\\files.example.com\k8s. - Открытый файл (
CREATE) — дескриптор FileId, по которому идутREAD,WRITE,CLOSE.
Диалекты. Клиент присылает список, сервер выбирает наибольший общий. 2.1 добавил аренды (lease), 3.0 — шифрование, multichannel (несколько TCP-соединений на сессию) и устойчивые дескрипторы для отказоустойчивых кластеров файловых серверов, 3.0.2 — мелкие улучшения, 3.1.1 — проверку целостности переговоров (хеш SHA-512 всех сообщений до аутентификации, чтобы посредник не мог понизить диалект) и шифры AES-GCM.
Кредиты. Управление потоком: каждый запрос тратит кредиты, сервер выдаёт новые в ответах. Сколько у клиента кредитов, столько запросов он может держать в полёте одновременно.
Составные запросы (compound): несколько команд в одном сообщении, связанных полем NextCommand, — например, CREATE и CLOSE подряд, чтобы за один круг узнать атрибуты файла. Сервер может ответить так же, одним составным сообщением.
Асинхронные ответы. Если операция не может завершиться сразу, сервер отвечает промежуточным ответом со статусом STATUS_PENDING, а окончательный присылает позже.
Oplock и lease. Сервер может разрешить клиенту кэшировать файл: читать из своего кэша (R), откладывать запись (W), держать дескриптор открытым после close (H). Старый механизм называется oplock, с 2.1 — lease, привязанный к ключу, а не к дескриптору. Когда другой клиент открывает тот же файл несовместимым образом, сервер посылает держателю разрыв (break), ждёт подтверждения и только потом отвечает второму клиенту — а пока ждёт, отвечает ему STATUS_PENDING.
Подпись и шифрование SMB3. Подпись — код аутентичности (AES-CMAC, с 3.1.1 в новых Windows — AES-GMAC) на каждом сообщении: сервер и клиент обнаруживают подмену. Шифрование — AES-CCM или AES-GCM всего сообщения после аутентификации; с 3.1.1 по умолчанию договариваются об AES-128-GCM. Зашифрованное сообщение заодно защищено и от подмены, так что отдельная подпись поверх шифрования не нужна. Включаются на сервере для всего сервера или для отдельной шары, клиент может требовать их сам.
Клиент в Linux
SMB-клиент в Linux — модуль ядра cifs (исходники в fs/smb/client); имя осталось с первой версии протокола, но говорит он на SMB2/3. Монтирует его утилита mount.cifs из пакета cifs-utils: она разбирает опции, достаёт пароль, а для Kerberos через вызов cifs.upcall получает билет. Драйверу кластера пакет на узлах не нужен — mount.cifs лежит в его образе, — а для ручной проверки поставим его на k8s-node-1 на время:
user@k8s-node-1:~$ sudo apt install cifs-utils
user@k8s-node-1:~$ sudo mount -t cifs //files.example.com/k8s /mnt \
-o vers=3,username=svc-k8s-smb,domain=EXAMPLE,seal,uid=10001,gid=10001,dir_mode=0770,file_mode=0660
Password for svc-k8s-smb@//files.example.com/k8s:
user@k8s-node-1:~$ findmnt -t cifs -o TARGET,SOURCE,FSTYPE,OPTIONS
TARGET SOURCE FSTYPE OPTIONS
/mnt //files.example.com/k8s cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=svc-k8s-smb,domain=EXAMPLE,uid=10001,forceuid,gid=10001,forcegid,addr=192.168.10.31,file_mode=0660,dir_mode=0770,seal,soft,nounix,serverino,mapposix,reparse=nfs,nativesocket,symlink=native,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=1,closetimeo=1
user@k8s-node-1:~$ ls /mnt
ls: cannot open directory '/mnt': Permission denied
vers=3 в опциях, vers=3.1.1 в итоге — договорились о наибольшем общем диалекте. soft — значение по умолчанию, о нём ниже. domain=EXAMPLE здесь для примера: Samba в роли отдельного сервера (standalone server) имя домена не проверяет, Windows-серверу в домене оно нужно.
У NFS права решает UID процесса, который клиент передаёт серверу. У SMB иначе: все операции через одно монтирование идут от одной учётной записи, той, что указана при монтировании. Права на сервере проверяются для неё, а владельца и режим файлов, которые видят процессы на узле, клиент эмулирует из опций uid, gid, file_mode, dir_mode. Отсюда Permission denied выше: ls запущен от пользователя с UID 1000, а каталог «принадлежит» 10001 с режимом 0770. Процесс с UID 10001 пишет, процесс с чужим UID, но группой 10001 — тоже, а сервер в обоих случаях видит одну служебную учётную запись:
user@k8s-node-1:~$ sudo setpriv --reuid=10001 --regid=10001 --clear-groups sh -c 'echo hello > /mnt/test.txt; mkdir /mnt/dir; ls -lan /mnt'
total 4
drwxrwx--- 2 10001 10001 0 Oct 30 12:23 .
drwxr-xr-x 20 0 0 4096 Oct 26 22:11 ..
drwxrwx--- 2 10001 10001 0 Oct 30 12:23 dir
-rw-rw---- 1 10001 10001 6 Oct 30 12:23 test.txt
user@k8s-node-1:~$ cat /mnt/test.txt
cat: /mnt/test.txt: Permission denied
user@k8s-node-1:~$ sudo setpriv --reuid=2000 --regid=10001 --clear-groups sh -c 'cat /mnt/test.txt; echo more >> /mnt/test.txt && echo appended'
hello
appended
user@files:~$ sudo ls -lan /srv/samba/k8s
total 16
drwxrwx--- 3 999 982 4096 Oct 30 12:23 .
drwxr-xr-x 3 0 0 4096 Oct 30 12:22 ..
drwxrwx---+2 999 982 4096 Oct 30 12:23 dir
-rw-rw----+1 999 982 11 Oct 30 12:23 test.txt
На сервере оба файла принадлежат svc-k8s-smb (UID 999, группа 982), а режим у них из настроек шары, о которых ниже. Это главное, что надо помнить о правах на SMB-томах: UID процесса до сервера не доходит, всё разграничение между процессами узла — на клиенте.
Отказы выглядят так. Диалект ниже, чем разрешает сервер, и неверный пароль (пароль mount.cifs берёт и из переменной PASSWD):
user@k8s-node-1:~$ sudo PASSWD="$SMB_PASS" mount -t cifs //files.example.com/k8s /mnt -o vers=2.1,username=svc-k8s-smb
mount error(95): Operation not supported
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg)
user@k8s-node-1:~$ sudo PASSWD=wrong-password mount -t cifs //files.example.com/k8s /mnt -o vers=3,username=svc-k8s-smb
mount error(13): Permission denied
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg)
user@k8s-node-1:~$ sudo dmesg -T | grep -i cifs | tail -5
[Fri Oct 30 12:23:35 2026] CIFS: VFS: \\files.example.com Dialect not supported by server. Consider specifying vers=1.0 or vers=2.0 on mount for accessing older servers
[Fri Oct 30 12:23:35 2026] CIFS: VFS: cifs_mount failed w/return code = -95
[Fri Oct 30 12:23:35 2026] CIFS: Status code returned 0xc000006d STATUS_LOGON_FAILURE
[Fri Oct 30 12:23:35 2026] CIFS: VFS: \\files.example.com failed to create a new SMB session with Unknown: -13
[Fri Oct 30 12:23:35 2026] CIFS: VFS: cifs_mount failed w/return code = -13
После проверки — sudo umount /mnt и sudo apt purge cifs-utils: дальше монтирует только драйвер.
Сервер: шара для кластера
Samba
Samba — свободная реализация сервера SMB для Linux. На files.example.com (Ubuntu 26.04) ставим пакет samba (Samba 4.23) и заводим отдельную служебную учётную запись — только для кластера, без входа в систему, с длинным случайным паролем. Samba в роли отдельного сервера хранит свои пароли отдельно от системных (smbpasswd), но каждой учётной записи Samba должен соответствовать пользователь Linux — от его имени smbd работает с файлами:
user@files:~$ sudo apt install samba
user@files:~$ sudo useradd --system --no-create-home --shell /usr/sbin/nologin svc-k8s-smb
user@files:~$ id svc-k8s-smb
uid=999(svc-k8s-smb) gid=982(svc-k8s-smb) groups=982(svc-k8s-smb)
user@files:~$ sudo install -d -o svc-k8s-smb -g svc-k8s-smb -m 0770 /srv/samba/k8s
user@files:~$ sudo smbpasswd -a svc-k8s-smb
New SMB password:
Retype new SMB password:
Added user svc-k8s-smb.
user@files:~$ sudo pdbedit -L -v -u svc-k8s-smb | grep -E 'Unix username|Account Flags|Password must change'
Unix username: svc-k8s-smb
Account Flags: [U ]
Password must change: never
Пароль Linux у svc-k8s-smb не задан, shell — nologin: войти на сервер по SSH или с консоли этой учётной записью нельзя, она существует только для Samba. Каталог шары принадлежит ей с режимом 0770: остальные пользователи сервера данных кластера не видят.
/etc/samba/smb.conf целиком (стандартный файл Ubuntu с примерами шар заменяем, а не правим):
# /etc/samba/smb.conf
# Kubernetes volumes for csi-driver-smb: cluster nodes only, SMB3 with signing and encryption
[global]
server role = standalone server
server min protocol = SMB3
server signing = mandatory
server smb encrypt = required
map to guest = never
restrict anonymous = 2
disable netbios = yes
smb ports = 445
load printers = no
hosts allow = 192.168.10.17 192.168.10.20 192.168.10.21 127.0.0.1
[k8s]
path = /srv/samba/k8s
valid users = svc-k8s-smb
read only = no
create mask = 0660
directory mask = 0770
server min protocol = SMB3— без SMB1 и SMB2. Тонкость: в Samba значениеSMB3означает его последний вариант,SMB3_11, то есть сервер пускает только клиентов с диалектом 3.1.1. На стенде монтирование сvers=3.0получило тот же отказDialect not supported by server, что иvers=2.1выше. Если среди клиентов есть умеющие максимум 3.0 или 3.0.2, пишутSMB3_00.server signing = mandatoryиserver smb encrypt = required— подпись и шифрование обязательны для всех сессий; клиент, не умеющий шифровать, получит отказ.map to guest = neverиrestrict anonymous = 2— никакого гостевого и анонимного доступа.neverи так значение по умолчанию, но Ubuntu в своёмsmb.confставитbad user, поэтому значение лучше указать явно.disable netbios = yesиsmb ports = 445— только прямой TCP на 445, без NetBIOS: служба имёнnmbdс этой настройкой не стартует (systemd пишетstart condition unmet), аsmbdперестаёт слушать 139 — со стандартной конфигурацией Ubuntu он слушал и 139, и 445.hosts allow— адреса узлов кластера и локальный адрес для проверок на самом сервере; всем остальным Samba отказывает сразу после TCP-соединения.valid users— в шару пускается только служебная учётная запись.create maskиdirectory mask— режим, с которымsmbdсоздаёт файлы и каталоги на диске сервера:0660и0770, без доступа для остальных.
Проверка и перезапуск:
user@files:~$ testparm -s
Load smb config files from /etc/samba/smb.conf
Loaded services file OK.
Weak crypto is allowed by GnuTLS (e.g. NTLM as a compatibility fallback)
Server role: ROLE_STANDALONE
# Global parameters
[global]
disable netbios = Yes
load printers = No
restrict anonymous = 2
server min protocol = SMB3
server role = standalone server
server signing = required
server smb transports = 445
idmap config * : backend = tdb
hosts allow = 192.168.10.17 192.168.10.20 192.168.10.21 127.0.0.1
server smb encrypt = required
[k8s]
create mask = 0660
directory mask = 0770
path = /srv/samba/k8s
read only = No
valid users = svc-k8s-smb
user@files:~$ sudo systemctl restart smbd
testparm показывает только отличия от умолчаний и канонические имена параметров: mandatory превратилось в required (синонимы), smb ports — в server smb transports (так этот параметр называется в Samba 4.23), а map to guest = never исчезло, потому что совпадает с умолчанием. Строка Weak crypto is allowed by GnuTLS — предупреждение о том, что библиотека шифрования разрешает алгоритмы, нужные NTLM; к нашей конфигурации оно отношения не имеет.
Windows Server
На Windows Server то же самое делается так (команды — по документации Microsoft, на стенде их не выполняли). Служебная учётная запись в домене — без членства в группах кроме Domain Users, с длинным случайным паролем; вход на компьютеры ей запрещают групповой политикой (Deny log on locally, Deny log on through Remote Desktop Services):
PS C:\> New-ADUser -Name 'svc-k8s-smb' -SamAccountName svc-k8s-smb -AccountPassword (Read-Host -AsSecureString) `
-Enabled $true -CannotChangePassword $true -PasswordNeverExpires $true -Description 'Kubernetes SMB volumes'
-PasswordNeverExpires — сознательно: истечение пароля по политике домена для тома выглядит как смена пароля на сервере, и, как показано ниже, при первом же переподключении тома перестанут работать. Пароль служебной учётной записи меняют по процедуре, а не по таймеру.
Каталог, права NTFS и шара — права только у этой учётной записи и администраторов, без Everyone:
PS C:\> New-Item -ItemType Directory D:\Shares\k8s
PS C:\> icacls D:\Shares\k8s /inheritance:r /grant:r 'BUILTIN\Administrators:(OI)(CI)F' 'NT AUTHORITY\SYSTEM:(OI)(CI)F' 'EXAMPLE\svc-k8s-smb:(OI)(CI)M'
PS C:\> New-SmbShare -Name k8s -Path D:\Shares\k8s -ChangeAccess 'EXAMPLE\svc-k8s-smb' -FullAccess 'BUILTIN\Administrators' `
-EncryptData $true -FolderEnumerationMode AccessBased
(OI)(CI)M — право Modify (чтение, запись, удаление, но не смена прав) с наследованием на файлы и подкаталоги. /inheritance:r убирает права, унаследованные от диска: иначе Users с диска D: получат доступ к данным кластера. Право на шару — Change, не Full: служебной учётной записи не нужно менять права NTFS.
Настройки сервера — SMB1 выключен, шифрование и подпись обязательны:
PS C:\> Set-SmbServerConfiguration -EnableSMB1Protocol $false -EncryptData $true -RejectUnencryptedAccess $true `
-RequireSecuritySignature $true -Force
PS C:\> Get-SmbServerConfiguration | Select-Object EnableSMB1Protocol, EncryptData, RejectUnencryptedAccess, RequireSecuritySignature
EncryptData на уровне сервера шифрует все шары; если сервер обслуживает и старые клиенты без SMB3, шифрование включают только на шаре кластера (-EncryptData $true в New-SmbShare, как выше). RejectUnencryptedAccess (по умолчанию и так $true) запрещает доступ к зашифрованной шаре клиентам, не умеющим шифровать, — без него они получили бы доступ открытым текстом. Шифрование SMB3 подписывает сообщения само, RequireSecuritySignature нужен для сессий без шифрования.
Файрвол Windows: если сервер обслуживает только кластер, правило на 445 ограничивают адресами узлов; если и пользователей — их сетями. Порты 137–139 (NetBIOS) кластеру не нужны:
PS C:\> New-NetFirewallRule -DisplayName 'SMB from k8s nodes' -Direction Inbound -Protocol TCP -LocalPort 445 `
-RemoteAddress 192.168.10.17,192.168.10.20,192.168.10.21 -Action Allow
Встроенные правила группы «File and Printer Sharing» при этом надо сузить или выключить, иначе они разрешат 445 всем.
Файрвол сервера и политика для подов
На files.example.com работает ufw с теми же политиками по умолчанию и правилом SSH для администраторов, что у NFS-сервера в части 22. Для SMB достаточно открыть 445/tcp от трёх узлов — UDP и порты NetBIOS не нужны:
user@files:~$ for ip in 192.168.10.17 192.168.10.20 192.168.10.21; do
> sudo ufw allow from $ip to any port 445 proto tcp comment 'SMB: k8s nodes'
> done
Rule added
Rule added
Rule added
user@files:~$ sudo ufw status | grep 445
445/tcp ALLOW 192.168.10.17 # SMB: k8s nodes
445/tcp ALLOW 192.168.10.20 # SMB: k8s nodes
445/tcp ALLOW 192.168.10.21 # SMB: k8s nodes
Как и с NFS, тома монтирует kubelet на узле, а подам доступ к серверу напрямую не нужен. GlobalNetworkPolicy из части 22 достаточно расширить вторым адресом:
egress:
- action: Deny
destination:
nets:
- 192.168.10.30/32
- 192.168.10.31/32
Для SMB это не менее важно, чем для NFS: и hosts allow, и файрвол пропустят под так же, как узел, — пакеты пода уходят наружу с адресом узла. Без пароля под ничего не прочитает, но тогда пароль остаётся единственным рубежом, а открытая из любого пода точка входа в службу аутентификации — лишняя поверхность атаки. С политикой под из demo до сервера не достаёт, а том у него, как увидим ниже, работает:
$ kubectl -n demo exec deploy/writer -- nc -zv -w 3 192.168.10.31 445
nc: 192.168.10.31 (192.168.10.31:445): Connection timed out
command terminated with exit code 1
Установка драйвера
Драйвер разрабатывается в организации kubernetes-csi, актуальный выпуск — v1.20.4. Как и csi-driver-nfs в прошлой части, проект публикует для каждого выпуска манифесты в каталоге deploy/v1.20.4/; скрипт install-driver.sh применяет их и вдобавок DaemonSet для Windows-узлов, которых в нашем кластере нет, поэтому берём четыре файла для Linux:
$ V=v1.20.4
$ for f in rbac-csi-smb.yaml csi-smb-driver.yaml csi-smb-controller.yaml csi-smb-node.yaml; do curl -sLO https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/$V/deploy/$V/$f; done
$ kubectl apply -f rbac-csi-smb.yaml -f csi-smb-driver.yaml -f csi-smb-controller.yaml -f csi-smb-node.yaml
serviceaccount/csi-smb-controller-sa created
serviceaccount/csi-smb-node-sa created
clusterrole.rbac.authorization.k8s.io/smb-external-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/smb-csi-provisioner-binding created
clusterrole.rbac.authorization.k8s.io/smb-external-resizer-role created
clusterrolebinding.rbac.authorization.k8s.io/smb-csi-resizer-role created
clusterrole.rbac.authorization.k8s.io/csi-smb-node-secret-role created
clusterrolebinding.rbac.authorization.k8s.io/csi-smb-node-secret-binding created
csidriver.storage.k8s.io/smb.csi.k8s.io created
deployment.apps/csi-smb-controller created
daemonset.apps/csi-smb-node created
$ kubectl -n kube-system get pods -l 'app in (csi-smb-controller,csi-smb-node)' -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
csi-smb-controller-6f4f896698-x7r46 4/4 Running 1 (32s ago) 2m7s 192.168.10.20 k8s-node-1 <none> <none>
csi-smb-node-929cp 3/3 Running 1 (17s ago) 2m6s 192.168.10.20 k8s-node-1 <none> <none>
csi-smb-node-pf88k 3/3 Running 0 2m6s 192.168.10.17 k8s-master-1 <none> <none>
csi-smb-node-t7nb8 3/3 Running 0 2m6s 192.168.10.21 k8s-node-2 <none> <none>
$ kubectl get csidriver smb.csi.k8s.io -o yaml | sed -n '/^spec:/,$p'
spec:
attachRequired: false
fsGroupPolicy: ReadWriteOnceWithFSType
podInfoOnMount: true
requiresRepublish: false
seLinuxMount: false
storageCapacity: false
volumeLifecycleModes:
- Persistent
- Ephemeral
Тот же драйвер ставится Helm-чартом из репозитория (у чарта версия без буквы v, 1.20.4); Helm — в части 27. Устройство то же, что у NFS-драйвера:
csi-smb-controller— Deployment вhostNetworkс четырьмя контейнерами:csi-provisioner(v6.3.0),csi-resizer(v2.2.1),liveness-probeи сам драйверsmb(образsmbplugin:v1.20.4, Debian 12 сcifs-utils7.0). Контроллеру нужно монтировать шару, чтобы создавать в ней каталоги томов, поэтому и сервер, и файрвол должны пускать узел, на котором он окажется. Снапшотов у драйвера нет; из дополнительных возможностей — расширение и клонирование тома.csi-smb-node— DaemonSet на всех узлах, включая мастер, сnode-driver-registrar,liveness-probeиsmb. Перезапуск при первом старте — тот же, что у NFS-драйвера: sidecar-контейнер не дождался сокета драйвера, пока скачивался его образ.- CSIDriver:
attachRequired: false— шага подключения к узлу нет.podInfoOnMount: true— kubelet передаёт драйверу имя, неймспейс и UID пода.Ephemeral— кроме PV, драйвер умеет встроенные эфемерные тома прямо в спецификации пода (Secret для них драйвер ищет только в неймспейсе пода).fsGroupPolicyв манифесте не задан, и API-сервер подставил значение по умолчаниюReadWriteOnceWithFSType; на деле для этого драйвера оно ничего не решает, потому чтоfsGroupон берёт на себя сам — об этом ниже.
Есть шаг NodeStage. В отличие от NFS-драйвера, этот монтирует шару один раз на узел для каждого PV — в «глобальный» каталог /var/lib/kubelet/plugins/kubernetes.io/csi/smb.csi.k8s.io/<хеш>/globalmount (хеш — SHA-256 от volumeHandle), — а в каталоги подов (NodePublish) делает bind-монтирования из него. Все поды узла с одним томом делят одно монтирование cifs и одни опции. Это важно при смене опций, о чём ниже.
fsGroup через VOLUME_MOUNT_GROUP. Драйвер объявляет возможность узла VOLUME_MOUNT_GROUP, и kubelet, видя её, не меняет группу файлов сам (на SMB это и невозможно — владельцы эмулируются), а передаёт fsGroup пода драйверу в поле volume_mount_group. Драйвер дописывает к опциям монтирования gid=<fsGroup> и даёт группе полные права в режимах: в file_mode и dir_mode добавляются биты rwx для группы, а если режимы не заданы, ставятся file_mode=0774,dir_mode=0775. Если gid уже есть в опциях, драйвер ничего не трогает. Рекурсивного обхода тома нет, поэтому fsGroupChangePolicy здесь ничего не ускоряет.
Учётные данные
Пароль служебной учётной записи хранится в Secret:
apiVersion: v1
kind: Namespace
metadata:
name: storage-credentials
---
apiVersion: v1
kind: Secret
metadata:
name: smb-files-k8s
namespace: storage-credentials
type: Opaque
stringData:
username: svc-k8s-smb
password: "<пароль из хранилища секретов>"
$ kubectl apply -f smb-secret.yaml
namespace/storage-credentials created
secret/smb-files-k8s created
Для Windows-сервера в домене в Secret добавляют domain: EXAMPLE, драйвер передаст его опцией domain=. Samba-серверу домен не нужен.
Не в default и не в неймспейсе приложения. В официальных примерах Secret лежит в default, и это антипример: доступ к нему получает каждый, кто может читать секреты в default. Отдельный неймспейс storage-credentials, где права на чтение секретов есть только у администраторов кластера (RBAC, ролевая модель доступа Kubernetes, — в части 29), отделяет пароль от команд приложений. Сам манифест с паролем в Git не кладут; как хранить секреты в репозитории — в части 41.
Кто ещё прочитает этот пароль. Для PV Secret достаёт kubelet и передаёт драйверу в вызове NodeStageVolume (в журнале драйвера поле так и выглядит: "secrets":"***stripped***"), контроллеру — external-provisioner. Но в манифестах выпуска обеим сервисным учётным записям драйвера, и контроллера, и узлов, выдана ClusterRole с правом get на все секреты кластера: DaemonSet узлов читает их сам для встроенных эфемерных томов. Значит, тот, кто может выполнить kubectl exec в под драйвера в kube-system, может прочитать любой Secret кластера, — доступ к kube-system и так равен правам администратора, но держать это в голове стоит.
Дальше драйвер передаёт пароль mount.cifs в командной строке mount -t cifs -o …,username=…,password=… (если в пароле есть запятая, кавычка или обратный апостроф — через временный файл учётных данных). В журнал драйвера он не попадает — там вместо него <masked>, — но на время монтирования виден в аргументах процесса mount на узле. Ещё одна причина держать на узлах только то, что нужно кластеру.
Ротация пароля. Смена пароля в Secret не меняет его в уже смонтированных томах: ядро помнит пароль, с которым том монтировался, и использует его при каждом переподключении сессии. Проверим, что будет, если сменить пароль на сервере: работающие тома продолжают писать, пока сессия жива, но после первого же разрыва (здесь — systemctl restart smbd на сервере) клиент пытается войти со старым паролем каждые 10 секунд, а приложение получает Permission denied — даже на томе с hard:
user@files:~$ sudo smbpasswd svc-k8s-smb
New SMB password:
Retype new SMB password:
user@files:~$ sudo systemctl restart smbd
user@k8s-node-1:~$ sudo dmesg -T | grep -i cifs | tail -3
[Fri Oct 30 12:34:12 2026] CIFS: Status code returned 0xc000006d STATUS_LOGON_FAILURE
[Fri Oct 30 12:34:22 2026] CIFS: Status code returned 0xc000006d STATUS_LOGON_FAILURE
[Fri Oct 30 12:34:32 2026] CIFS: Status code returned 0xc000006d STATUS_LOGON_FAILURE
user@k8s-node-1:~$ sudo grep 'Session Status' /proc/fs/cifs/DebugData
1) Address: 192.168.10.31 Uses: 1 Capability: 0x30006f Session Status: 3 password no longer valid
$ kubectl -n demo logs writer-bc8b4f458-k6z47 | tail -2
sh: can't create /reports/visits.log: Permission denied
sh: can't create /reports/visits.log: Permission denied
Это сбивает с толку: «Permission denied» выглядит как ошибка прав на файл, а на деле сессия не может пройти аутентификацию. Когда на сервере вернули прежний пароль, клиент вошёл при следующей попытке, и запись продолжилась сама, без перемонтирования.
Поэтому надёжная процедура смены пароля — две учётные записи с одинаковыми правами, которые сменяют друг друга: завести вторую, записать её в Secret, перемонтировать тома на всех узлах (порядок ниже), убедиться в /proc/fs/cifs/DebugData и smbstatus на сервере, что сессии идут от новой, и только потом менять пароль или отключать первую. Ядро и mount.cifs умеют и запасной пароль (password2, клиент пробует его, если основной не подошёл), но csi-driver-smb берёт из Secret только username, password и domain.
StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: smb
provisioner: smb.csi.k8s.io
parameters:
source: //files.example.com/k8s
subDir: ${pvc.metadata.namespace}-${pvc.metadata.name}-${pv.metadata.name}
onDelete: archive
csi.storage.k8s.io/provisioner-secret-name: smb-files-k8s
csi.storage.k8s.io/provisioner-secret-namespace: storage-credentials
csi.storage.k8s.io/node-stage-secret-name: smb-files-k8s
csi.storage.k8s.io/node-stage-secret-namespace: storage-credentials
reclaimPolicy: Retain
volumeBindingMode: Immediate
allowVolumeExpansion: true
mountOptions:
- vers=3
- seal
- dir_mode=0770
- file_mode=0660
- uid=10001
- cache=strict
- actimeo=1
- nobrl
- nolease
- mfsymlinks
- hard
source— шара в виде//сервер/шара. Имя разрешают плагины драйвера через CoreDNS кластера (dnsPolicy: ClusterFirstWithHostNet, как у NFS-драйвера).subDir,onDelete— так же, как у NFS-драйвера: шаблоны имён (с${pv.metadata.name}, чтобы новый PVC с тем же именем не получил старый каталог) иdelete/retain/archive. И так же приreclaimPolicy: RetainonDeleteсрабатывает только после того, как администратор переключит освобождённый PV наDelete.provisioner-secret-*— учётные данные для контроллера: с ними он монтирует шару и создаёт каталог тома, без них каталог не создаётся вовсе. Имя Secret записывается в аннотации PV, и по нему жеDeleteVolumeмонтирует шару при удалении.node-stage-secret-*— для kubelet и узлового плагина, при монтировании на узле. Здесь это один Secret; при желании контроллеру можно дать отдельную учётную запись.reclaimPolicy: Retain— для ценных данных, как и в прошлой части.
mountOptions построчно
vers=3 — «SMB 3.0 или новее»: клиент предложит 3.0, 3.0.2 и 3.1.1, а сервер выберет наибольший общий. SMB2 исключён, значит, шифрование и подпись SMB3 доступны всегда. Жёсткое vers=3.1.1 надёжнее по проверке целостности переговоров, но если хоть один из серверов, к которым монтируются тома с этими опциями, умеет максимум 3.0.2 (так бывает у NAS и у старых Windows), его тома просто не смонтируются. vers=3 — явная версия без этой ловушки; если все серверы точно 3.1.1, можно указать её. С нашей Samba договариваются, понятно, о 3.1.1.
seal — требовать шифрование SMB3 от клиента, даже если сервер его не требует. Сервер, который шифрование требует, клиент cifs уважает и без seal, так что опция — гарантия на случай сервера, настроенного слабее, чем ожидалось. sign — требовать подпись; опция есть в ядре (/proc/fs/cifs/mount_params), но отдельного описания в справке mount.cifs нет. При seal отдельная подпись не нужна: зашифрованные сообщения и так защищены от подмены.
dir_mode и file_mode — режим, который клиент показывает для каталогов и файлов (по умолчанию 0755). Сервер не передаёт клиенту права POSIX, поэтому эти режимы — единственное, что проверяет клиент, когда процесс на узле обращается к файлу. Официальный пример драйвера ставит dir_mode=0777,file_mode=0777, и это антипример: любой процесс любого пода, в который смонтирован том, с любым UID может читать, писать и запускать всё, а сервер этого не видит — для него всё делает одна служебная учётная запись. 0770 и 0660 ограничивают доступ владельцем и группой тома.
uid и gid — кому на узле «принадлежат» все файлы тома (ядро при этом само включает forceuid и forcegid). uid=10001 — UID приложения. gid в классе не задан: его подставит драйвер из fsGroup пода (VOLUME_MOUNT_GROUP). Монтирование одно на узел и PV, поэтому все поды, использующие том, должны иметь один и тот же fsGroup: группу задаёт под, первым смонтировавший том на узле.
cache — режим кэширования. strict (по умолчанию) — кэшировать данные, только пока сервер выдал lease или oplock, то есть пока никто другой файл не меняет; none — не кэшировать вовсе, каждое чтение идёт на сервер; loose — кэшировать без гарантий согласованности, для томов RWX на нескольких узлах неприемлемо.
actimeo — сколько секунд кэшировать атрибуты файлов и содержимое каталогов (по умолчанию 1). Больше — меньше запросов к серверу, но другой узел дольше не увидит новый файл. Для RWX-томов оставляют по умолчанию или ставят 0.
nobrl — не отправлять байтовые блокировки на сервер, держать их только на клиенте. Windows соблюдает блокировки принудительно: если один клиент заблокировал диапазон, чтение этого диапазона другим клиентом получит ошибку, а не будет ждать. Некоторые среды выполнения превращают режим совместного доступа к файлу в flock на весь файл, cifs отправляет его на сервер, и чтение из соседнего пода во время записи заканчивается EACCES («Permission denied», хотя права ни при чём). Цена nobrl: блокировки перестают действовать между узлами. Включать её можно, только если приложение не полагается на блокировки для согласованности данных между подами.
nolease — не запрашивать у сервера ни lease, ни oplock. Серверу не придётся отзывать их у других клиентов, а клиент не кэширует данные (при cache=strict без lease кэширование и так не включается). Для томов, которые одновременно читают и пишут поды на разных узлах, это почти ничего не стоит; зачем это нужно, видно в разборе ниже.
mfsymlinks — хранить символические ссылки как специальные текстовые файлы (формат Minshall–French). Без расширений POSIX сервер SMB не хранит символические ссылки в понятном Linux виде, а многие приложения и пакетные менеджеры на них рассчитывают; файлы mfsymlinks понимает любой сервер. В /proc/mounts новых ядер опция показывается как symlink=mfsymlinks.
noperm — в списке нет, и это сознательно: опция отключает проверку прав на клиенте, и тогда dir_mode, file_mode, uid и gid перестают что-либо ограничивать. Её советуют, когда «не хватает прав»; правильное лечение — верные uid, gid и fsGroup.
hard — как у NFS: при разрыве запросы ждут восстановления, а не завершаются ошибкой. По умолчанию у cifs soft (его и показал findmnt при ручном монтировании), и операции, попавшие в окно переподключения, сразу получают ошибку. hard прячет короткие разрывы от приложения, но не устраняет их причину; цена та же, что в прошлой части, — процессы в состоянии D, если сервер пропал надолго.
Как и у NFS-драйвера, DeleteVolume опций класса не получает: на стенде контроллер монтировал шару для удаления с -o ,file_mode=0777,dir_mode=0777,<masked> — без vers и seal. С нашим сервером это работает (клиент сам договаривается о 3.1.1 и шифрует, раз сервер требует), а для сервера, которому нужны особые опции, их кладут в тот же Secret ключом mountOptions — драйвер берёт их оттуда.
Kerberos вместо пароля
Если файловый сервер в домене, монтировать можно по Kerberos: опции sec=krb5 и cruid=<UID> говорят клиенту взять билет из кэша учётных данных этого UID. Драйвер поддерживает это так: кэш билетов (файл ccache, полученный kinit для служебной учётной записи, в base64) кладут в Secret под ключом krb5cc_<UID>, совпадающим с cruid, а на узле драйвер записывает его в /var/lib/kubelet/kerberos/ (каталог должен существовать — драйвер его не создаёт). Билет у сервера получает cifs.upcall, который ядро запускает на самом узле, поэтому на узлах нужны cifs-utils, настроенный Kerberos и строка default_ccache_name, указывающая на этот каталог; Helm-чарт умеет записать её сам (linux.krb5AutoConfig=true пишет /etc/krb5.conf.d/90-csi-driver-smb.conf), остальная настройка Kerberos — забота администратора узлов. Пароля в кластере нет, но у билета ограниченный срок жизни, и кэш в Secret нужно регулярно обновлять внешним процессом — иначе через несколько часов или дней тома перестанут переподключаться. Для большинства кластеров это больше работы, чем выгоды; оправдано там, где политика запрещает NTLM. На стенде Kerberos не проверялся.
Том для приложения и права
PVC и Deployment из двух реплик в неймспейсе demo из части 11 — тот же writer на busybox, что в прошлой части, только том теперь reports на SMB:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: reports
namespace: demo
spec:
accessModes: ["ReadWriteMany"]
storageClassName: smb
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: writer
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: writer
template:
metadata:
labels:
app.kubernetes.io/name: writer
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: writer
image: busybox:1.37
command: ["sh", "-c", "while true; do echo \"$(date -u +%FT%TZ) $HOSTNAME\" >> /reports/visits.log; sleep 10; done"]
volumeMounts:
- name: reports
mountPath: /reports
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumes:
- name: reports
persistentVolumeClaim:
claimName: reports
$ kubectl apply -f sc.yaml -f writer.yaml
storageclass.storage.k8s.io/smb created
persistentvolumeclaim/reports created
deployment.apps/writer created
$ kubectl -n demo get pvc reports -w
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
reports Pending smb <unset> 0s
reports Pending pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 0 smb <unset> 1s
reports Bound pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 5Gi RWX smb <unset> 1s
$ kubectl -n demo get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
writer-554fccb9dc-8vb4g 1/1 Running 0 28s 172.20.172.10 k8s-node-2 <none> <none>
writer-554fccb9dc-kvd28 1/1 Running 0 27s 172.20.211.60 k8s-node-1 <none> <none>
Том создан за секунду — так это выглядит в журнале контроллера (--v=5 в манифесте, строки сокращены):
$ kubectl -n kube-system logs deploy/csi-smb-controller -c smb | grep -E 'CreateVolume|internally|Mounting cmd'
I1030 12:26:51.685032 1 utils.go:76] GRPC call: /csi.v1.Controller/CreateVolume
I1030 12:26:51.685043 1 utils.go:77] GRPC request: {"capacity_range":{"required_bytes":5368709120},"name":"pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27",…,"secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"mount_flags":["vers=3","seal","dir_mode=0770","file_mode=0660","uid=10001","cache=strict","actimeo=1","nobrl","nolease","mfsymlinks","hard"]}},"access_mode":{"mode":5}}]}
I1030 12:26:51.685200 1 controllerserver.go:350] internally mounting //files.example.com/k8s at /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
I1030 12:26:51.685419 1 mount_linux.go:270] Mounting cmd (mount) with arguments (-t cifs -o vers=3,seal,dir_mode=0770,file_mode=0660,uid=10001,cache=strict,actimeo=1,nobrl,nolease,mfsymlinks,hard,<masked> //files.example.com/k8s /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27)
I1030 12:26:51.699383 1 controllerserver.go:368] internally unmounting /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
Корень шары смонтирован с опциями класса, каталог создан, шара отмонтирована — всё за 17 мс. <masked> — то место, где в настоящей командной строке стоят имя и пароль.
А вот что получил node-плагин на k8s-node-1:
$ kubectl -n kube-system logs csi-smb-node-929cp -c smb | grep -E 'NodeStageVolume: targetPath|Mounting cmd' | head -2
I1030 12:26:53.298942 1 nodeserver.go:297] NodeStageVolume: targetPath(/var/lib/kubelet/plugins/kubernetes.io/csi/smb.csi.k8s.io/d94cf53eb14c47a64c6df39efd71480c44ad55bf9fc4712e6cb3e4619397435b/globalmount) volumeID(files.example.com/k8s#demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#archive) context(…) mountflags([vers=3 seal dir_mode=0770 file_mode=0670 uid=10001 cache=strict actimeo=1 nobrl nolease mfsymlinks hard]) mountOptions([vers=3 seal dir_mode=0770 file_mode=0670 uid=10001 cache=strict actimeo=1 nobrl nolease mfsymlinks hard gid=10001])
I1030 12:26:53.299169 1 mount_linux.go:270] Mounting cmd (mount) with arguments (-t cifs -o vers=3,seal,dir_mode=0770,file_mode=0670,uid=10001,cache=strict,actimeo=1,nobrl,nolease,mfsymlinks,hard,gid=10001,<masked> //files.example.com/k8s/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 /var/lib/kubelet/plugins/kubernetes.io/csi/smb.csi.k8s.io/d94cf53eb14c47a64c6df39efd71480c44ad55bf9fc4712e6cb3e4619397435b/globalmount)
В запросе kubelet пришло "volume_mount_group":"10001" — fsGroup пода. Драйвер дописал gid=10001 и поднял права группы: file_mode=0660 из класса стало 0670. В поде это выглядит так:
$ kubectl -n demo exec deploy/writer -- sh -c 'id; ls -lan /reports; tail -2 /reports/visits.log; df -h /reports'
uid=10001 gid=10001 groups=10001
total 8
drwxrwx--- 2 10001 10001 0 Oct 30 12:26 .
drwxr-xr-x 1 0 0 4096 Oct 30 12:26 ..
-rw-rwx--- 1 10001 10001 450 Oct 30 12:27 visits.log
2026-10-30T12:27:37Z writer-554fccb9dc-kvd28
2026-10-30T12:27:37Z writer-554fccb9dc-8vb4g
Filesystem Size Used Available Use% Mounted on
//files.example.com/k8s/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
30.3G 7.2G 23.1G 24% /reports
Приложение пишет не от root и без 0777, обе реплики дописывают строки в один файл. Но у файла теперь бит исполнения для группы — побочный эффект того, как драйвер обрабатывает fsGroup. Если это нежелательно, gid задают в классе сам (gid=10001 рядом с uid=10001): тогда драйвер режимы не трогает, а fsGroup пода для этого тома ни на что не влияет. На стенде класс с явным gid дал ровно file_mode=0660. Раз uid и так стоит в классе, класс фактически принадлежит одному приложению, и явный gid в нём ничего не усложняет.
Размер PVC — не квота, как и у NFS: df показывает весь диск сервера, а не 5 ГиБ. Ограничивают квотами на сервере (в Windows — квоты FSRM на папку, в Samba — квоты файловой системы) и мониторингом занятого места.
Одно соединение и одна сессия на узел
В прошлой части выяснилось, что клиент NFS один на пару «узел — сервер», и опции соединения задаёт первое монтирование. У cifs похоже, но строже. Добавим второй том того же сервера из класса с другими опциями (без seal, с явным gid) и под на k8s-node-1, в котором смонтированы оба тома:
user@k8s-node-1:~$ sudo grep -E '^[0-9]\)|Sessions:|Session Status|\\\\' /proc/fs/cifs/DebugData
1) ConnectionId: 0x5 Hostname: files.example.com
Sessions:
1) Address: 192.168.10.31 Uses: 2 Capability: 0x30006f Session Status: 1
0) IPC: \\files.example.com\IPC$ Mounts: 1 DevInfo: 0x0 Attributes: 0x0
1) \\files.example.com\k8s Mounts: 1 DevInfo: 0x20 Attributes: 0x1006f
2) \\files.example.com\k8s Mounts: 1 DevInfo: 0x20 Attributes: 0x1006f
user@k8s-node-1:~$ sudo ss -tn -4 dst 192.168.10.31
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.10.20:42516 192.168.10.31:445
Все тома одного сервера от одной учётной записи на узле идут через одно TCP-соединение и одну сессию (Uses: 2) — один вход, одни ключи. А подключений к шаре два: seal у одного тома и его отсутствие у другого — разные подключения. Ручное монтирование с теми же опциями, что у второго класса, присоединилось к уже существующему подключению (Mounts: 2). В отличие от NFS, cifs не присоединяет монтирование с несовместимыми опциями молча: по исходникам клиента несовпадающие диалект, echo_interval, retrans и параметры безопасности дают отдельное соединение, другая учётная запись — отдельную сессию, другое значение seal — отдельное подключение к шаре.
Практических следствий два. Первое: разрыв соединения или неудачный повторный вход задевает все тома этого сервера на узле разом. Второе: смена пароля, как мы видели, ломает их тоже все вместе — ещё одна причина менять учётные записи по процедуре.
Когда сервер недоступен
Отрежем k8s-node-2 от сервера правилом в начале цепочки INPUT на сервере (как и в прошлой части, ufw deny для этого не годится: установленные соединения он пропускает) и подождём почти четыре минуты:
user@files:~$ sudo iptables -I INPUT 1 -s 192.168.10.21 -p tcp --dport 445 -j DROP
user@k8s-node-2:~$ ps -eo pid,stat,wchan:25,cmd | grep 'while true' | grep -v grep # через 30 секунд
319125 Ds - sh -c while true; do echo "$(date -u +%FT%TZ) $HOSTNAME" >> /reports/visits.log; sleep 10; done
user@k8s-node-2:~$ sudo dmesg -T | grep -i cifs | tail -1 # через 3 минуты
[Fri Oct 30 12:40:23 2026] CIFS: VFS: \\files.example.com has not responded in 180 seconds. Reconnecting...
$ timeout 15 kubectl -n demo exec writer-bc8b4f458-k6z47 -- ls -l /reports; echo rc=$?
rc=124
user@files:~$ sudo iptables -D INPUT -s 192.168.10.21 -p tcp --dport 445 -j DROP
Процесс пода висит в состоянии D на записи, kubectl exec с ls не возвращается — как с NFS. Клиент заметил пропажу сервера только через 180 секунд: эхо-запросы он шлёт раз в echo_interval (по умолчанию 60 с) и объявляет сервер неотвечающим, когда от него ничего не было три интервала. После снятия правила клиент переподключился за несколько секунд, и запись, на которой процесс висел почти четыре минуты, завершилась:
user@files:~$ sudo grep -n -E 'T12:3[7-9]|T12:4[01]' /srv/samba/k8s/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/visits.log | sed -n '1,5p;26,31p'
130:2026-10-30T12:37:03Z writer-bc8b4f458-8wkrh
131:2026-10-30T12:37:06Z writer-bc8b4f458-k6z47
132:2026-10-30T12:37:13Z writer-bc8b4f458-8wkrh
133:2026-10-30T12:37:16Z writer-bc8b4f458-k6z47
134:2026-10-30T12:37:23Z writer-bc8b4f458-8wkrh
155:2026-10-30T12:40:53Z writer-bc8b4f458-8wkrh
156:2026-10-30T12:41:03Z writer-bc8b4f458-8wkrh
157:2026-10-30T12:37:26Z writer-bc8b4f458-k6z47
158:2026-10-30T12:41:13Z writer-bc8b4f458-8wkrh
159:2026-10-30T12:41:17Z writer-bc8b4f458-k6z47
160:2026-10-30T12:41:23Z writer-bc8b4f458-8wkrh
Строка с временем 12:37:26 записана после 12:41:03: это та самая запись, которая ждала сервер, — hard её не потерял. Реплика на k8s-node-1 всё это время писала как ни в чём не бывало. С soft (по умолчанию) после переподключения приложение получило бы ошибку на этой записи — EHOSTDOWN, «Host is down», — и с ней мы встретимся в разборе ниже. Что делать при долгом отказе сервера — так же, как с NFS в прошлой части: сначала чинить сервер или сеть, а не поды.
Разбор: «Host is down» на SMB-томах
Случай из практики, в одном кластере с NAS одного производителя. Поды периодически получали ошибку Host is down (EHOSTDOWN) при работе с файлами на SMB-томе. Сам NAS был доступен, поддержка производителя видела «обычные отключения клиентов».
Журнал ядра. На узлах — частые переподключения cifs, на нагруженных узлах десятки в минуту, и перед каждым одна и та же строка:
user@k8s-node-1:~$ sudo dmesg -T | grep -i cifs | tail -4
[Thu Sep 24 07:52:44 2026] CIFS: VFS: \\<адрес NAS> RFC 1002 unknown response type 0xfe
[Thu Sep 24 07:52:44 2026] CIFS: VFS: \\<адрес NAS> RFC 1002 unknown response type 0xfe
[Thu Sep 24 07:52:45 2026] CIFS: VFS: \\<адрес NAS> RFC 1002 unknown response type 0xfe
[Thu Sep 24 07:52:45 2026] CIFS: VFS: \\<адрес NAS> RFC 1002 unknown response type 0xfe
Клиент ожидал четырёхбайтовый заголовок кадра с нулевым типом, а прочитал байт 0xfe — первый байт сигнатуры SMB2. То есть он рассинхронизировался с потоком: читает заголовок кадра там, где на самом деле середина сообщения. После этого клиент рвёт соединение и переподключается, а на монтировании soft все операции, попавшие в это окно, сразу получают EHOSTDOWN. Время ошибок в журнале приложения совпадало с этими строками до секунды.
Дамп трафика. tcpdump на узле на несколько десятков секунд и разбор в Wireshark показали: разметка кадров на проводе целая, сервер поток не портит. Соединение всегда закрывал клиент, через доли миллисекунды после одного и того же кадра от NAS:
┌─────────────────┬──────────────────────┬──────────────────────┐
│ 00 00 00 99 │ SMB2 CREATE │ SMB2 CLOSE │
│ заголовок кадра │ STATUS_PENDING │ STATUS_PENDING │
│ длина 153 │ NextCommand = 80 │ связанная команда │
└─────────────────┴──────────────────────┴──────────────────────┘
клиент читает разбирает первый FE 53 4D 42 принят
длину кадра промежуточный ответ за заголовок нового
и считает кадр кадра: «тип 0xfe»
законченным
Это ответ на составной запрос клиента CREATE + CLOSE, которым cifs выполняет stat и похожие операции. Когда открываемый файл держал под lease другой клиент — другой под на другом узле, — NAS отправлял держателю разрыв lease и, пока ждал, отвечал на составной запрос двумя промежуточными ответами STATUS_PENDING в одном кадре. Клиент cifs обрабатывал первый промежуточный ответ и шёл читать следующий заголовок кадра из сокета, не дочитав вторую часть составного кадра. Начало второго ответа он принимал за заголовок — отсюда 0xfe и разрыв.
Это объясняло всё остальное: ошибка была только с этим NAS (другие серверы так не отвечают), не зависела от диалекта и росла вместе с конкурентным доступом к одним и тем же файлам — частота подскочила, когда одно из приложений стало работать в большом числе реплик на всех узлах. В исходниках клиента это видно прямо: в цикле приёма cifs_demultiplex_thread промежуточный ответ STATUS_PENDING завершает обработку кадра, и цикл идёт читать новый заголовок, минуя ветку, которая дочитывает остаток составного кадра. Этот код одинаков в ядрах 5.15, 6.8, в 7.0 и в основной ветке на октябрь 2026 года: обновление ядра не помогает.
Лечение — опции монтирования.
nolease— клиенты кластера перестают запрашивать lease и oplock. NAS не нужно отзывать их у соседних подов перед открытием файла, и пропадает сам повод отвечатьSTATUS_PENDING. На этих томах стоялоcache=none, так что клиент и так ничего не кэшировал, и потерь почти не было.nobrl— по соседней проблеме: конфликты принудительных байтовых блокировок давали в подахEACCESна чтение во время записи. Приложения не полагались на межузловые блокировки, поэтому блокировки оставили на клиенте.vers=3— сначала диалект попробовали понизить до 3.0, но разрывы остались: причина была не в диалекте. Вернули наибольший общий (3.1.1 с NAS), но не жёстко: у других серверов, с которыми работали те же тома, максимум был 3.0.2, иvers=3.1.1сломал бы их монтирование.
После перемонтирования разрывов стало на два порядка меньше: с 50–80 в минуту на узел до 5–11 на узел за 25 минут. Оставшиеся — от других клиентов NAS, которые продолжают брать oplock (например, рабочие станции Windows); на них nolease в кластере не действует. Следующий рубеж, если бы этого не хватило, — hard: он не убирает разрывы, но прячет их от приложения, как запись, переждавшую отказ сервера выше. В разборе это оставили запасным шагом, потому что hard превращает настоящий отказ NAS в зависшие процессы.
Как перемонтировать тома
Опции применяются только при новом монтировании, и здесь легко ошибиться. У динамических PV mountOptions скопированы из класса в момент создания, поэтому новые опции дописывают в каждый PV (kubectl patch pv … --type=json, как в прошлой части; у статических PV — так же). Но и после этого kubectl rollout restart ничего не изменит: монтирование NodeStage одно на узел и PV, а при плавном обновлении новый под поднимается на узле раньше, чем уходит старый, и монтирование ни на миг не освобождается. Проверим на стенде, поменяв в PV actimeo=1 на actimeo=5:
$ kubectl patch pv pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 --type=json \
-p '[{"op":"replace","path":"/spec/mountOptions","value":["vers=3","seal","dir_mode=0770","file_mode=0660","uid=10001","cache=strict","actimeo=5","nobrl","nolease","mfsymlinks","hard"]}]'
persistentvolume/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 patched
$ kubectl -n demo rollout restart deploy/writer
deployment.apps/writer restarted
$ kubectl -n demo get pods -o wide # после выкатки
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
writer-64474f4896-2g9cn 1/1 Running 0 74s 172.20.211.55 k8s-node-1 <none> <none>
writer-64474f4896-8hvkq 1/1 Running 0 69s 172.20.172.7 k8s-node-2 <none> <none>
user@k8s-node-1:~$ grep -o 'globalmount cifs [^ ]*' /proc/mounts | grep -o 'actimeo=[0-9]*'
actimeo=1
Новые поды на обоих узлах, а монтирование прежнее. Порядок — по одному узлу:
kubectl cordon k8s-node-1— новые поды на узел не попадут.- Удалить на узле поды, использующие SMB-тома (или
kubectl drain, если на узле нет ничего, что не переживёт переезда); их контроллеры поднимут замену на других узлах. - Дождаться, пока на узле не останется монтирований этого сервера:
grep cifs /proc/mountsпуст (NodeUnstage размонтировал глобальное монтирование). kubectl uncordon k8s-node-1, дождаться готовности деплойментов, перейти к следующему узлу.- В конце —
kubectl rollout restartтех же деплойментов, чтобы поды снова распределились по узлам.
$ kubectl cordon k8s-node-1
node/k8s-node-1 cordoned
$ kubectl -n demo delete pod --field-selector spec.nodeName=k8s-node-1 -l app.kubernetes.io/name=writer
pod "writer-64474f4896-2g9cn" deleted from demo namespace
user@k8s-node-1:~$ grep cifs /proc/mounts || echo '(no cifs mounts)'
(no cifs mounts)
$ kubectl uncordon k8s-node-1
node/k8s-node-1 uncordoned
То же для k8s-node-2, затем rollout restart, и на обоих узлах в /proc/mounts actimeo=5. Если у приложений нет PodDisruptionBudget, за количеством одновременно недоступных реплик на шаге 2 придётся следить самим (PodDisruptionBudget — в части 13).
Смотрим руками
PV — сразу после создания:
$ kubectl get pv pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 -o yaml
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: smb.csi.k8s.io
volume.kubernetes.io/provisioner-deletion-secret-name: smb-files-k8s
volume.kubernetes.io/provisioner-deletion-secret-namespace: storage-credentials
creationTimestamp: "2026-10-30T12:26:51Z"
finalizers:
- kubernetes.io/pv-protection
name: pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
resourceVersion: "1133559"
uid: 9d0558a4-4e96-4939-9061-3c0711b2c83f
spec:
accessModes:
- ReadWriteMany
capacity:
storage: 5Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: reports
namespace: demo
resourceVersion: "1133549"
uid: fc91f7d5-6db4-471b-a083-3b93c0bdae27
csi:
driver: smb.csi.k8s.io
nodeStageSecretRef:
name: smb-files-k8s
namespace: storage-credentials
volumeAttributes:
csi.storage.k8s.io/pv/name: pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
csi.storage.k8s.io/pvc/name: reports
csi.storage.k8s.io/pvc/namespace: demo
onDelete: archive
source: //files.example.com/k8s
storage.kubernetes.io/csiProvisionerIdentity: 1793363183585-5388-smb.csi.k8s.io
subDir: demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
volumeHandle: files.example.com/k8s#demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#archive
mountOptions:
- vers=3
- seal
- dir_mode=0770
- file_mode=0660
- uid=10001
- cache=strict
- actimeo=1
- nobrl
- nolease
- mfsymlinks
- hard
persistentVolumeReclaimPolicy: Retain
storageClassName: smb
volumeMode: Filesystem
status:
lastPhaseTransitionTime: "2026-10-30T12:26:51Z"
phase: Bound
volumeHandle из четырёх частей: сервер и шара, подкаталог, имя PV и политика удаления. Ссылка на Secret есть в двух местах: nodeStageSecretRef — для монтирования на узле, аннотации provisioner-deletion-secret-* — для DeleteVolume. Пароля в PV, понятно, нет.
Монтирования на узле — глобальное и bind-монтирование в под (после смены actimeo):
user@k8s-node-1:~$ grep cifs /proc/mounts
//files.example.com/k8s/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 /var/lib/kubelet/plugins/kubernetes.io/csi/smb.csi.k8s.io/d94cf53eb14c47a64c6df39efd71480c44ad55bf9fc4712e6cb3e4619397435b/globalmount cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,nolease,username=svc-k8s-smb,uid=10001,forceuid,gid=10001,forcegid,addr=192.168.10.31,file_mode=0670,dir_mode=0770,seal,hard,nounix,serverino,mapposix,nobrl,mfsymlinks,reparse=nfs,nativesocket,symlink=mfsymlinks,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=5,closetimeo=1 0 0
//files.example.com/k8s/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 /var/lib/kubelet/pods/55ea45b3-af45-4971-804b-2f299042f8bc/volumes/kubernetes.io~csi/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/mount cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,nolease,username=svc-k8s-smb,uid=10001,forceuid,gid=10001,forcegid,addr=192.168.10.31,file_mode=0670,dir_mode=0770,seal,hard,nounix,serverino,mapposix,nobrl,mfsymlinks,reparse=nfs,nativesocket,symlink=mfsymlinks,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=5,closetimeo=1 0 0
Две строки с одинаковыми опциями. vers=3.1.1 — договорённый диалект. gid=10001 и file_mode=0670 — работа драйвера с fsGroup. nounix — расширения POSIX не запрашивались, владельцы и режимы эмулируются. Пароля в списке нет: он передан ядру отдельно. Опции проверяют именно здесь, а не в манифесте: только /proc/mounts показывает, с чем том смонтирован на самом деле.
Внутреннее состояние клиента — соединение, диалект, шифрование, сессия, шары:
user@k8s-node-1:~$ sudo cat /proc/fs/cifs/DebugData
Display Internal CIFS Data Structures for Debugging
---------------------------------------------------
CIFS Version 2.59
Features: DFS,FSCACHE,STATS,DEBUG,ALLOW_INSECURE_LEGACY,CIFS_POSIX,UPCALL(SPNEGO),XATTR,ACL,WITNESS
CIFSMaxBufSize: 16384
Active VFS Requests: 0
Servers:
1) ConnectionId: 0x7 Hostname: files.example.com
ClientGUID: D96C6663-4CB8-E649-9719-6ABE45E30B22
Number of credits: 4149,1,1 Dialect 0x311 signed posix
Server capabilities: 0x30006f
TCP status: 1 Instance: 3
Local Users To Server: 1 SecMode: 0x3 Req On Wire: 0 Net namespace: 4026531833
In Send: 0 In MaxReq Wait: 0
Compression: no built-in support
Encryption: Negotiated cipher (AES128-GCM)
Sessions:
1) Address: 192.168.10.31 Uses: 1 Capability: 0x30006f Session Status: 1
Security type: RawNTLMSSP SessionId: 0x80bf4ed6 encrypted
User: 10001 Cred User: 0
Shares:
0) IPC: \\files.example.com\IPC$ Mounts: 1 DevInfo: 0x0 Attributes: 0x0
PathComponentMax: 0 Status: 1 type: 0 Serial Number: 0x0 encrypted
Share Capabilities: None Share Flags: 0x8000
tid: 0x50da3957 Maximal Access: 0x1f00a9
1) \\files.example.com\k8s Mounts: 1 DevInfo: 0x20 Attributes: 0x1006f
PathComponentMax: 255 Status: 1 type: DISK Serial Number: 0x5c3565d6 encrypted
Share Capabilities: None Aligned, Partition Aligned, Share Flags: 0x8000
tid: 0x51760654 Optimal sector size: 0x200 Maximal Access: 0x1f01ff
...
Dialect 0x311 — SMB 3.1.1; signed и SecMode: 0x3 — подпись включена и обязательна; posix — сервер предлагает расширения POSIX (Samba 4.23 умеет их по умолчанию), но монтирование их не просило. Encryption: Negotiated cipher (AES128-GCM) — согласованный шифр, у сессии и шар пометка encrypted. Security type: RawNTLMSSP — вход по NTLM. Number of credits — сколько запросов клиент может держать в полёте; In MaxReq Wait больше нуля означает, что запросы ждут кредитов. Status: 1 у сессии и шары — подключено; Instance: 3 — номер переподключения TCP: этот узел пережил перезапуск smbd и смену пароля из опытов выше. Дальше в файле — список сетевых интерфейсов сервера для multichannel, его мы опустили.
Счётчики операций и переподключений:
user@k8s-node-1:~$ sudo cat /proc/fs/cifs/Stats
Resources in use
CIFS Session: 1
Share (unique mount targets): 2
SMB Request/Response Buffer: 1 Pool size: 5
SMB Small Req/Resp Buffer: 1 Pool size: 30
Operations (MIDs): 0
2 session 2 share reconnects
Total vfs operations: 616 maximum at one time: 3
Max requests in flight: 3
1) \\files.example.com\k8s
SMBs: 553 since 2026-10-30 12:32:19 UTC
Bytes read: 0 Bytes written: 3403
Open files: 0 total (local), 0 open on server
TreeConnects: 2 total 0 failed
TreeDisconnects: 0 total 0 failed
Creates: 189 total 0 failed
Closes: 178 total 0 failed
Flushes: 1 total 0 failed
Reads: 0 total 0 failed
Writes: 77 total 0 failed
Locks: 0 total 0 failed
IOCTLs: 4 total 0 failed
QueryDirectories: 4 total 0 failed
ChangeNotifies: 0 total 0 failed
QueryInfos: 96 total 0 failed
SetInfos: 2 total 0 failed
OplockBreaks: 0 sent 0 failed
session reconnects и share reconnects — главные цифры здоровья: в нормальном состоянии они стоят на месте. Здесь их по два — от перезапуска smbd и неудачных входов после смены пароля. В разобранном выше случае счётчик переподключений сессий на загруженных узлах измерялся миллионами за несколько недель — по нему проблему и стоило искать раньше, чем по жалобам приложений. Locks: 0 — nobrl работает, блокировки на сервер не уходят; OplockBreaks: 0 — с nolease разрывать нечего.
Журнал ядра:
user@k8s-node-1:~$ sudo dmesg -T | grep -i cifs | tail -2
[Fri Oct 30 12:43:57 2026] CIFS: enabling forceuid mount option implicitly because uid= option is specified
[Fri Oct 30 12:43:57 2026] CIFS: Attempting to mount //files.example.com/k8s
При монтировании — Attempting to mount и уведомления о неявных опциях, больше ничего — хорошо. Строки has not responded in N seconds. Reconnecting... — сервер не отвечал (сеть или сам сервер), unknown response type — рассинхронизация потока, как в разборе, STATUS_LOGON_FAILURE и failed to create a new SMB session — ошибка аутентификации (часто — сменённый пароль). Время в dmesg -T вычисляется из времени загрузки и счётчика ядра и может расходиться с настоящим (на стенде — на 17 секунд); для точного сопоставления с журналами приложений лучше journalctl -k.
С другой стороны — сервер. smbstatus показывает, кто подключён, с каким диалектом, шифрованием и подписью:
user@files:~$ sudo smbstatus
Samba version 4.23.6-Ubuntu-4.23.6+dfsg-1ubuntu2.2
PID Username Group Machine Protocol Version Encryption Signing
----------------------------------------------------------------------------------------------------------------------------------------
14130 svc-k8s-smb svc-k8s-smb 192.168.10.20 (ipv4:192.168.10.20:57814) SMB3_11 AES-128-GCM AES-128-CMAC
14131 svc-k8s-smb svc-k8s-smb 192.168.10.21 (ipv4:192.168.10.21:55986) SMB3_11 AES-128-GCM AES-128-CMAC
Service pid Machine Connected at Encryption Signing
---------------------------------------------------------------------------------------------
IPC$ 14131 192.168.10.21 Fri Oct 30 12:41:07 PM 2026 UTC AES-128-GCM -
k8s 14131 192.168.10.21 Fri Oct 30 12:41:07 PM 2026 UTC AES-128-GCM AES-128-CMAC
IPC$ 14130 192.168.10.20 Fri Oct 30 12:35:11 PM 2026 UTC AES-128-GCM AES-128-CMAC
k8s 14130 192.168.10.20 Fri Oct 30 12:35:04 PM 2026 UTC AES-128-GCM AES-128-CMAC
No locked files
По процессу smbd на каждый узел: одно соединение на узел, как и показал DebugData. Время подключения — момент последнего переподключения: k8s-node-2 переподключился в 12:41:07 после опыта с отказом. No locked files — блокировок на сервере нет.
Трафик для разбора в Wireshark — графическом анализаторе сетевых протоколов, который умеет разбирать SMB2/3 по полям:
user@k8s-node-1:~$ sudo timeout 12 tcpdump -i any -s 0 -w /tmp/smb.pcap host 192.168.10.31 and port 445
tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
19 packets captured
21 packets received by filter
0 packets dropped by kernel
user@k8s-node-1:~$ sudo tcpdump -r /tmp/smb.pcap -nn -X -c 1 'tcp[tcpflags] & tcp-push != 0' 2>/dev/null | sed -n '1p;5,6p'
12:36:43.914216 ens18 Out IP 192.168.10.20.57814 > 192.168.10.31.445: Flags [P.], seq 1371088206:1371088734, ack 1960316619, win 126, options [nop,nop,TS val 318912597 ecr 1880430020], length 528
0x0030: 7015 15c4 0000 020c fd53 4d42 42cd d8c6 p........SMBB...
0x0040: ea98 d8db 62b6 ca70 6b4a 596b ab8d 18ce ....b..pkJYk....
После заголовков IP и TCP (52 байта, строки до 0x0030 опущены) — 00 00 02 0c: заголовок кадра с нулевым типом и длиной 524 байта, — а за ним fd 53 4d 42, заголовок преобразования: сообщение зашифровано. Команды и статусы внутри не видны — только длины кадров, время и то, какая сторона закрыла TCP-соединение; для разбора по командам нужен ключ сессии или тестовая шара без шифрования, на которой проблема воспроизводится. 0 packets dropped by kernel — обязательное условие: дамп с потерями пакетов для такого разбора бесполезен. В Wireshark — фильтр smb2, колонки smb2.cmd, smb2.nt_status, smb2.flags.async; для поиска разрывов — tcp.flags.fin == 1 || tcp.flags.reset == 1 и смотреть, какой кадр шёл перед ними.
Расширение и клон. Расширение мгновенное: драйвер просто записывает новый размер — квоты на шаре он не ставит. Клон — копирование файлов cp -a из каталога исходного тома контроллером:
$ kubectl -n demo patch pvc reports -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
persistentvolumeclaim/reports patched
$ kubectl -n kube-system logs deploy/csi-smb-controller -c smb | grep 'ControllerExpandVolume(' | tail -1
I1030 12:44:10.097347 1 controllerserver.go:321] ControllerExpandVolume(files.example.com/k8s#demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27#archive) successfully, currentQuota: 10737418240 bytes
$ kubectl apply -f reports-copy.yaml # PVC с dataSource: kind PersistentVolumeClaim, name reports
persistentvolumeclaim/reports-copy created
$ kubectl -n kube-system logs deploy/csi-smb-controller -c smb | grep copied | tail -1
I1030 12:44:18.563142 1 controllerserver.go:415] copied /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/. -> /tmp/pvc-7026f4d6-053b-431d-8d0e-7361f1f3e8fb/demo-reports-copy-pvc-7026f4d6-053b-431d-8d0e-7361f1f3e8fb
Клон живого тома — не снимок: файлы, которые приложение меняло во время копирования, окажутся в копии в каком-то промежуточном состоянии.
И освобождение. Удаляем приложение и заявки — PV остаются Released; данные проверены и больше не нужны — переключаем PV на Delete, и срабатывает onDelete: archive:
$ kubectl -n demo delete deploy writer
deployment.apps "writer" deleted from demo namespace
$ kubectl -n demo delete pvc reports reports-copy
persistentvolumeclaim "reports" deleted from demo namespace
persistentvolumeclaim "reports-copy" deleted from demo namespace
$ kubectl patch pv pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
persistentvolume/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 patched
$ kubectl -n kube-system logs deploy/csi-smb-controller -c smb | grep -E 'Mounting cmd|archiving' | tail -2
I1030 12:45:31.137291 1 mount_linux.go:270] Mounting cmd (mount) with arguments (-t cifs -o ,file_mode=0777,dir_mode=0777,<masked> //files.example.com/k8s /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27)
I1030 12:45:31.147817 1 controllerserver.go:224] archiving subdirectory /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27 --> /tmp/pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27/archived-demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
user@files:~$ sudo ls -la /srv/samba/k8s
total 16
drwxrwx--- 4 svc-k8s-smb svc-k8s-smb 4096 Oct 30 12:45 .
drwxr-xr-x 3 root root 4096 Oct 30 12:22 ..
drwxrwx---+2 svc-k8s-smb svc-k8s-smb 4096 Oct 30 12:26 archived-demo-reports-copy-pvc-7026f4d6-053b-431d-8d0e-7361f1f3e8fb
drwxrwx---+2 svc-k8s-smb svc-k8s-smb 4096 Oct 30 12:26 archived-demo-reports-pvc-fc91f7d5-6db4-471b-a083-3b93c0bdae27
Второй PV переключён так же. Видна и та самая строка монтирования без опций класса — -o ,file_mode=0777,dir_mode=0777. Каталоги archived-… удаляют на сервере руками, когда точно не нужны.
Источники
- csi-driver-smb: репозиторий, выпуск v1.20.4, манифесты выпуска, установка v1.20.4 и Helm-чарт — состав драйвера, образы, RBAC, версия чарта без
v,krb5AutoConfig. - csi-driver-smb: параметры драйвера —
source,subDirи его шаблоны,onDelete, Secret для контроллера и узла, форматvolumeHandle,mountOptionsдляDeleteVolume, Kerberos; пример StorageClass — с0777, который мы не повторяем; исходник node-плагина —gidизvolume_mount_group, права группы, передача пароляmount. - Kubernetes CSI: CSIDriver Object и fsGroup Support —
fsGroupPolicy,podInfoOnMount, делегированиеfsGroupдрайверу; Secrets and Credentials — параметрыprovisioner-secret-*иnode-stage-secret-*; CSI Ephemeral Inline Volumes — режимEphemeral. - Container Storage Interface Specification —
NodeStageVolume,VOLUME_MOUNT_GROUPи полеvolume_mount_group,DeleteVolumeбез опций монтирования. - Kubernetes: Storage Classes и Persistent Volumes — поля класса, опции монтирования, освобождение и расширение; Configure a Security Context и делегирование fsGroup драйверу CSI; Good practices for Kubernetes Secrets и RBAC — кто читает Secret; Safely Drain a Node —
cordonиdrain. - Клиент Linux: mount.cifs(8) —
vers,seal,uid/gid,file_mode/dir_mode,cache,actimeo,nobrl,nolease,mfsymlinks,hard/soft,echo_interval,password2, переменнаяPASSWD; cifs.upcall(8) — Kerberos; Linux kernel: CIFS usage —/proc/fs/cifs/DebugData,Stats,mount_params; fs/smb/client/connect.c — цикл приёмаcifs_demultiplex_thread, проверка «has not responded», правила совместного использования соединений и сессий. - Samba: smb.conf(5) —
server min protocolи вариантSMB3_11,server signing,server smb encrypt,map to guest,restrict anonymous,disable netbios,smb ports,hosts allow,valid users, маски; smbpasswd(8), pdbedit(8), testparm(1) и smbstatus(1) — учётные записи, проверка конфигурации, подключения; Ubuntu Server: Samba as a file server; useradd(8) и ufw(8). - Windows Server: New-SmbShare и Set-SmbServerConfiguration — параметры шары и сервера; SMB security enhancements — шифрование,
RejectUnencryptedAccessпо умолчанию, AES-128-GCM, SMB1 с версии 1709; SMB signing overview — подпись, AES-CMAC и AES-GMAC; Detect, enable, and disable SMBv1, SMBv2, and SMBv3; New-ADUser, icacls и New-NetFirewallRule — учётная запись, права NTFS, файрвол. - Протокол: MS-SMB2 — диалекты, кредиты, составные запросы,
STATUS_PENDING, lease и разрывы, подпись и заголовок преобразования; MS-NLMP — NTLM; RFC 1002 — заголовок сессии NetBIOS; Overview of file sharing using SMB 3. - Диагностика: Wireshark: SMB2 — разбор SMB2/3 и фильтры; tcpdump(1) — захват и
-X.
Дальше — другие хранилища: локальные тома и local-path-provisioner, S3 через CSI и почему это не файловая система, распределённые Longhorn и Ceph, и где в кластере держать базы данных.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.