Докер контейнеры и прикладная некромантия

img_7.png

Оригинал статьи на хабре

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

Будет два живых примера:

  1. Медиавики, вики-движок на PHP, древняя инсталляция - обновляем с версии 1.24 от сентября 2014 года.
  2. Забавный инструмент для генерации комиксов в стиле xkcd, написанный на Node.JS, и в последний раз обновлявшийся в марте 2013 года. Поднимаем и исследуем. Будут внезапные сюжетные повороты, и трагическая концовка.

Интересно, как воскресить эти артефакты древности? Тогда погнали!

История первая. Mediawiki

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

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

Since Version 1.36, MediaWiki only commits to supporting upgrades from two LTS releases ago (see phab:T259771). Upgrades from older versions of MediaWiki will have to be performed in multiple steps. This means that if you want to upgrade to 1.41 from 1.34 or earlier, you’ll first have to upgrade your 1.34 wiki to 1.35 (or 1.39), and, from 1.35 (or 1.39), you’ll be able to upgrade to 1.41.

То бишь

С версии 1.36, MediaWiki поддерживает обновления только с двух прошлых LTS релизов. Обновления с более старых версий MediaWiki нужно делать в несколько этапов. То есть, если вы хотите обновиться до 1.41 с версии 1.34 или более ранней, то вам сначала нужно будет обновить версию 1.34 до 1.35 (или 1.39), а потом с 1.35 (или 1.39) вы сможете обновиться до to 1.41.

Затем я ещё увидел диаграмму совместимости для PHP:

img.png

И для MySQL:

img_1.png

Да, прям с наскоку не получится. Прикинув положение звёзд, я решил, что мне должно хватить Mysql 5.7 для всех версий (как оказалось, не ошибся), а вот PHP лучше обновлять в несколько этапов. В результате у меня получилось три этапа обновления:

  1. Текущая версия Mediawiki 1.24 и PHP 5.6
  2. Промежуточная версия 1.35 и PHP 7.4.3
  3. Целевая, последняя на момент написания поста версия 1.41 и PHP 8.1

Возможно, вы спросите, а зачем первым этапом стоит моя же версия? Ответ такой - на момент начала этого развлечения проект был развёрнут на каком-то позабытом богом сервере непонятной и древней конфигурации, и сначала нужно было быстро унести его себе и убедиться, что ничего не упало и работает в точности так же.

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

Подводные камни

Ну дальше вроде бы всё просто - берём подготовленные PHP контейнеры, копируем код, запускам обновления, ротируем контейнеры.

Но… Не так-то быстро. Ведь mediawiki кроме самого PHP требует ещё некоторого количества различных PHP расширений и системных библиотек. И подобрать такой образ, в котором всё это уже будет, у меня не получилось.

Почему я не использовал готовые контейнеры, которые предоставляются медиавики? На этом месте должен был быть ответ, что там есть образы только с версии 1.39:

img_2.png

Но… В момент написания статьи я заметил, что в ридми медиавики указаны не все теги, которые есть фактически. И если провалиться в них на докерхабе, то там есть версии от 1.29.0… Поэтому правильный ответ на вопрос “почему я не использовал готовые образы от медиавики”- потому что я не умею пользоваться докер хабом :( Надеюсь, вы не повторите мою ошибку, однако продолжу историю. Не потому что надо делать именно так, а потому что это скорее общая история, которая будет применима к любому проекту, в том числе к тому, где за вас никто контейнеры не приготовил. Да и не факт, что эти образы рабочие и расширяемые, я их не пробовал.

Итак, далее я решил по рабочей привычке собирать контейнер на дебиане. И тут меня ждал облом - на новых версиях дебиана нужных мне пакетов уже нет, а репозитории старых версий уже умерли…

Долго я пытался решить эту проблему, искал готовые контейнеры, и ничего не подходило. Но внезапно оказалось, что Alpine Linux поддерживает всё нужное мне старьё! Казалось бы - вот оно - просто меняй версию пакета PHP и собирай контейнеры…

Однако, и тут оказались подводные камни, ведь для разных версий PHP расширения ставятся немного по-разному.

Счастливый финал?

В общем, потратив какое-то время на подбор пакетов и способа их установки, я смог таки собрать нужные версии. Более того - всё взлетело сразу и почти без ошибок! И даже обновления базы на несколько гигабайтов прошли успешно. Здесь моё почтение разработчикам медиавики - не так просто сохранять совместимость десятилетями. Хотя во всём остальном у меня скорее сложилось ощущение, что я вернулся на 10 лет назад - способы работы с расширениями, скинами, и взаимодействие с разработчиками просто ужасное.

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

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

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

История вторая. cmx.js

Когда-то существовало веб-приложение cmx.io, и там можно было при помощи мыши, HTML и такой-то матери нарисовать комикс в стиле xkcd. Об этом даже есть статья на wired.

Например, у моего коллеги по хобби Леонида когда-то из вот такой разметки рендерился такой комикс:

img_3.png

Но ничто не вечно под луной, сайт благополучно умер, исходники не собираются, и Леонид спросил, кто бы мог это поднять. Видимо, он хочет написать ещё статью на хабр - дело святое, как не помочь. Тем более, что приложение на Node.JS - там небось дел-то на пять минут, подобрать нужную версию ноды - и поехали. Но…

Подводные камни

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

  • Зависимости, которые не собирались под Node > 8
  • Код, сгенерированный yeoman, древним генератором бойлерплейта для веб приложений
  • Grunt в качестве таск раннера
  • CoffeeScript! Матерь божья!
  • Bootstrap 2.2.2
  • Bower, который ставит requirejs, modernizr и jquery версии 1.91
  • Какие-то скрипты на руби и зависимости от его гемов

Ух! Звучит как стюардесса, которую не надо откапывать. Однако, почему бы не сделать это для рисовалки комиксов? Опять же…

img_4.png

Так что я решил попробовать воскресить этот проект.

Успех?

Удивительно, но bower репозитории всё ещё работают, и мне удалось собрать работающий контейнер, в котором ставились и bower зависимости и ruby и нужный gem. Код, написанный 11 лет назад, снова работает!

Успех!

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

  • Оказалось, что руби гем нужен для компиляции Sass… Всё бы ничего, но только в этом проекте Sass нигде не используется. Выкинул.
  • Оказалось, что нерабочая зависимость нужна для пережималки джипегов… Которая тоже не используется. Выкинул.
  • И есть зависимости для тестов, включающие в себя неработающий, но очень толстый PhantomJS. А вот самих тестов в проекте нет. Выкинул.
  • А зависимости с bower… Скопированы в сам репозиторий. И тоже не используются. Выкинул.

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

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

На самом деле, проблема вовсе не в ноде, не в npm и не в маленьких зависимостях. А вовсе наоборот. Но для того, чтобы это заметить, надо посмотреть на количество соавторов проекта и на количество его пользователей. Количество соавторов, помимо автора… Один… Количество пользователей мы можем подсчитать лишь примерно, исходя из того, что на гитхабе опубликовано порядка 290 гистов, экспортированных из этого проекта. Предположим, что:

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

Итого, по оценке научным методом трёх П (пол, палец, потолок) выходит, что у проекта было порядка 29.000 пользователей. И напомню - один соавтор… Будь там ещё хотя бы 2-3 разработчика, они бы наверняка заметили косяки и отрезали всё лишнее. И своим комиксом-примером автор показывал, что он ждёт соавторов:

img_5.png

Но… Ожидания оказались тщетны, предполагаемые им человеко-часы не были потрачены, и 792 звёздочки и 89 форков привели всего лишь одного разработчика… Здесь важно задать вопрос - почему? А ответ на него довольно прост. Размера и сложность проекта обратно пропорционален желанию разработчика разобраться и принести пользу. Потому что чем больше контекста - тем больше времени он потратит на то, чтобы вникнуть.

Именно поэтому важны маленькие проекты и маленькие пакеты - в которые можно прийти и принести пользу за час, а не за неделю непрерывной работы. А ещё важно уважение и культура пожертвований. Которые тоже работают примерно никак - как на моём личном опыте, так и например на печальном примере крайне уважаемого мною Дениса Пушкарёва. Так что, на текущем этапе развития культуры open source, маленькие кирпичики - это лучший способ облегчить работу разработчикам, и дать их хоть сколько-то соавторов.

Так что проблема лишних зависимостей и неиспользуемого бойлерплейта - это не проблема конкретного автора, ноды, NPM или какой-то другой технологии. Эта проблема - мы, разработчики, которые используют open source решения, и относятся к ним крайне потребительски.

Так зачем тут докер?

Может возникнуть закономерный вопрос - а что мы вообще говорим о докере, если в итоге оказалось, что можно просто отрезать всё лишнее и оставить только ноду?

Вопрос справедливый, но на него есть ответ. И даже несколько.

  1. Отрывание лишнего - процесс итеративный. Вряд ли у меня получилось бы оторвать всё разом и сразу собрать работающее приложение. А так - я смог поднять такое же окружение, убедиться, что оно работает идентично, и понемногу откусывать лишнее.
  2. Теперь я могу поделиться этим сервисом с вами. Даже если у вас другая OS и не стоит Node.JS
  3. В таком виде сервис сможет прожить ещё долго - до тех пор, пока не отвалится совместимость браузера.

Бонус

Чтобы не заканчивать на печальной ноте - поделюсь забавной находкой.

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

img_6.png

И какие-то очень странные вещи, похожие на начало DnD истории - там есть тюрьма, два стула и атака зомби. Я не стал подымать сервис в вебе, потому что наверняка там есть уязвимости стародавних времён. А локально вполне можно поиграться. Но будьте осторожны и отсматривайте код перед рендерингом - там запросто может оказаться какой-нибудь злой JS, автор не делал от него защиты при рендеринге.

Вместо заключения

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