Главная » Хабрахабр » Операция на сердце: как мы переписывали основной компонент DLP-системы

Операция на сердце: как мы переписывали основной компонент DLP-системы

Переписывание legacy-кода как поход к стоматологу – вроде, все понимают, что надо бы пойти, но все равно прокрастинируют и стараются оттянуть неизбежное, потому что знают: будет больно. В нашем случае дела обстояли еще хуже: нам надо было переписать ключевую часть системы, и в силу внешних обстоятельств мы не могли заменять старые куски кода на новые по частям, только все сразу и целиком. И все это в условиях нехватки времени, ресурсов и документации, но с требованием руководства, что в результате «операции» ни один заказчик не должен пострадать.

Под катом история о том, как мы переписали основной компонент продукта с 17-летней историей (!) со Scheme на Clojure, и все сразу заработало как надо (ну, почти :)).

17 лет в «Дозоре»

Продукт Solar Dozor – DLP-система с очень долгой историей. Первая версия появилась еще в далеком 2001 году как относительно небольшой сервис фильтрации почтового трафика. За 17 лет продукт вырос до большого программного комплекса, который выполняет сбор, фильтрацию и анализ разнородной информации, курсирующей внутри организации, и защищает бизнес клиентов от внутренних угроз.

При разработке 6-й версии Solar Dozor мы решительным образом встряхнули продукт, выкинули из кода старые костыли и заменили их новыми, обновили интерфейс, пересмотрели функционал в сторону современных реалий – в общем, сделали продукт архитектурно и концептуально более целостным.

На тот момент под капотом обновленного Solar Dozor существовал огромный пласт монолитного legacy-кода – того самого сервиса фильтрации, который все эти 17 лет постепенно обрастал новым функционалом, воплощая как долгосрочные решения, так и сиюминутные бизнес-задачи, но сумел остаться в рамках изначальной архитектурной парадигмы.


Сервис фильтрации

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

Но вот духу тронуть огромный и древний системный сервис явно недоставало. Понимание того, что назрела необходимость в обновлении системы, появилось уже довольно давно.

Не пытаемся оттянуть неизбежное

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

Нельзя было выпиливать и переделывать функционал по частям, потому что все эти части востребованы заказчиками, и мы не могли «закрыть их на реконструкцию». При таком раскладе ни о какой поэтапной замене речи идти не могло. Только целиком, только сразу. Надо было аккуратно извлечь старый сервис и предоставить ему полнофункциональную замену.

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

Также предстояло побороться за то, чтобы уменьшить потребление ресурсов, сохранив (а в идеале – увеличив) текущий темп обработки.

Немного о начинке

На всем пути развития продукта команда Solar Dozor тяготела к функциональному подходу. Отсюда следует довольно нестандартный для зрелой индустрии выбор языков программирования. В разные этапы жизни системы это были Scheme, OCaml, Scala, Clojure, помимо традиционных С(++) и Java.

Как бы ни хотелось петь дифирамбы простоте и элегантности этого языка, нельзя не признать, что его развитие отвечает больше академическим интересам, нежели промышленным. Основной сервис фильтрации и другие сервисы, помогающие приему и передаче сообщений, были написаны и развивались на языке Scheme в различных его реализациях (последней использовалась Racket). Новый сервис также решено было реализовать на языке Clojure. Особенно заметно отставание в сравнении с другими, более современными сервисами Solar Dozor, которые разрабатываются в основном на Scala и Clojure.

Clojure?!

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

Clojure также является современным представителем семейства Lisp-языков, и перейти с одного Lisp на другой обычно довольно просто. Во-первых, не хотелось терять уникальный опыт команды, разрабатывающей на Scheme.

Важно и то, что Clojure функционирует на платформе JVM, а значит, можно использовать совместную базу с другими сервисам на Java и Scala, а также пользоваться многочисленным инструментарием для профилирования и отладки. Во-вторых, благодаря приверженности функциональным принципам и ряду уникальных архитектурных решений, Clojure обеспечивает беспрецедентную легкость манипулирования потоками данных.

Это обеспечивает легкость чтения чужого кода и облегчает передачу кода коллеге по команде. В-третьих, Clojure – краткий и выразительный язык.

Практически в любой ситуации, когда есть сомнения, можно просто создать прототип и продолжить дискуссию уже более предметно, с новыми данными. Наконец, мы ценим Clojure за легкость прототипирования и так называемую REPL-ориентированную разработку. REPL-ориентированная разработка дает быструю отдачу, ведь для проверки работоспособности функции не надо не то что перекомпилировать программу, но даже перезапускать ее (даже если программа — сервис, расположенный на удаленном сервере).

Забегая вперед, могу сказать: считаю, что мы не прогадали с выбором.

Собираем функционал по крупицам

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

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

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


Процесс фильтрации сообщения

Документации недостаточно

Каков на самом деле функционал системы? Ответ на этот вопрос дают различные описания, такие как пользовательская документация, руководства и архитектурные документы, отражающие структуру сервиса в различных аспектах. Но когда доходит до дела, прекрасно понимаешь, насколько расходятся представления и реальность, сколько нюансов и неучтенных возможностей содержит старый код.

Берегите свой код! Хочу обратиться ко всем разработчикам. Не полагайтесь на документацию. Это самое главное ваше достояние. Доверяйте лишь исходным текстам.

Главное – привыкнуть к некоторым отдельным формам, несущим в себе легкий налет Lisp-архаики. К счастью для нас, код на Scheme, благодаря самой природе языка, созданного для обучения программированию, довольно легко читать даже неподготовленному человеку.

Выстраиваем процесс

Объем работы был колоссальный, а команда весьма небольшая. Так что не обошлось без организационных трудностей. Рабочий поток багов и запросов на исправление (и небольшие доработки) старого сервиса фильтрации и не думал останавливаться. Разработчикам регулярно приходилось отвлекаться на эти задачи.

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

Будучи центральным компонентом, сервис фильтрации использует многочисленные сервисы распаковки и анализа содержимого (текстов, изображений, цифровых отпечатков и т.п.). Другим фактором, который добавил немало хлопот, стали внешние зависимости сервиса. В процессе разработки пришлось также переписать некоторые компоненты на современный лад (а некоторые и на современный язык). Работа с ними частично ориентировалась на старые архитектурные решения.

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

Начинаем разработку

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

Так, например, документ Word может содержать в себе не только текст, но и изображения, встроенный документ Excel, OLE-объекты и еще много чего интересного. Тут надо пояснить, что под распаковкой понимается рекурсивный процесс получения из файла частей и извлечение из них полезной информации.

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

Еще комплимент в сторону Clojure: мы получили рабочий прототип, в котором обозначили контуры будущего функционала, в кратчайшие сроки.

DSL для политики

Вторым этапом стало добавление проверки сообщения с помощью политик фильтрации.

Он получил название MFLang. Для описания политик был создан специальный DSL – простой язык без излишеств, который позволил в более-менее человекочитаемом виде представить правила и условия политики.

Скрипт на MFLang «на лету» интерпретируется в Clojure-код, кэширует результаты проверок над сообщением, ведет подробный лог работы (и, откровенно говоря, заслуживает отдельной статьи).

Долой копания в БД или в экспортном формате! Использование DSL пришлось по душе тестировщикам. Также появилась возможность получить детализированный лог проверки сообщения, из которого понятно, какие данные брались для проверки и какие результаты вернули функции сравнения. Теперь можно было просто прислать сгенерированное правило для проверки, и сразу становилось ясно, какие условия проверялись.

Можно с уверенностью сказать, что MFLang оказался совершенно неоценимым подспорьем для отладки функционала.

В полную силу

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

Главный вопрос, конечно же, состоял в том, насколько реализованный функционал соответствует ожидаемому и насколько полно он его реализует.

Замечу, что если необходимость модульного тестирования уже давно не подвергается сомнению (хотя сами практики TDD до сих пор вызывают оживленные споры), то внедрение автоматизированного тестирования системного функционала зачастую наталкивается на открытое сопротивление.

Но сам процесс их создания сопряжен с рядом трудностей — сбором необходимых данных, определением интересующих показателей и вариантов тестирования. Разработка автотестов помогает всем членам команды лучше понять процесс работы продукта, экономит силы на регресс, вселяет определенную уверенность в работоспособности продукта. Программисты неизбежно воспринимают создание автотестов как необязательную, побочную работу, от которой по возможности лучше увильнуть.

Но если удается преодолеть сопротивление, создается довольно прочный фундамент, позволяющий выстраивать представление о работоспособности системы.

Проводим замену

И вот наступил важный момент: мы включили сервис в комплект поставки. Пока вместе со старым. Таким образом можно было одной командой провести смену версии и сравнить поведение сервисов.

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

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

И только когда начали поступать вопросы от команды внедрения, наступило понимание – сервис, над которым мы так долго трудились, уже стоит на площадках и… работает!

Хей! Конечно, были и баги, и небольшие доработки, тем не менее через месяц активного использования у заказчиков был вынесен вердикт: внедрение продукта с новой версией сервиса фильтрации вызвало меньше проблем, чем внедрение предыдущих версий. Похоже, мы справились!

В итоге

Разработка нового сервиса фильтрации заняла примерно полтора года. Дольше, чем предполагалось изначально, но не критично, тем более, что фактическая трудоемкость работ совпала с первоначальной оценкой. Гораздо важнее, что нам удалось оправдать ожидания руководства и заказчиков и заложить основы для будущих доработок продукта. Уже в текущем состоянии видно значительное снижение потребления ресурсов – при том, что в продукте еще есть широкие возможности для оптимизации.

Могу добавить немного личных впечатлений.

Впервые за долгое время появляется уверенность в том, что контроль над продуктом возвращается в наши руки. Замена центрального компонента с долгой историей – глоток свежего воздуха для разработки.

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

Вначале были опасения, связанные с динамической природой языка, скоростью и устойчивостью к ошибкам. Для нас это был первый опыт разработки такого масштабного проекта на Clojure. К счастью, они не оправдались.

Остается лишь пожелать, чтобы новый компонент проработал так же долго и успешно, как его предшественник.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Как программист новую машину подбирал

В предыдущих статьях (I, II, III) я подробно рассказывал о разработке сервиса для поиска выгодных б/у автомобилей в РФ. В крупных городах существует огромное количество официальных дилеров, по крайней мере для популярных брендов. Поездив продолжительное время на различных б/у машинах, ...

ReactOS 0.4.10 — теперь со вкусом BTRFS и весёлыми иконками для папок

Привет, Хабр! И какой! Прошло всего каких-то пять месяцев со дня прошлого релиза ReactOS, а мы уже выкатываем новый! Как обычно, у нас для вас фирменный коктейль из киллер-фич и адового тюнинга незначительных компонентов системы. В этом выпуске — внедрение ...