Thursday, December 27, 2018

Highly-available Fileserver in a IaaS cloud

Вот есть, скажем, IaaS-облачко, а в нём виртуалка с Windows, работает файловым сервером - публикует папки по обычному протоколу SMB. Хочется, чтобы если она упадёт, файлы были доступны с какой-нибудь другой. Как этого добиться минимальными средствами?

Казалось бы, для такого совершенно стандартного желания и решение должно быть совершенно стандартным, особенно уж для такой неновой и распространённой штуки, как общие папки Windows: поставь такую-то фичу, и щёлкни раз-два-три. Ага, щас!


Вариант 1 - shared storage

Удобнее всего, конечно, чтобы у двух виртуалок был общий диск. Тогда на них можно настроить Failover Cluster, в нём запустить fileserver role с такой прекрасной фичей, как Transparent Failover, которая позволяет клиентским приложениям вообще не чувствовать, что сервер сменился. Клиенты будут обращаться по некоему floating IP, который будет переходить с сервера на сервер вместе с ролью в случае фэйловера. Скажем, в данном примере этому IP соответствует DNS-имя Filer:

Но увы! наше облачко такое не предоставляет.


Вариант 2 - репликация

Пускай у каждой из виртуалок будет свой местный диск, а мы будем файлы между ними синхронизировать.
В набор Windows Server для таких дел входит DFS Replication (DFS-R).

Достоинства:
  • фича входит в стандартный набор в Windows Server, ей сто лет в обед.
На этом достоинства заканчиваются и начинаются недостатки:
  • главный - никаких гарантий по части целостности данных. Один клиент может изменять файл на одном сервере, а другой в то же - тот же файл на другом сервере, и пожалуйста: конфликт;
  • если файл залочен клиентом, то изменения в нём копироваться не будут, пока клиент замок не снимет; пример - логи. Стало быть, его чтение с другого сервера реальное содержание не покажет;
  • очень непрозрачный и недокументированный способ работы. Пример: если файлы синхронизуются более-менее сразу, то вот изменения в NTFS permissions или ownership - со здоровенной задержкой, могущей доходить минут до 20. В чём логика, догадываюсь - такое изменение может переписать ACLs сверху вних по здоровеннейшему дереву, так что лучше подождать, пока юзер закончит их вносить, и лишь затем реплицировать. Но вот не документировано это нигде. В случае накладок, десинхронизации, конфликтов понять, что именно пошло не так, очень тяжело.
Схожий по функциям софт от прочих производителей я не пробовал, но наверняка и у них те же ограничения.

Поскольку это не кластер, и никакого floating IP между серверами нет, как нам обеспечить переключение? Тут может помочь другая фича - DFS-Namespaces или DFS-N, она позволяет распределять клиентов между двумя или более серверами. К Replication она отношения прямого не имеет - она просто позволяет замаскировать UNC-пути к общим папкам на разных серверах за одним общим путём, и неважно, каким образом файлы на этих серверах оказались. К примеру, при обращении к контроллеру домена example.org по пути \\example.org\SHARED\Folder, SMB-клиент Windows получит ссылки на два пути:
  • \\server1.example.org\SharedFolder
  • \\server2.example.org\SharedFolder
Он выберет одну из них и будет работать соответственно либо с одним сервером, либо с другим - а если тот перестанет отвечать, переключится на другой.

Фича полезная, но failover достаточно медленный - если сервер вдруг пропадает с горизонта, то клиент сперва пытается посылать пакеты по существующему TCP-соединению, затем - открыть новые на тот же сервер, и лишь затем - пойти на следующий сервер по списку, полученному от DFS namespace. Уходит на это секунд 80.

Ещё возможность: просто определить DNS-имя, указывающее на IP-адреса обоих серверов -  к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
Тогда клиенты распределятся между серверами - получив от DNS оба адреса, одни выберут первый, а другие - второй. Если же выбранный адрес перестанет отвечать, SMB-клиент достаточно умён, чтобы переключиться на второй - см. ниже про failover. Занимает процесс около 30-ти секунд.

Недостаток - не будет работать протокол аутентификации Kerberos, поскольку SMB-служба на каждом сервере привязана к его личному computer account в Active Directory, а аккаунт - к его личному хостнейму. Привязать же общий хостнейм к обоим аккаунтам нельзя. Можно ли запустить SMB-службу не под локальным аккаунтом (SYSTEM), а под доменным - обычным юзерским или gMSA? Не знаю, не пробовал.

Впрочем, всегда есть в запасе старый добрый NTLM.


Вариант 3 - виртуальный shared storage

Если облако не предоставляет shared storage, давайте построим его сами!
Тут, конечно, напрашивается воткнуть ещё одну виртуалку, которая будет предоставлять диски для наших файлсерверов по протоколу iSCSI - и тогда мы можем построить красивенький Failover Cluster и радоваться. Но это будет, естественно, самообман, потому как тогда наш iSCSI-сервер станет единой точкой отказа - если он рухнет, то всему хана.

Стало быть, надо подымать не одну виртуалку, а пару-тройку и реплицировать между ними на сей раз не файлы, а блоки. Что сразу же сильно усложняет систему: нам-то хотелось всего-навсего, чтобы пара Windows-машинок подменяла друг друга в случае чего, а тут приходится сооружать целый зоопарк.


Можно попытаться функции совместить: а пускай наши файлсервера заодно и работают как virtual SAN - синхронизируют между собой некий виртуальный диск и к нему же и подключаются как iSCSI-клиенты (то, что называется модным словом hyper-converged).
Но:
  • это повышает риск - надо полагаться на то, что при поломке, падении, перезагрузке не только Failover Cluster отработает как надо, но и virtual SAN.
  • вариантов virtual SAN очень много, но большинство из них под Линукс. Стало быть, на наши виртуалочки их не воткнёшь.
  • вероятно, что количество виртуалок всё равно придётся повышать до трёх, чтобы избежать возможности split brain.
На практике я опробовал два подхода к этому снаряду:

1. Windows S2D - первый кандидат, поскольку готовая фича в Windows Server 2016. Очень хотелочь бы опробовать, но увы. У неё суровые требования к локальным дискам, которые без хаканья на уровне VMware обойти нельзя. А облачко подобных игр не позволяет.

2. StarWind vSAN - привлёк внимание тем, что написан для Windows и интересной бизнес-моделью: разница между платной и бесплатной версиями лишь в отсутствии в бесплатном варианте техподдержки и графической консоли - управление идёт через PowerShell. Освоить ихний PowerShell совсем несложно, но:
  • требует он скрупулёзной внимательности и аккуратности, особенно если создаёшь много дисков; цена опечатки может быть слишком высока.
  • увы, багов слишком много. Диски могут рассинхронизироваться навсегда от простой перезагрузки; одни и те же команды могут выполняться раз выполняться, а раз - выдавать совершенно невразумительные ошибки; траблшутить всё это одному - не легче, чем DFS, а то и тяжелее. И привести эти баги могут к очень тяжёлым последствиям.
Основательно намучившись, решил, что в продакшене у меня никакого Старвинда не будет. Возможно, что при работе через платный GUI вдруг та же система проявляет чудеса надёжности, но мне это что-то сомнительно.


Конечный вариант - слоноптица

В конце остановился на совмещении вариантов 1 и 2: дублировать файлы с помощью DFS Replication, но при этом использовать плавающий адрес Failover Cluster для доступа к ним.


Иными словами, если у нас есть \\server1.example.org и \\server2.example.org, то для доступа к файлам мы будем использовать  некое третье имя - скажем, \\filer.example.org - и оно будет привязано к кластерному IP-адресу, переходящему от одного сервера к другому в случае падения. Или же IP-адресам, в случае если наши сервера сидят в разных сетях.

Строится это следующим образом:
  1. Определяем DFS-R между двумя машинами.
  2. Добавляем обе в Failover Cluster. Поскольку shared storage у нас нету, используем какой-нибудь удалённый shared folder как quorum witness - или Azure как cloud witness.
  3. Создаём в кластере empty role, даём этой роли некое имя, привязываем к IP-адресу, прописываем в DNS. Computer account в Active Directory для этой роли не создаётся - незачем.
  4. Определяем на каждой машине те же shared folders на основе дерева, синхронизируемого посредством DFS-R.
Это позволяет, с одной стороны, использовать локальные диски. а с другой, избежать недостатков DFS:
  • все клиенты юзают тот же сервер, который будет предоставлять замок на запись в конкретный файл лишь одному из них. Чтение изменений в файле доступны всем сразу.
  • соответственно, нет конфликтов между серверами, все изменения идут в одном направлении.
  • очень быстрый graceful failover, если оба сервера в той же сети (не graceful - не очень). См. ниже.
Но стоит учитывать:
  • мы лишаемся возможности распределять нагрузку между серверами (нам это особенно и не нужно - она и так мала).
  • если файл был открыт на запись, были внесены изменения, записаны на диск, но сервер рухнул перед тем, как замок был снят - значит, реплицированы эти изменения не были. Если рухнувший сервер подымется перед тем, как тот же файл был изменён на втором - репликация произойдёт. Если после - то реплицирована будет версия файла со второго сервера, поскольку у неё будет метка времени посвежее.

Дополнения и уточнения
Свидетель из Фрязино

Тонкий момент - если мы просто подымем два сервера в рамках Failover Cluster и будем ждать, что они будут друг друга взаимно подстраховывать, нас ждёт облом. В тот момент, что один из них отключится, роль файлсервера остановится так же и на втором, прекратит работать наш кластер.

Почему? Для избежания неприятной ситуации под названием "split brain", когда оба сервера считают себя главными и могут, в отсутствии координации друг с другом, искорёжить данные.

Как этого избежать? Есть два варианта:
  • Добавить третий сервер в кластер - и заодно в replication group DFS-R, чтобы копия файлов была на всех трёх.
  • Добавить так называемого "свидетеля" (witness).
Свидетель этот является не процессом, как сервер, а просто-напросто неким "журналом", куда оба сервера могут писать, отмечаясь, что они живы и указывая, кто из них в активной роли. 
  • Если сервер обнаруживает, что не слышит второй, но "свидетель" доступен - он продолжает работать.
  • Если он обнаруживает, что не может связаться ни со вторым, ни со "свидетелем" - он останавливается.
  • Если оба сервера общаются нормально, но "свидетель" отвалился - они продолжают работать как обычно.

В Windows 2016 есть три варианта для witness:
  1. Либо это shared disk, общий для всех узлов кластера - но мы начали с того, что наше облако таких не предоставляет. Если предоставляет - нам не нужна эта морока с DFS-R, стоит выстроить просто обычный Failover Cluster по рецептам Microsoft.
  2. Либо эта shared folder на каком-то третьем SMB-сервере - но тогда надо иметь этот сервер. Его не надо подключать в кластер, достаточно, чтобы он был доступен с обоих кластерных узлов. Это может быть контроллер домена, к примеру.
  3. Либо (новая фишка Win2016) это может быть blob в микрософтовском облаке - в Azure storage account. Но тогда надо располагать подпиской на Azure.

Немного технических потрохов про failover:

Скорость переключения очень зависит от того, происходит ли оно в результате того, что бывший активный сервер сам сдаёт полномочия (graceful failover - по команде админа или из-за "мягкой" перезагрузки) - или же в результате резкого падения сервера или полной потери связи с ним. Если graceful может занять до 5 секунд, то hard failover - до 20-ти. Разница происходит от того, что оставшемуся серверу надо во втором случае установить, что ранее активный признаков жизни не подаёт - ни напрямую, не через witness (см. ниже) - а в первом тот может сообщить и сам.

После того, как второй сервер решает тянуть одеяло на себя, события могут развиваться по следующим сценариям:

Внутри одной сети (VLAN, subnet):

Перенос кластерного IP - очень быстрая операция, прозрачная для клиента: сервер, забирающий его к себе, отсылает ряд пакетов Gratuitous ARP (GARP), прописывающих новый MAC-адрес для этого IP в ARP-таблице подключённых к той же сети раутеров или клиентов. Заодно и свитчи, физические или виртуальные, прописывают у себя, на каком порту сидит этот MAC. Всё, трафик продолжает течь как тёк.
Естественно, TCP-подключения при этом слетят - поскольку новый сервер вдруг получит какие-то TCP-пакеты без SYN-флага. Но поскольку при этом он отошлёт RST, клиент возобновит их сразу же, без попыток повторных передач.

Между разными сетями:

Естественно, перекинуть floating IP из сети в сеть нельзя, да и незачем. Решается эта задача вот как. Наш Failover Cluster в курсе о существовании двух сетей - по каждой на сервер. Мы можем привязать нашу кластерную "роль" к двум IP-адресам - по одному на сеть - и прописать между ними "OR dependancy". Иными словами, для того, чтобы "роль" запустилась на хосте, достаточно, чтобы один из этих адресов был доступен.

Затем мы прописываем оба адреса в DNS на одно и то же имя - к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
SMB-клиенты при запросе получают из DNS оба адреса и пытаются параллельно подключиться на оба. Лишь один из них ответит (в этом разница со случаем, если бы мы просто приписали к этому имени частные IP-адреса server1 и server2, о чём см. выше).

Если посреди работы с одним из адресов он перестаёт отвечать - Windows пытается в течении 15 секунд повторять (retransmit) запрос на уровне того же TCP-соединения - а затем посылает RST и снова пытается подключиться на оба одновременно. И если наш Failover Cluster отработал как положено - то соединение на второй адрес будет установлено благополучно.

В обоих вариантах, ещё пара секунд уйдёт на аутентификацию клиента против нового сервера - по Kerberos или NTLM.

Какой вариант быстрее? Это, как сказано выше, зависит от того, насколько быстро передадут друг другу роль наши сервера.
В случае graceful failover - переключить IP-адреса между двумя MAC-адресами в той же сети берёт намного меньше времени, чем дождаться, пока SMB-клиент начнёт стучаться по запасному адресу.
В случае же hard failover - наибольшую задержку обеспечит сам кластер.

Но и даже в наихудшем случае оба варианта всё же куда быстрее, чем переключение между двумя UNC-путями, полученными от DFS-N.


Ещё свидетель. Но совсем другой. И сугубо опциональный.

Есть некий способ сократить время, которое SMB-клиент тратит на переподключение. Идея в следующем: подключившись к кластеру по floating IP, клиент получает адреса всех узлов кластера. Затем он устанавливает соединение на один из неактивных узлов, и просит тот сообщить ему по дружески, если активный вдруг отвалится  - предполагается, что узлы кластера узнают об этом событии раньше, чем клиенты. Если клиент получит такую подсказку - то начнёт переподключение немедленно, не ожидая, пока существующее TCP-соединение вылетит по таймауту.

Называется этот узел-подсказчик опять таки witness - и он, хотя имеет прямое отношение к Failover Cluster и к shared folders, не имеет ничего общего со "свидетелем" для координации кластера, о котором рассказано выше. Спасибо Микрософту за этот гнусный бардак в терминологии.

Несмотря на то, что звучит эта идея интересно, у себя я это включать большого смысла не вижу, и вот почему:
  • в случае переключения между узлами кластера в той же сети IP-адрес остаётся тем же, и клиент будет пытаться переподключиться на него же. Если новый активный узел этот адрес ещё не подобрал - неважно, продолжает ли клиент стучаться по тому же TCP-соединению, или пытается установить новое, один чёрт он в ответ ничего не получит. Если же подобрал - то на попытку retransmission по старому соединению он моментально ответит сегментом RST, после чего клиент тут же откроет новое.
  • В случае hard failover между узлами в разных сетях, новому активному узлу возьмёт даже больше времени принять на себя роль, чем клиенту - переподключиться по запасному адресу.
Таким образом, единственный случай, где есть смысл в этом протоколе - это graceful failover в кластере, разнесённом по двум разным сетям. Но дело ещё и в том, что он не очень-то дружелюбен к файерволлам. Для того, чтобы он работал, нам надо дополнительно к SMB на floating IP позволить также RPC на него же, а так же RPC на все индивидуальные адреса кластерных узлов. Поскольку открывать при этом клиентам доступ на личные SMB-службы этих узлов не хочется, эта бодяга требует двух правил вместо одного:
  1. SMB на адрес fileserver role
  2. RPС на адрес fileserver role + адреса серверов
Во-первых, это усложняет конфигурацию, а во-вторых, открытие доступа к RPC само по себе является дополнительным риском. Поэтому овчинка, на мой взгляд, выделки не стоит.

Впрочем, насколько я понимаю, придуман witness protocol не столько для кластерного файлсервера общего назначения, о котором этот пост, сколько для Scale-Out File Server. А в рамках Scale-Out не существует floating IP - соединения устанавливаются на адреса самих серверов. В этом случае заранее знать, что такой-то адрес отвалился и стучаться к нему незачем, действительно имеет большой смысл. Впрочем, Scale-Out я тут не рассматриваю.

No comments:

Post a Comment