Наверх ▲

Построение облачного хранилища для хранения и доставки статического контента на основе интеграции Nginx и Openstack Swift

Станислав Богатырев Станислав Богатырев Ведущий системный администратор облачного хостинга виртуальных ресурсов и систем хранения данных Clodo.

Станислав Богатырев: Здравствуйте! Меня зовут Станислав Богатырев. Я представляю "облачный" хостинг Clodo. Рассказывать буду тоже об "облачном" хранилище для разных данных, в том числе бинарных. Мы недавно его запустили. Хочется поделиться некоторыми аспектами реализации и проблемами, с которыми мы встретились. 

Итак, любое решение должно создаваться для какой-то проблемы. Проблема "облака" в России и во всем мире – это маркетинг, который сформировал у людей неверные ожидания, подчас совершенно фантастические. 

Так, пользователь приходит в "облако" со своим сайтом, сделанном в обычной CMS с применением long stack, и надеется, что у него как по волшебству сразу появится возможность выдерживать любые нагрузки, обслуживать любое количество пользователей и не тратить при этом денег. К сожалению, это не так.

Технически "облако" представляет собой некие виды ресурсов, комбинируя которые разработчик может создавать свое решение. С такими типами "облачных" ресурсов, как процессор, память или жесткий диск все более или менее понятно (они знакомы рядовым пользователям по работе на стационарных компьютерах). С остальными типами ресурсов дело обстоит несколько сложнее. Пытаясь комбинировать уже знакомые типы, пользователи создают свои решения. Они получаются не самыми эффективными. Люди расстраиваются и разочаровываются в "облаках". Это не совсем то, чего нам бы хотелось.

К типам "облачных" ресурсов также можно отнести, например, ресурсы балансировки нагрузки и ресурсы доставки контента (думаю, эта тема близка всем). Это то, с чем сталкивается любой человек: передача картинок с сервера пользователям и так далее. 

Часто приходится наблюдать следующую картину: пользователь использует виртуальный сервер, зная, что его легко масштабировать вертикально. Человек берет огромный винчестер, складывает много мелких файлов в одну директорию, раздает их – и у него ничего не получается.

На этом слайде мы предлагаем более "заточенную" под использование "облака" архитектуру. На российских "облаках" ее нельзя было полностью реализовать, т.к. российские "облака" представляют собой три доступных для всех ресурса. Но зачем быть "как все"? Мы решили стать немного лучше и создали сервис для предоставления контента (Cloud Storage), для раздачи и хранения данных, и скоро введем балансировку. Таким образом, мы позволим пользователю уложить свое решение в такую схему.

Здесь нужны минимальные доработки привычной архитектуры. У нас здесь есть базы данных, знакомые всем. Как мы знаем, в классическом варианте LVM стека они не очень хорошо масштабируются горизонтально. Для них удобно брать небольшое количество вертикально масштабируемых серверов. Генераторы динамического контента могут быть абсолютно одинаковыми при том условии, что мы выносим всю статику, все файлы в "облачное" хранилище и раздаем их через некие внешние сервисы. Они незатратны по ресурсам, их по мере необходимости можно легко масштабировать горизонтально. Нагрузка между ними делится нашим внешним сервисом Load Balancing.

Таким образом, запросы от клиента на статику уходят на Cloud Storage, на раздачу. Раздача происходит быстро – засчет того, что это все кэшируется. Оборудование "заточено" именно под это: там быстрый канал, и так далее. На оплачиваемое время процессора, диска и памяти (традиционных сервисов) приходится лишь часть генерации динамики пользователя. Это существенная экономия. Несколько наших клиентов перешли на такую архитектуру. Увеличилось качество и скорость раздачи, а затраты уменьшились, как ни странно.

Какими характеристиками должно обладать хорошее "облачное" хранилище?

Когда мы создавали наше хранилище, мы думали о том, каким оно должно быть. 

• Во-первых, оно должно надежно хранить данные пользователей. Это очевидно. 

• Мы хотели, чтобы у нашего хранилища был простой, удобный, понятный API для управления данными. Так как сейчас большинством серверов пользуются не только люди, но и различные роботы, API должен быть адаптирован еще и к программам. 

• Основная функция, помимо хранения, – это быстрая раздача контента в больших количествах по протоколу HTTP.

• Также внезапно возникла следующая проблема: люди не готовы сразу взять и переписать свой сервис или сайт и перейти на другой вид доступа к данным. Поэтому на период миграции надо было сделать вариант удобного промежуточного доступа в файловую систему – возможно, с применением протокола FTP.

Мы остановили свой выбор на OpenStack Swift. Во-первых, это решение Open Source. Во-вторых, оно отвечало всем нашим требованиям, архитектурно соответствовало нашим задумкам. Вместо того чтобы изобретать свой велосипед, мы решили доработать существующий. 

В чем особенность Swift? На картинке показано, как он организован архитектурно. "Auth node" – это некий сервер авторизации, на который пользователь приходит при доступе через API (мы сейчас не говорим про HTTP-раздачу, мы говорим про работу по API). Он указывает логин, пароль и получает токен, по которому будут авторизоваться все его запросы. Также он получает URL-кластер, с которым ему необходимо работать. Дальше он идет на ноду прокси (Proxy node), через которую проходят все запросы. Нода прокси по хеш-кольцу находит сервер back-end, к которому ей надо обратиться. Посылает запрос, получает данные, возвращает пользователю. Все просто.  

Swift оперирует с тремя сущностями. Чтобы было понятно, чем это отличается от файлов: это объектное хранилище. Есть 3 типа сущностей. 

1. Первая, наиболее высокого уровня – это аккаунт. Это некий агрегатор контейнеров. 

2. Контейнер – это директория первого уровня в виде для пользователя, которая может иметь некие метаатрибуты. Права доступа, к примеру. 

3. Самая последняя и самая востребованная пользователями сущность – это объекты, которые привязаны к контейнерам. Объекты хранятся плоско. Нет никакой структуры директорий. Директория – это всего лишь иллюзия с именами.

В контейнере может находиться неограниченное количество объектов. Мы у нас был миллион объектов, разработчики Swift пробовали использовать 10 миллионов объектов. Каждый из типов объектов "завязан" на собственное хеш-кольцо. Работает все это достаточно быстро. При наполнении кластера предельным количеством объектов скорость практически не изменяется. В плане доступа все функционирует почти так же, как на пустом.

Что мы сделали сначала? Сначала мы взяли и развернули OpenStat и Swift так, как рекомендует сообщество разработчиков. Мы взяли серверы front-end, некоторые серверы back-end, и поставили их под управление решения Pacemaker. Мы любим все автоматизировать и экономить силы сотрудников.

На серверах back-end у нас были жесткие диски SATA с настроенной файловой системой XFS (мы слегка оптимизировали настройки). На серверах back-end мы развернули все сервисы хранилища Swift (объектные, аккаунтные, контейнерные). Плюс мы там поставили логирование и биллинг, потому что с пользователей нам нужно было брать деньги. Это тоже считалось серверах back-end.

На серверах front-end мы поставили Swift Proxy. Так как у нас каждый кластер должен быть надежным внутри себя, есть некий набор плавающих IP-адресов, которые с помощью Pacemaker перекидываются на "живые" серверы.

Мы "подняли" это решение, но, к сожалению, по тестам на front-end получили производительность всего лишь в 400 запросов в секунду, что весьма печально. Поэтому мы решили думать, что же нам делать дальше.

Первое, наиболее очевидное решение – это поставить NGINX вперед, перед Swift Proxy. Это сработало. Анализ запросов показал, что нигде никакое кэширование не применялось. Жизненный цикл запроса от момента отправки запроса клиентом до получения ответа все равно проходил до серверов back-end и обратно. Поэтому применение кэширования в каком-то виде было очевидным решением. 

На серверах front-end мы для кэша поставили диски SAS под ReiserFS. Нам не хотелось тратить емкость дисков на RAID, поэтому там просто добавлены диски. На каждый диск выделена отдельная кэш-зона. Для этого пришлось немного пропатчить NGINX, добавив для него поддержку нескольких кэшей. Спасибо Кириллу Коринфскому, он в этом помог.

В таком варианте по тестам мы получили уже 12 тысяч запросов на front-end. Это уже более приемлемое решение. Кластеров в дата-центре может быть много. На каждый кластер идет канал с агрегацией где-то по 4 гигабита на каждый front-end. В целом достаточно неплохая утилизация как по каналу, так и по процессору. 

При этом в первом случае мы "упирались" в ресурсы системы, поскольку Swift написан на Python и потребляет значительное количество ресурсов. Во втором случае у нас при максимальной нагрузке почти половина ресурсов относительно свободна, и мы уже знаем, для чего их использовать.

 

Когда мы ввели сервис в эксплуатацию, у пользователей начались проблемы. Во-первых, им очень не понравился формат ссылок, предоставляемых им после того, как они помещают данные в хранилища. 

Что есть что на верхней ссылке? cs1.clodo.ru – это кластер, к которому надо обратиться, чтобы получить данные. v1 – версия протокола, все это парсится сервером front-end. Потом у нас идет аккаунт пользователя. Дальше префиксом "public" обозначен контейнер. images/image01.gif – это имя объекта. Это не директория "images" с файлом изображения. 

Для решения проблемы мы снова задействовали NGINX. Мы начали выделять каждому пользователю по виртуальному HTTP-серверу (не по процессу, а именно по конфигурации) с его доменом "public". Это заработало. Дополнительно мы получили возможность контролировать параметры кэширования для данных каждого пользователя, а также задавать валидность, и так далее. 

Пользователи заявили, что им не нравится префикс "public". Например, если у них изображение расположено в контейнере "public", они хотят видеть в ссылке просто "images". Это мы тоже решили с помощью NGINX. Просто статистически посмотрели, куда чаще "тыкаются" пользователи. В 98 % случаев наши ожидания оправдывались, поэтому потери производительности на перебор мы не ощущаем. 

Пользователи рассказали нам еще об одной проблеме: «Я удалил, а все это видно». Очень много пользователей на это пожаловалось.

В чем, собственно говоря, проблема? Так как мы кэшируем ответы сервера на некоторое время, есть две ситуации. 

Первая. Пользователь посмотрел список файлов. Добавил файл, посмотрел список файлов и удивился: «Где мой файл?» Естественно, он там есть. Если он запросит сам файл, он его получит, но в списке он его не увидит. Пользователь расстраивается.

Вторая проблема более существенна. Пользователь случайно выложил свою неудачную фотографию в бане, а потом ее удалил. Фотография уже осела в кэше. Это, действительно, неприятно. 

Аналогичные сервисы, например, у RackSpace, связанные с внешней сетью CDN, отвечают: «Это ваша проблема. Через 24 часа фотография удалится из кэша». Но будет уже поздно. Так как среди наших клиентов, которым требуется этот сервис, есть периодические издания и банки фотографий, имеющие отношение к журналистике, им такая ситуация совсем не подходила. 

Поэтому нам пришлось решить и этот вопрос. У нас рядом с NginX появился демон, который управляет кэшем. Сначала мы пытались заняться программированием на файлах конфигурации NginX и написать логику очистки кэша внутри NginX. 

Сначала мы использовали множество if. Это было не самое удачное решение, и мы от него сразу отказались. Мы им пользовались не больше 5 минут. 

Потом мы попробовали встроенное решение Lua. Время, которое мы должны были тратить на обработку запроса, все равно тратилось на какие-то неинтересные вещи. Нам это не понравилось.

Теперь при обработке запроса в post action мы посылаем на FastCGI только сам запрос (англ. request) пользователя  без "body". Демон кэша хранит у себя эти запросы и оптимизированно вычищает нужный кэш. Так, например, если пользователь удалил файл images/party/banya/jpg, надо обновить кэш для директории "party", скинуть кэш по листингу. Если пользователь удалил файл, надо очистить кэш для этого файла. Притом не только на одном сервере front-end; естественно, демон чистит это на всех серверах front-end в ожидающем кластере. Это очень мило с его стороны.

Проект в продакшне уже около трех недель после выхода и тестирования. Он живой и развивается. У нас есть некие планы на будущее. 

Во-первых, мы собираемся сделать проект под названием «Все логи людям!». Он некоторым образом связан с биллингом, так как Swift предоставляет возможность биллинга с помощью анализа логов, но с небольшим допущением. Первый час мы не делаем ничего, до начала второго часа ждем, когда накопятся данные за прошлый час. В конце третьего часа мы их обрабатываем, биллингуем. Так как биллинг у нас везде посекундный либо поминутный, нам такое решение не подходило. Мы переписали биллинг, анализ логов и систему статистики для Swift. В качестве приятного бонуса мы получили возможность складывать логи раздачи пользователей на их же "облачные" хранилища для последующего анализа. Это, согласитесь, очень полезная штука.

Далее мы хотим все это оптимизировать для раздачи медийного контента, в частности, видео. Также мы хотим сделать репликацию между разными кластерами, желательно в разных дата-центрах. Новый релиз Swift позволяет это сделать (он вышел недавно, 22-го числа). При следующем обновлении мы сделаем такую репликацию. Плюс, предварительно посмотрев, как происходит эта репликация, мы поняли, что можно сделать и резервное копирование части хранилища непосредственно для пользователя. Это будет эффективнее по сравнению с вариантом, в котором пользователь сам будет выкачивать себе все свое содержимое. Во-первых, это не будет лишний раз нагружать наши серверы front-end. Во-вторых, это просто удобнее.

Дальше мы хотим сделать авторизацию по pubcookies и подписанным запросам. Сейчас у нас контейнер может быть либо частным (англ. private), и в этом случае он недоступен по HTTP без авторизации (будет возвращаться 403-я ошибка серверам front-end), либо он общедоступный, и из него идет раздача всего, что есть в контейнере. Промежуточного решение по сценарию вроде «я вошел и хочу отдавать часть фотобанка, например, фотографии в большом разрешении, только этому пользователю» пока не реализовано. Но с введением pubcookies мы этот вопрос тоже решим.

Сейчас мы хотим втянуть Swift Proxy в модуль NginX, так как текущий Swift Proxy на Python выполняет только поиск нужного сервера back-end, разбирая хеш-кольцо, просто смотрит, куда обратиться.

Дальше мы хотим доделать поддержку HTTP 1.1 через Keep Alive в Upstream, новой полезной возможностью NginX. Надо немного изменить загрузку данных в хранилище и добавить интеллекта демону кэша. Может быть, переписать его на чем-нибудь, кроме Perl, на котором он написан, просто для быстроты. 

Что у нас получилось в итоге?

Суммарная емкость на один кластер - это 840 терабайт SATA на сервере back-end. Быстрый кэш на серверах front-end, 7 терабайт SAS. 512 гигабайт памяти на кэширование самых горячих данных. Все это помещается в 30 единиц (англ. unit) в стеке.

Используем Debian. Дисков нет, везде образы Debian Live. Проверкой кластера занимается Pacemaker, он конфигурируется посредством Chef. Данные по конфигурации – это интеграция с нашей Clodo Panel. Через нее же идет и авторизация. Подобное решение можно использовать не только у нас, но и в частных "облаках". Собрать это все в домашних условиях реально, к тому же при этом реально не повторять наших ошибок. 

Собственно говоря, все. Я готов выслушать любые ваши вопросы.

Вопросы и ответы

Вопрос из зала: Здравствуйте! Спасибо за доклад. Вопрос: почему нужно было вообще инвалидировать кэши и не подходит ли под данный профиль использования список типа "relocation list" или "delaytion list"?

Станислав Богатырев: Во-первых, инвалидировать кэши необходимо по поведенческим причинам, которые я уже назначил. Также хотелось обойтись минимальными модификациями уже существующего решения. У нас уже был NginX, у нас уже был Swift. Все это работало. Вносить какие-то существенные изменения в эту логику просто не хотелось, поэтому мы завели отдельный демон.

Реплика из зала: Все равно вы на каждый запрос делаете дополнительный подзапрос на инвалидацию кэшей.

Станислав Богатырев: Нет, не совсем так. Демон делает это по-умному. Он сначала копит, долго думает и делает необходимый минимум запросов на очистку.

Вопрос из зала: То есть все равно есть какой-то промежуток между удалением и реальным исчезновением контента?

Станислав Богатырев: Да, есть. Более того, если мы начали удалять много-много, долго-долго, то этот зазор увеличивается. Конкретные алгоритмы инвалидации мы будем совершенствовать под нужды пользователей. Но мы стараемся не переходить некую грань разумности. Не всем нужна очистка кэша в реальном времени.

Вопрос из зала: А цифру можно озвучить? Через сколько исчезает фотография после удаления?

Станислав Богатырев: Если повезет, то почти мгновенно.

Реплика из зала: Спасибо.

Вопрос из зала: Вы сами говорили про волшебство "облака" и любимую CMS пользователя. У вас это, получается, невозможно, потому что нужно CMS дорабатывать?

Станислав Богатырев: Здесь есть представители «Битрикс», они уже все доработали.

Вопрос из зала: То есть вы ждете, пока разработчики большинства CMS подстроятся под ваше облако?

Станислав Богатырев: Не совсем так. У нас на GitHub выложен репозитарий Clodo corp., мы туда потихоньку выкладываем разные интересные вещи. Там есть написанный на Python FTP-сервер, который делает трансляцию из обычного FTP в наше "облако". Мы практически полностью совместимы с RackSpace API. Поэтому можно использовать все наработки мирового сообщества в этом области. Это клиент Fuse, это широчайший набор "оберток" практически для всех языков. Вариантов масса. Можно ничего не менять, просто по Fuse подмонтировать, и это будет работать.

Вопрос из зала: Скажите, пожалуйста, могу ли я как-то начать раздавать контент не по HTTP, а по другому протоколу? По FMS, например. У меня есть видеофайлы, и я хочу организовать их потоковую передачу пользователям. 

Станислав Богатырев: На данный момент такой возможности нет. Если вам очень хочется, мы поговорим об этом отдельно, напишите мне.

Вопрос из зала: Хорошо. Спасибо. Есть другой вопрос. Вы сказали, что биллинг устроен на стороне Swift. Какие запросы биллингуются? Именно запросы к хранилищу или запросы на отдачу? Запросы к NginX биллингуются?

Станислав Богатырев: Я, видимо, неясно выразился. Сама обработка идет на стороне сервера back-end. Биллингуется то, что обращается в NginX. Входящий трафик не биллингуется. Биллингуется то, что отправляется из нашего хранилища пользователю с удачными ответами.

Вопрос из зала: Скажите, пожалуйста. Вы используете OpenStack как "облачную" платформу. Правильно?

Станислав Богатырев: Нет. 

Вопрос из зала: Каким образом вы ее используете?

Станислав Богатырев: Мы используем только OpenStack Swift как основу для кластера нашего "облачного" хранения. У нас не OpenStack. У нас Zen, со своими доработками. Не OpenStack совсем.

Вопрос из зала: Здравствуйте. Существует ли у вас поддержка временных URL? Допустим, возможны ли случаи, когда какой-то приватный контент может быть отдан по ссылке, которая будет недействительна через 10-15 секунд?

Станислав Богатырев: Это есть у нас в планах вместе с pubcookies, благо для NginX уже есть замечательный модуль. 

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Олег Илларионов

Олег Илларионов

Разработчик социальной сети ВКонтакте.

Олег Илларионов (ВКонтакте) рассказывает об изменениях принципов работы с вебом и отвечает на вопросы о самой популярной российской соцсети.

Александр Кудымов

Александр Кудымов

Проектировщик интерфейсов, канбан-мастер.

Евгений Кобзев и Александр Кудымов (СКБ "Контур") объясняют, почему им стал тесен скрам и на какие грабли не стоит наступать.

Сергей Боченков

Сергей Боченков

Специалист по высокоскоростным технологиям.

Сергей Боченков и Александр Панков из Advaction рассказывают об http-демонах, работающих в ведущих проектах Рунета.