ICT: задания, которые переживают перезапуск, Windows XP в контейнере и выкладка без ручных шагов
Полгода работы над оркестратором ICT: события заданий хранятся в базе и переживают перезапуск хаба, унаследованная программа расчёта абсолютного TEC запускается в настоящей Windows XP внутри KVM-контейнера, образы собираются в CI и выкладываются сами, а доступ разграничен и записывается в журнал. В конце — что нашли ревизия кода и линтеры.
В июньском обзоре ICT была описана как набор контейнеризованных сервисов с веб-оркестратором поверх. С тех пор в оркестраторе появился модуль карт ионосферы — ему посвящён отдельный разбор. Остальная работа ушла в то, что редко видно со стороны: чтобы платформой можно было пользоваться каждый день, не заглядывая на сервер. Это 67 коммитов с апреля по сентябрь, 1 октября они слиты в основную ветку. Веб-интерфейс называется «ИоноПоток».

Задание, которое переживает перезапуск
Конвертер — это контейнер, который может работать часами, а хаб, который его запустил, обновляется и перезапускается. Поэтому события задания — строки лога, прогресс и завершение — теперь сначала записываются в базу, а браузер получает их оттуда через Server-Sent Events. Перезагрузка страницы показывает последние 200 строк лога вместо всего потока, а в базе у каждого задания хранятся только последние строки лога — по умолчанию 2000, — чтобы она не росла без предела.
После пересборки хаб сам находит запущенные им контейнеры. Каждый такой контейнер помечен метками — конвертер, номер задания, пользователь, — и хаб по ним снова подключается к заданию. Задание, начатое до обновления, остаётся в списке запусков, а его прогресс продолжает приходить в браузер.
Отдельно пришлось разобраться со статусами. Задание, которое остановил пользователь, записывалось как проваленное и показывалось красным, как настоящая ошибка. Теперь у него свой статус «остановлено» с приглушённым значком, а старые записи переведены в него при запуске. Попутно нашлась гонка: контейнер завершается, пока команда остановки ещё выполняется, и сообщение о завершении могло превратить остановку пользователем в «ошибку (143)». Теперь остановка записывается до вызова docker stop, а побеждает первое записанное конечное состояние.

Ещё одна находка касалась потока событий. Сжатие ответов в используемой версии Starlette сжимало и text/event-stream, а компрессор придерживал события, пока не заполнится буфер. Лог работающего задания приходил рывками или только в конце: поток молчал по восемь секунд, пока контейнер печатал строку в секунду. Запросы от EventSource теперь идут без сжатия. Наконец, обращения к Docker API — список контейнеров, запуск, остановка с ожиданием до 10 секунд — вынесены из цикла событий в рабочий поток, чтобы не останавливать отрисовку страниц.
Windows XP в контейнере
Расчёт абсолютного TEC выполняет унаследованная программа absolTEC.exe — 32-битное приложение, рассчитанное на Windows XP. В июне она запускалась под Wine. Теперь есть второй способ: настоящая Windows XP, которая работает гостевой системой в KVM-контейнере на основе dockur/windows. Для программы это родная платформа.
Хаб и гостевая система обмениваются через общую папку. Хост записывает папку задания, сценарий-наблюдатель внутри XP забирает её, запускает программу и пишет лог и код завершения. Хост читает лог по мере записи и разбирает прогресс так же, как для Wine. Если время вышло, хост просит гостевую систему завершить именно эту копию программы.
Внутри одного запуска станции можно считать параллельно, по слотам. У каждого слота своя рабочая папка и своя копия программы: одновременные расчёты не должны делить файл настроек и папку результатов. А вот самих заданий XP одновременно выполняет не больше одного: гостевая система берёт работу из одной очереди, второе задание просто ждало бы в ней и упиралось в тайм-аут. К тому же программа называет папку результатов по первым четырём символам имени станции, и при параллельных заданиях результаты станций с общим префиксом могли бы перепутаться. Поэтому каждое задание сначала складывает результаты в собственную промежуточную папку.
Если контейнер с XP остановлен, хаб запускает его сам и предупреждает: системе нужна минута-другая на загрузку, поэтому у тайм-аута должен быть запас.

У пакетных запусков появились настройки на случай сбоев. Станция, на которой программа завершилась с ошибкой, пропускается, и расчёт продолжается. Но 25 ошибок подряд прерывают запуск: это уже признак системного сбоя, а не плохих данных одной станции. Прерванный пакет можно продолжить, пропуская станции, для которых результаты уже есть.
Выкладка без ручных шагов
Каждое изменение основной ветки проходит в CI тесты и линтеры, а после успешных тестов собираются образы хаба и индексатора данных. Образы публикуются с тегом коммита. На сервере их подхватывает Watchtower: по умолчанию раз в пять минут он проверяет, не вышел ли новый образ, и перезапускает помеченные контейнеры.
Когда сборки выкладываются сами, на вопрос «что сейчас запущено» должен отвечать интерфейс. Поэтому на панели теперь видны версия — тот самый коммит, которым помечен образ, — и время сборки, в часовом поясе того, кто смотрит. При автоматической выкладке время сборки и есть время последнего обновления.
На этом пути нашлась и ошибка сборки. Страницы загружают htmx, Plotly и шрифты из папки самого приложения, чтобы браузерам во внутренней сети не нужен был интернет. Но эта папка не хранилась в репозитории, и ни один шаг сборки её не заполнял, так что образы из CI отдавали страницы без них. Теперь отдельная стадия сборки скачивает эти файлы, сверяет скрипты по SHA-256 и проверяет, что стили шрифтов ссылаются только на WOFF2-файлы Google Fonts. Любая ошибка загрузки останавливает сборку, вместо того чтобы выпустить сломанный образ.
Доступ и журнал
У пользователей есть роли — администратор и оператор, — а у каждой учётной записи отдельные права: на страницы и на каждый конвертер. Новая учётная запись по умолчанию видит только обзорные страницы — аналитику, карту станций и индексированные данные; доступ к конвертерам выдаётся по одному.
Летом к этому добавился слой защиты. В строгом режиме, который включает конфигурация Docker Compose, хаб не запустится со слабым ключом подписи сессий; без него он создаст случайный временный ключ вместо известной заглушки. Администратор с паролем по умолчанию обязан сменить его при первом входе. Попытки входа ограничены: восемь за пять минут. Добавлены проверка источника запросов против CSRF и заголовки безопасности. Доступ хаба к Docker можно сузить: дополнительный файл конфигурации ставит перед сокетом Docker прокси, который пропускает только нужные группы API. А все входы, включая неудачные, создание учётных записей, смена паролей и запуск заданий пишутся в журнал, который приложение никогда не изменяет и не удаляет.


Ревизия кода
Последний день сентября ушёл на ревизию. Замечания ревью, сделанного на отдельной ветке рефакторинга, перепроверены на актуальном коде, и те, что ещё были верны, перенесены. Среди них:
- фрагменты ошибок задания выводили введённые в форму значения и текст исключения без экранирования — теперь всё экранируется в одном месте;
- прокси к аналитическому сервису пересылал ему cookie сессии хаба — больше не пересылает;
- у индексатора данных нет своей аутентификации, поэтому он теперь принимает только пути внутри настроенных каталогов с данными;
- стартовое индексирование выполняло блокирующий обход файловой системы прямо в цикле событий, и пока оно шло, не отвечал даже
/health. Заодно выяснилось, что задокументированное значение настройки запуска индексирования не распознавалось, и индексирование молча пропускалось. Теперь обход идёт в отдельном потоке, а оба значения работают; - изменение каталога сбрасывало кэш только того типа данных, который успевал заметить его первым, а изменение во время сканирования терялось. Теперь у каждого каталога есть счётчик изменений, и каждый кэш помнит, какое поколение он видел.
Линтеры нашли 296 замечаний ruff и 21 замечание ESLint; теперь они исправлены, и линтеры вместе с mypy запускаются в CI. Почти всё было механическим, но одна находка оказалась настоящей ошибкой: модуль отрисовки карт при импорте регистрирует шрифты и пишет в журнал через объект, определённый ниже по файлу. Шрифт, который не удалось зарегистрировать, вызвал бы NameError вместо записи в журнал.
Около 3700 строк скриптов и стилей переехали из шаблонов страниц в статические файлы, которые видят и линтер, и кэш браузера. Поведение не должно было измениться ни в чём, и это проверено напрямую: на всех шести затронутых страницах, на двух языках, сравнивались дерево страницы после загрузки, вычисленные стили и состояние после перебора каждого выпадающего списка и флажка. Версии до и после совпали.
Выводы
- Состояние задания должно жить дольше процесса, который его показывает: события сначала записываются в базу, а потом уже отдаются браузеру.
- «Остановлено» и «ошибка» — разные статусы. Если статус неверно называет причину, история запусков превращается в шум.
- Унаследованную программу надёжнее запускать на её родной платформе. KVM-контейнер делает Windows XP ещё одним сервисом, но допущения самой программы — вроде имён папок по четырём символам — придётся учесть в обвязке.
- При автоматической выкладке версия и время сборки на панели отвечают на вопрос «что запущено» без входа на сервер.
- Среди сотен механических замечаний линтеров и ревью попадаются настоящие ошибки — поэтому их стоит разбирать целиком, а не отключать.
Доступность
Исходный код оркестратора открыт; в README описаны настройки, запуск через Docker Compose и методика модуля карт.
- Исходный код: github.com/nikita-konkin/ict-hub
Теги
Другие обновления по проекту
ICT: неделя анимации карт за минуту, карта станций на OpenStreetMap и что мешало браузеру
Анимация карт ионосферы за неделю строилась 18 минут и обрывалась на прокси. Теперь дни загружаются, а кадры рисуются в параллельных процессах в фоне — та же неделя за 62 секунд...
ICT: от наклонного TEC к картам ионосферы — методика IonMaps
Как модуль IonMaps в ICT превращает parquet-выгрузку TEC-suite в региональные карты вертикального электронного содержания: восстановление VTEC из наклонного TEC, интерполяция (л...