Логотип
Программист видит мир через данные и алгоритмы. (автор не известен)

Как контейнеризировать устаревшее приложение Linux 15-летней давности без простоев

Как контейнеризировать устаревшее приложение Linux 15-летней давности без простоев

15-летнее приложение для Linux, в котором до сих пор реализована основная бизнес-логика, — это не проблема, которую нужно решать. Это актив, к которому никто не хочет прикасаться, потому что никто уже не понимает в полной мере, как оно работает.

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

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

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

Docker, но вы также можете использовать Podman.

 

Убедитесь, что приложение подходит для контейнеризации

Прежде чем что-либо писать, проверьте приложение по двум спискам.

У хороших кандидатов обычно есть:

  • Четкие границы приложения
  • Воспроизводимые зависимости (вы можете перечислить все необходимое, даже если список длинный)
  • Стандартные сетевые интерфейсы (TCP, HTTP и т. п.)
  • Управляемые требования к хранилищу
  • Отсутствие необычных аппаратных зависимостей
  • Среда выполнения, работающая без специальных привилегий хоста

 

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

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

 

Инвентаризация всех зависимостей

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

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

 

Найдите все файлы, к которым обращается процесс во время работы:

strace -f -e trace=open,openat -p <PID> 2>&1 | grep -v ENOENT

 

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

 

Найдите все открытые сетевые подключения и дескрипторы файлов:

lsof -p <PID>

 

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

Читать  6 лучших современных систем инициализации Linux (1992–2025)

 

Узнайте, с чем связан бинарный файл:

ldd /path/to/binary

 

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

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

Четыре категории объясняют большинство сюрпризов, которые выявляет этот шаг:

  1. Предположения о файловой системе. Считывает или записывает в /etc//opt//var/lib/ или /tmp/ которые предполагают, что эти пути всегда существуют.
  2. Предположения о разрешении. Запуск от имени root или ожидание определенного UID или GID, которого не будет у пользователя контейнера по умолчанию.
  3. Предположения о сети. Вызовы localhost службы, которая раньше запускалась на том же физическом компьютере, или ссылки на фиксированное имя хоста или статический IP.
  4. Предположения об операционной системе. Зависимость от определенного поведения ядра, часового пояса, локали или системной утилиты, которые не установлены по умолчанию в минимальном образе.

 

Как только в результатах stracelsof, и ldd перестанут появляться новые данные, можно приступать к сборке.

 

«Выбрать базовый образ»

FROM ubuntu:latest не всегда является правильной отправной точкой. Базовый образ — это начальная файловая система и набор инструментов, на основе которых создается контейнер.

Подберите базовый образ в соответствии с тем, что вы нашли на шаге 2, а не с самой новой версией:

# Проверьте версию glibc, которую использует старый сервер
ldd --version

# Проверьте версию OpenSSL
openssl version

# Сравнение с тем, что доступно в базовом образе-кандидате
docker run --rm ubuntu:24.04 bash -c "ldd --version && openssl version"

 

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

Ubuntu 26.04 LTS — это текущая версия, но она не всегда является правильным выбором для миграции с более старых версий. Для приложений, которым уже 15 лет, ubuntu:24.04 обычно является более безопасной отправной точкой. Это по-прежнему полностью поддерживаемая версия LTS. Если вам нужен более новый набор инструментов, вы можете перейти на версию 26.04 позже, когда контейнер станет стабильным.

 

Напишите первый файл Dockerfile и ожидайте, что он завершится ошибкой

Начните с минимума, необходимого для запуска приложения, непосредственно на основе вашего инвентаря шага 2:

ИЗ ubuntu: 24.04

# Установите пакеты среды выполнения и операционной системы, идентифицированные в ходе анализа зависимостей
RUN apt-get update && apt-get install -y \
    libssl3 \
    libpq5 \
    <other-packages-from-your-inventory> \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY ./legacy-app /app/

EXPOSE 8080
CMD ["/app/legacy-app"]

 

Соберите и запустите его:

docker build -t legacy-app:test .
docker run --rm -p 8080:8080 legacy-app:test

 

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

Читать  Thunderbird 142 позволяет добавлять подписи в PDF-файлы прямо в приложении

Распространенные ошибки при первом запуске и их возможные причины:

СимптомВероятная причина
permission denied при записи в файлУ пользователя контейнера нет прав собственности; проверьте UID/GID, указанные на шаге 2
Отказано в соединении с базой данныхПриложение использует localhost для службы, которая раньше работала на том же хосте; внутри контейнера localhost относится к самому контейнеру.
Отсутствует файл при запускеПуть из /etc//opt/, или /var/lib/ не был скопирован в образ
Ошибка несоответствия версии библиотекиВерсия библиотеки базового образа отличается от той, которую ldd сообщил старый сервер
Процесс завершается немедленно, ошибок нетПроверить docker logs <container-id>; часто проблема в отсутствующей переменной среды

 

Устраняйте по одной ошибке за раз, перестраивайте и запускайте заново. Именно в этом цикле, а не в исходном Dockerfile, происходит большая часть реальной работы.

 

Вынесите состояние за пределы контейнера

По умолчанию файловая система контейнера является временной. Все, что в ней записано, исчезает при удалении контейнера.

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

docker run -d \
 -v /host/data/uploads:/app/uploads \
 -v /host/data/reports:/app/reports \
 -p 8080:8080 \
 legacy-app:test

 

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

 

Разделяйте фоновые процессы

На практике система, которой 15 лет, часто представляет собой один сервер, на котором запущено приложение, а также воркер, задания cron, скрипты очистки и ротация журналов, потому что никто никогда их не разделял.

Перечислите все фоновые процессы, обнаруженные на шаге 2, и присвойте им один из следующих статусов:

# Пример docker-compose.yml с разделением обязанностей
services:
 app:
 image: legacy-app:latest
 ports:
 - "8080:8080"

 worker:
 image: legacy-app:latest
 command: ["/app/run-worker.sh"]

 cron:
 image: legacy-app:latest
 command: ["/app/run-cron.sh"]

 

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

 

Защитите контейнер перед запуском в эксплуатацию

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

Запускайте контейнер от имени пользователя без прав root:

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

 

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

docker run -d \
 --memory="512m" \
 --cpus="1.0" \
 legacy-app:production

 

Перед развертыванием образа выполните его сканирование на наличие известных уязвимостей:

docker scout cves legacy-app:production

 

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

Читать  Как узнать, работает ли игра в Linux

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

 

Проверка совместимости с устаревшей системой

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

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

ПроверитьLegacyContainer
StartupРаботаетПодтвердить
ВходРаботаетПодтвердить
Операции с базой данныхРаботаетПодтвердить
Обработка файловРаботаетПодтвердить
Внешние вызовы APIРаботаетПодтвердить
Запланированные заданияРаботаетПодтвердить
Время отклика при нагрузкеИсходный уровеньСравнение с исходным уровнем

 

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

# Пример использования Apache Bench для обеих систем
ab -n 1000 -c 10 http://legacy-server:8080/
ab -n 1000 -c 10 http://localhost:8080/

 

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

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

 

Постепенное развертывание

Одномоментный перевод всего производственного трафика со старой системы на новую — самый рискованный способ внедрения этого изменения.

Запустите обе системы параллельно за обратным прокси-сервером и постепенно перераспределяйте трафик:

upstream backend {
    server legacy-server:8080 weight=9;
    server container-app:8080 weight=1;
}

 

Увеличивайте вес контейнера по мере накопления опыта и отслеживайте частоту ошибок и время отклика на каждом этапе.

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

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

Безопасная миграция — это не та миграция, при которой сбой невозможен. Это та миграция, при которой сбой имеет контролируемый, заранее определенный путь возврата к рабочему состоянию.

 

Что решает контейнеризация и чего она не решает

Контейнеризация обычно улучшает изоляцию зависимостей, воспроизводимость среды, согласованность развертывания, переносимость, скорость отката и интеграцию CI/CD.

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

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

 

Когда следует сначала использовать контейнеры, а когда нет

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

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

Универсально верного ответа не существует. Правильный выбор зависит от того, какое из этих условий действительно применимо к вашей системе.

 

Контейнеризация — это шаг, а не финишная прямая

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

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

Редактор: AndreyEx

Рейтинг: 5 (1 голос)
Если статья понравилась, то поделитесь ей в социальных сетях:

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

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

17 − одиннадцать =

Это может быть вам интересно


Спасибо!

Теперь редакторы в курсе.

Прокрутить страницу до начала