Новый бинарник уже на диске, сервис отвечает по-старому: проверяем /proc/PID/exe
В каталоге релиза лежит новый исполняемый файл, проверка его версии проходит, а работающий сервис ведёт себя как раньше. Такое бывает после обновления с атомарной заменой файла или переключением ссылки current. Новый запуск получит новую версию. Уже работающий процесс продолжит исполнять тот объект, с которым стартовал. Проверка файла по имени сообщает, что произойдёт при следующем запуске. Для расследования нужно сначала посмотреть, какой файл исполняется сейчас.
Сравните два объекта на одном хосте
Если сервис запущен через systemd, возьмите текущий MainPID. Пример рассчитан на программу с собственным нативным исполняемым файлом. Выполняйте команды на Linux-хосте сервиса. Подставьте имя юнита и путь, по которому менеджер должен открыть программу при новом запуске.
pid=$(systemctl show --value -p MainPID report-api.service)
expected=/opt/report/current/bin/report-api
if [ "$pid" -gt 0 ]; then
readlink "/proc/$pid/exe"
stat -Lc 'running device=%d inode=%i %n' "/proc/$pid/exe"
stat -Lc 'expected device=%d inode=%i %n' "$expected"
fi
/proc/$pid/exe указывает на исполняемый файл конкретного процесса. Опция -L заставляет stat пройти по ссылке и вывести метаданные файла, а не самой ссылки. Сравнивайте пару из устройства и inode: одного inode недостаточно, поскольку одинаковые номера могут встретиться на разных файловых системах.
Если пары отличаются, процесс работает с другим файловым объектом. При выкладке через каталоги релизов вывод readlink часто сразу покажет путь вроде /opt/report/releases/old/bin/report-api. Если файл в старом каталоге заменили через переименование и прежнее имя исчезло, к пути может добавиться (deleted). В обоих случаях сравните путь с тем, что на самом деле указано в настройке запуска. Возможно, юнит явно закреплён за предыдущим каталогом и повторный запуск снова возьмёт старый бинарник.
Когда сравниваете именно байты, добавьте sha256sum "/proc/$pid/exe" "$expected". Сравнивайте хеши с ожидаемым артефактом сборки, если он есть. Два разных inode могут содержать одинаковые байты, например после копирования того же релиза. Доступ к ссылке ограничен правами процесса и настройками системы. При отказе выполняйте проверку с разрешённым доступом к процессу, не ослабляя общие настройки /proc.
Посмотрите, как это происходит в отдельном каталоге
Для демонстрации нужны Linux и стандартные утилиты. Скопируйте sleep под новым именем, запустите его и атомарно замените файл копией true. Команды работают только внутри только что созданной временной папки. Через десять секунд фоновый процесс завершится сам.
work=$(mktemp -d) || exit 1
cp "$(command -v sleep)" "$work/worker" || exit 1
cp "$(type -P true)" "$work/next-worker" || exit 1
"$work/worker" 10 &
pid=$!
while [ ! "$work/worker" -ef "/proc/$pid/exe" ]; do
kill -0 "$pid" 2>/dev/null || exit 1
sleep 0.01
done
mv -T "$work/next-worker" "$work/worker" || exit 1
readlink "/proc/$pid/exe"
stat -Lc 'running device=%d inode=%i' "/proc/$pid/exe"
stat -Lc 'expected device=%d inode=%i' "$work/worker"
wait "$pid"
В этом фрагменте используйте Bash: type -P true ищет внешний исполняемый файл, хотя у Bash есть и встроенная команда true. Проверка -ef ждёт, пока фоновый процесс действительно запустит копию sleep. После замены оба пути ведут к разным объектам, а wait завершится только после окончания старого sleep. Такой опыт показывает, почему мгновенная смена имени файла и смена запущенного процесса происходят в разные моменты.
Повторите проверку на процессе, который принимает запросы
В службе с управляющим процессом и несколькими обработчиками MainPID может указывать на управляющий процесс. Проверьте фактические PID обработчиков. У приложения на Python или Node.js exe покажет интерпретатор, поэтому версию приложения сверяйте по его собственной метке сборки и параметрам запуска. Для нативного сервиса после штатной выкладки сопоставьте новый PID, совпадение файла с ожидаемым артефактом и ответ приложения. Если пары совпали, а снаружи видна старая версия, проверьте другие экземпляры за балансировщиком.
Откройте программу курса DevOps-инженера. Вы настроите службу на Ubuntu Server и потренируетесь искать причину сбоя на собственном стенде. Начать можно с пробного урока о фоновых процессах, systemd и journalctl.
Продолжить на практике
- DevOps-инженер с нуля: полный курс ↗
Практический курс по DevOps для начинающих: от настройки Linux и сетей до сборки контейнеров, автоматизации в GitLab CI/CD, развёртывания в Kubernetes, мониторинга и восстановления данных на собственном компьютере.
