Есть у нас в Яндексе кластер, на котором живёт много проектов на Питоне. Большие, маленькие — около двух десятков. И все они пользуются общими библиотеками. Не отдельными копиями одних и тех же библиотек, а именно общими файлами, лежащими в системных каталогах. Периодически эта система вызывает вопросы и сомнения, и мы обсуждаем внутри, как бы её можно было поменять. Но на этот раз мне захотелось получить ещё и мнение извне. Потому что глупо думать, что в одной компании собрались все самые умные люди в мире…
Проблема (как бы)
На самом деле, проблемы пока никакой нет. Но есть её предчувствие.
Время от времени мы обновляем самую большую и сложную библиотеку кластера — Django. Все публичные изменения версий Django до сего момента считаются обратно совместимыми. Но не для нас, потому что мы используем её за пределами формально документированных интерфейсов и не стесняемся использовать любой код, на котором можно построить что-то полезное.
Вот, например, буквально вчера перед отпуском меня застало письмо, где разработчик Афиши советовался, как теперь определять кастомные FilterSpec'и для админки…
Поэтому любой апгрейд Django у нас требует по крайней мере тестирования, а иногда и переписывания небольших кусков кода под изменения. Плюс к этому — использование новых фич с удалением устаревших воркараундов, написанных в их отсутствии.
А поскольку мы живём с одной общей копией библиотеки, то рано или поздно происходит момент, когда надо обновлять весь кластер одновременно. Здесь появляются задержки из-за того, что какой-то один проект, которому "не повезло" быть сильно зависимым от изменений, делает всё дольше остальных, а остальные его ждут, потому что уже завязались на новый код Django, и вынуждены откладывать выкладку в продакшн. Пока что это происходит не так уж накладно, но, повторюсь, есть ощущение, что это может перерасти в проблему в будущем.
Виртуалки
Вариант решения проблемы, который первым приходит в голову — виртуальные среды (virtualenv или виртуальные машины — не важно). Выгоды, казалось бы, налицо: никому никого не надо ждать, все проекты могут делать внутри своих сред что угодно. Но есть и минусы:
-
Апгрейды библиотек в виртуальных машинах будет делать каждая команда в разное время. При возникновении трудностей, все команды будут должны решать их сами и по отдельности, а не все вместе и один раз.
-
У команд не будет инициативы апгрейдиться, пока всё "и так работает", чтобы не отвлекаться от своих прикладных задач. Поэтому они не будут получать бесплатной выгоды, которая бывает от апгрейда общей библиотеки на более эффективную версию.
-
Месиво версий труднее поддерживать админам, которые у нас общие на весь кластер.
-
Локальные патчи к библиотекам придётся делать и поддерживать для всех версий, которые существуют в компании.
-
У разработчиков пропадает инициатива распространять свои внутрипроектные удачные решения в другие проекты, потому что когда у всех своя уникальная среда, мало гарантий, что общая библиотека заработает сразу и легко.
Мировое сообщество
Впрочем, всё вышенаписанное не так страшно. Как удачно выразился один наш разработчик:
Практически любые инфраструктурные ограничения преодолимы, если рассматривать их как задачи, которые надо решить.
Например, если расширить точку зрения и рассмотреть в качестве своебразного Большого Продакшн-Кластера весь питоний мир, то будет видно, что проекты, живущие каждый в своей среде, так или иначе решают проблемы апгрейда библиотек. Мало, думаю, найдётся живых проектов, работающих на Django трёхлетней давности. Люди переходят на новые версии сами, публикуют собственные решения в виде библиотек и, в общем и целом, держат нос по ветру. В результате, мы все, в общем-то, программируем примерно на одном и том же языке, примерно с одними и теми же средствами. Без всякой навязанной извне синхронизации по времени.
То же самое можно (и даже довольно просто) сымитировать и в рамках одной компании. Это потребует каких-то инфраструктурных переделок, но — см. цитату выше.
Ключевое отличие
Мне, кажется, удалось сформулировать, основное отличие того, что мы делаем сейчас в Яндексе и того, как живёт мир. Дело в том, что веб-сервисы, даже если взять только написанные на Питоне и Django, очень разные. Ходя по вебу, я не могу просто по взгляду на сайт сказать, что он написан на Django. А вот про яндексовые сайты я вполне могу сказать, что они сделаны в Яндексе. В них много чего происходит одинаково. От логотипа и меню сверху шапки до шрифтов и шорткатов Ctrl+→ для листания страниц.
Так вот, выбор между виртуалками и общей средой по сути сводится к тому, насколько сильно мы хотим контролировать "яндексовость" питоньих проектов. Это, опять-таки, не однозначный выбор. Возможно, расселив проекты в виртуалки, мы потеряем в "яндексовости" не так сильно, чтобы это было заметно. Или, например, сможем на это влиять не с помощью библиотек, а с помощью, например, набора тестов перед выкладкой в продакшн, которые будут гарантировать, что проект соответствует каким-то ожиданиям.
В общем, я не знаю пока, что мы будем делать. Если кому есть, что сказать — буду очень признателен.
Комментарии: 21
Только у меня есть парочку, на версии еще 0.97, с приличной посещаемостью и довольно известные. И регулярно дорабатываемые. Обновляться смысла большого нет, т.к. этот труд представляется куда большим по сравнению теми плюшками которые будут в новой версии. Кроме этих есть еще проект на котором обновляем джану с каждым релизом, но например уже на 1.3 не вижу большого смысла переходить, т.к. появились обратные несовместимости в filefield и imagefield при удалении. И в будущем возможно откажемся от обновлений в принципе, потому в джанго уже давно есть все что нужно.
Извиняюсь, что не по теме вопроса)
Если есть возможность, то я использую одну общую библиотеку для всех проектов (более 10). Но когда в Django какие-то большие изменения, например, в 1.3 csrf_token - стал обязательным, а я его нигде не использовал, то обновление всех форм может занять долгое время, которого нет.
Тогда в рамках текущий итерации я правлю проект за проектом переводя их на версию 1.3 с помощью ln -s /path/to/django-1.3 django
и когда все готово, происходит глобальное обновление, а символические ссылки удаляются.
Чем плох компромиссный вариант? Можно же сочетать общее с частным. Общие библиотеки лежат, допустим, в /usr/local/lib/yandex/python, а код проектов в /usr/local/src/projname. Для проекта projname используется PYTHONPATH=/usr/local/lib/projname:/usr/local/lib/yandex/python. При этом, допустим, в точке 0 по времени все проекты синхронизировали внешние зависимости, поэтому локальная папка библиотек пустая. import django будет импортировать из общих. Если настала пора для всех обновляться, однако, какой-то один проект еще требует старую джангу - для него в /usr/local/lib/projname заливается нужная версия, для всех остальных ничего не меняется - можно обновлять общие зависимости. Когда код projname актуализирован, этот "форк" можно "смержить" удалив старую версию из локальной директории зависимостей.
По-моему перечисленные проблемы чисто теоретические и на практике либо не возникают, либо легко решаются. У каждого проекта должна быть своя среда, а переход на новые версии библиотек можно делать административно, типа "через два месяца джанго 1.1 использовать не будем нигде".
У меня проекты живут под buildout, поэтому даже два проекта на одном сервере могут использовать разные версии Django и других библиотек. А по желанию можно заставить использовать и общую версию.
buildout вообще решает много проблем, но он довольно глючный (что странно для такого старого и распространенного продукта).
А разве нельзя держать установленными несколько версий джанги, и выбирать нужную для данного проекта с помощью манипуляций с питоновскими путями? Виртуальные машины тут — явный оверкил
Достаточно первой команде описать возникшие трудности в вики.
И это хорошо. Зачем тратить время на апгрейд, если он не нужен? Тем более, если в данный момент, например, совсем не до того.
А вот тут я наверное что-то пропустил. В чем заключается поддержка библиотек админами? Они их допиливают? Или тестируют? Или что?
Наверное все-таки не ко всем версиям, а только к тем, которые используются командами, которым эти патчи нужны. А если кому-то другому этот патч не нужет, его версия библиотеки будет непропатченной.
+1
А это вообще вопрос дизайна, а не программирования :)
Как я понимаю проблема не столько технического сколько эмоционального плана. Можно даже сказать, что это вопрос привычки и желания, что бы все проекты шли строем, нога в ногу. Часто подобные проблемы решаются с течением времени - вы просто привыкните к новому положению.
Хотя тут есть тонкий момент - "корпоративный дух", который обязывает быть как все и носить одинаковые галстуки. Но как и в случае со стилем одежды ваша проблема может решаться простым приказом по организации - "регулярно обновлять проекты или выговор с занесением в грудную клетку".
С технической стороны можно использовать много разных стимулов: выводить грозные deprecation warnings, при прогоне тестов сообщать о появлении новых версий пакетов, при выходе новых пакетов автоматически прогонять тесты всех проектов с использованием новой версии, создавать Know a Good Set (KGS) версий пакетов, дублировать все сообщения на почту ответственных лиц.
Полностью согласен. Кромо того, используя vitualenv, вы можете переводить проекты на собственные версии библиотек постепенно, пока остальные могут жить с общими библиотеками
Административное решение работать не будет. Произойдёт то, что и должно, при очередном обновлении где-то что-то отвалится, придется откатиться назад, а это огромная проблема на головы всех и бездарная трата времени. Это не говоря уже о том, есть готовые проекты, выделение ресурсов на обновление которых само по себе есть пустая трата времени.
Проблем из раздела "виртуалки" просто не существует, потому что основное преимущество неизолированных вирутальных сред в том, что они могут использовать как общесистемные библиотеки, так и конкретные версии совместно. Скажем, есть общесистемная джанга, и если какой-то проект чувствует, что нужно проапгрейдиться он просто в своей виртуалке обновляет джангу. Когда же все проекты перешли на новую версию, она обновляется общесистемно.
Достаточно недолго поадминить и поразрабатывать одновременно на одном сервере несколько проектов, чтобы понять, что у виртуальных сред нет альтернатив.
Соглашусь с предыдущим комментатором в вопросе оптимальности административных решений.
В качестве виртуальной среды использую zc.buildout(если не ошибаюсь, аналог bundler в ruby), который мне показался удобным и универсальным чем virtualenv,в плане рутины при разворачивании окружения проекта.
А если такой вариант?
Разделить библиотеки на два вида - "популярные" (которые используются в большом количестве проектов) и "экзотические". Для популярных библиотек существует "сборка", в ней лежат конкретные версии всех популярных библиотек. Эта сборка лежит в виде общих файлов, предназначенных для совместного использования разными проектами. А "экзотические" библиотеки лежат в каждом проекте локально.
Сборок может быть несколько. Регулярно (скажем, раз в месяц) появляется новая сборка, но старые тоже остаются. Если проект хочет использовать свежие версии библиотек, он переходит к более свежей сборке.
Преимуществом (по сравнению с виртуалками) может являться то, что количество "разнобоя" уменьшается - устраняется "комбинаторный взрыв" сочетания всех свежих версий для всех библиотек. При этом админы применяют локальные патчи к конкретным сборкам, и в случае желания перейти на более свежую сборку разработчики проекта могут быть спокойны, что админы уже позаботились и применили все необходимые патчи.
По сравнению с текущим вариантом преимуществом может бють то, что если конкретному проекту не нравится самая свежая сборка (скажем, она в нем что-то ломает), то он может какое-то время продолжать пользоваться старой сборкой, пока админы не напишут патч и не решат проблему.
Может просто типизировать проекты. Разложить по сходности задач, собрав в пул для простоты обновления. Создать виртуальную среду для каждого пула, админить будет проще ибо виртуализации будет меньше, команды будут работать слаженней в пределах одного пула, проще будет контролировать общий общий апгрейд систем. Нужно будет только поработать над тем, что бы пулы не разваливались на свои задачи и работали совместно с другими. Хотя, наверно, я играю в КО.
Но ведь эта самая «яндексовость» никуда не девается если брать не только питон-проекты, но и написанные на других ЯП.
(Лишь предпологаю): возможно на админах лежит задача деплоя девелоперских версий проектов на продакшен? Как-то уж очень сомнительно (IMHO), что каждый разработчик или команда в Я может «хаотично пушить» всё и вся в рабочий проект.
Согласен с:
По моему, на первом этапе можно завернуть всё в пустые виртуальные окружения. В результате проекты продолжают брать глобальные либы. Дальше, когда у Вас назначен день Х - "надо обновить джанго до 1.3" - не нужно ждать всем, пока подтянуться отстающие. Просто им ставится локально старая версия, а глобально обновляется до новой. Потом, когда разработчики откорректируют то что нужно, в соответствии с новой джангой - просто снести из локального.
Думаю, для этого можно даже придумать какой-то простой автоматизирующий механизм, всё равно же у Вас должны быть какие-то requirements.txt - в которых просто прописана джанго с конкретной версией или без нее - если готова к обновлению. И какой-то скрипт, который перед обновлением той же джанги, проверяет какие проекты не готовы к обновлению, и соответственно им до устанавливает. И соответствующий механизм возвращения к работе с глобальной джангой.
Много уже понаписали...
Мне кажется, что главная проблема виртуалок (окружений или машин, не важно) в том, что не будет мотивации обновляться. На предыдущей своей работе, я замучился объяснять, что переход версии питона с 2.5 на 2.6, это хорошо. Уже 2.7 вышел, а им всё лень было ченджлоги почитать. То же касалось и версий джанги.
Централизованный подход, как мне кажется, должен заставить не только вовремя обновлятся, но и думать, прежде чем делать какой-нить совсем недокуменнтированный хак, чтобы не переписывать его в будущем.
Вариант с частичным разделением окружений с виду хорош. Но я боюсь, что велика вероятность его эволюционирования в полностью независимые окружения. Т.е. нужно будет следить за разработчиками и пинать тех, кто не обновляется. Для этих целей можно даже небольшую программку написать, чтобы та следила за окружениями и говорила о тех, у кого больше всего не обновлённых пакетов.
Иван, по мне так недостаточно данных. Расскажите какова процедура обновления и основных библиотек, и рабочих проектов. Ещё хорошо бы знать что распространяется в виде пакетов, а что просто лежит в файловой системе.
Оба подхода имеют право на существование. Выбор конкретного определяется наибольшей эффективностью / наименьшей стоимостью сопровождения в каждом конкретном случае. И, конечно, готовностью/мотивированностью всех участников регулярно слегка переписывать свой код под новую версию общей библиотеки, равно как и готовностью всей компании к возможным проблемам несовместимости.
Может иметь смысл и компромиссный вариант: держать несколько версий из строго определённого на каждый конкретный момент времени диапазона, единого для всех разработчиков компании. Наименее расторопным это даст возможность перейти на свежую версию позже, не тормозя при этом более прогрессивных. Регулярное же обновление набора поддерживаемых версий (постоянное движение поддерживаемой «полосы» версий вверх от старых версий к свежим) гарантирует, что не будет стагнации.
Мне кажется в посте обозначено несколько проблем, с порога речь поднимается о разделении сред, но это только часть решения.
Я, ради решения схожих проблем, работаю со средой, разделенной на несколько уровней:
Django, прочие ключевые библиотеки вроде south, admin-tools и т.д., как они есть из репозиториев.
Уровень оберток. Суть - туда складываются наборы оверрайженых классов джанго и ключевых библиотек с улучшенным функционалом или фиксами которые, сохраняют или незначительно расширяют родительский интерфейс и наращивают на родительскую версию некоторый функционал, ну, например, filefield с опциональным удалением файла или query умеющий кэшить результаты в memcached. Существенная часть хаков окажется именно там. Удобно привязать к этому слою отдельную документацию.
Уровень для reusable проектов, виджетов и прочего барахла. Тут у вас, по идее, должна жить яндексовость. Опять же, покрывается отдельным слоем документации и тестов.
Дальше начинается непосредственно проект:
Форки приложений, которые только с этим проектом, либо сильно под него модифицированны, пантеон таких форков только захламит общую среду и увеличит шанс, случайно скопировав код из проекта в проект, потянуть за ним и форк.
Сам проект.
Конфигурации (с ними отдельная сложность, так как они тоже могут зависеть от библиотек)
Все это поддерживается рядом конвенций: перфиксы у интерфейсов оберток, и обозначение применения хаков на уровне документации методов или классов на уровне проекта, так выглядит комментарий к чужому коду, которому я не до конца доверяю:
А так к базозависимому хаку:
Если в новой среде что-то не заводится оно по исправлению получит приписку !version-req django [новая версия]
Можно сделать навеску на автодок, чтобы выделять подобные метаполя, но я пока особой необходимости в этом не нашел, как и превращать в полноценный механизм отслеживания зависимостей. В случае создания reusable приложений
Процесс подготовки новой версии среды будет состоять из сборки библиотек, тестирования оберток и слоя reusable-приложений после них. После этого производится миграция нужных приложений и их тестирование.
Полная централизованность обновлений и одна среда в данном случае, имхо, зло, так как у проектов, скорее всего, разные стадии и итерации цикла разработки, и нет смысла просто ради идеи гомогенной среды, тестировать и править вообще все хозяйство, только потому что в одном из приложений что-то удобнее сделать именно с новой версией библиотеки.
Стимуляция перехода, если она вообще необходима, это уже вопрос организационный, можно обязать разработчиков осуществлять переход на новую среду сразу после того, как их подпроект доведен до stable релиза, и очевидно, что большинство всплывших на тестировании с новой средой багов связано именно с миграцией.
Да, админы раскатывают пакеты по продакшену. А если что-то ломается в коде - это головная боль разработчиков. Поэтому аргумент "больше работы админам" остается непонятным :)
Всё в deb-пакетах.