Мигрируем продакшн с Debian 7 Wheezy на Jessie: что идёт гладко и где ждут init-скрипты
Debian 8 Jessie в стабильном канале - начинаем миграцию серверов с Wheezy. do-release-upgrade работает, но кастомные init.d-скрипты нужно переписывать в unit-файлы.
Debian 8 Jessie стабилен - массовая миграция с Debian 7 Wheezy началась
Когда Jessie объявили стабильным, мы не стали ждать. У нас под сопровождением достаточно серверов на Wheezy, чтобы подойти к делу системно, а не разбираться с каждым в пожарном режиме. Прошли уже первую волну миграций - есть что рассказать.
Коротко: сам апгрейд работает значительно чище, чем можно было ожидать. Вся боль - в кастомных init.d-скриптах, которые накопились за годы на Wheezy.
Порядок действий, который у нас прижился
Мы не делаем do-release-upgrade вслепую. Перед каждым сервером - короткая подготовка.
Первое - инвентаризация кастомных init.d-скриптов. Ищем всё нестандартное в /etc/init.d/: что не пришло из пакетов, что было написано руками или когда-то скопировано с StackOverflow. Это занимает 15-20 минут, но экономит час разбора после апгрейда.
Второе - снапшот или резервная копия. Для VPS - снапшот через API провайдера прямо перед началом. Для железа - проверяем что последний бэкап свежий. Очевидно, но лучше написать.
Третье - сам апгрейд. Обновляем текущую систему, потом меняем sources.list и делаем dist-upgrade:
apt-get update && apt-get upgrade
apt-get dist-upgrade
# Убеждаемся что нет незакрытых зависимостей
apt-get autoremove
# Переключаем репозитории на Jessie
sed -i 's/wheezy/jessie/g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list
apt-get update
apt-get dist-upgrade
Апгрейд занимает от 20 минут до часа в зависимости от количества пакетов и скорости канала. Всё делаем внутри screen или tmux - обрыв SSH посередине неприятная история.
Четвёртое - перезагрузка и проверка. После апгрейда смотрим что поднялось, что нет, изучаем systemctl status для ключевых сервисов.
Где именно начинаются проблемы
Пакетные сервисы - nginx, postgresql, openssh, exim - поднимаются сами. systemd видит их unit-файлы из пакетов и дальше всё работает как ожидается.
Проблемы начинаются ровно там, где мы и предсказывали в апреле: кастомные init.d-скрипты.
Слой совместимости в Jessie существует, и большинство скриптов технически запускаются. Но «технически запускается» и «работает корректно» - разные вещи. Конкретные случаи из наших миграций:
Скрипт, который ловит процесс по PID-файлу. Создавал /var/run/myapp/myapp.pid при старте и ожидал найти директорию при следующей загрузке. В Jessie /var/run - tmpfs, между перезагрузками пусто. Скрипт падал при старте с невнятной ошибкой.
Скрипт с su - appuser -c "...". Работал на Wheezy, в Jessie через слой совместимости вёл себя непредсказуемо с переменными окружения. Диагноз занял время, потому что при ручном запуске всё было нормально - проблема воспроизводилась только при старте системы.
Скрипт с hardcoded путями к /etc/default/-файлам с нестандартным форматом. Незначительное, но пришлось поправить.
Unit-файлы: что переписываем
Для каждого кастомного init.d-скрипта пишем unit-файл. Мы разобрали синтаксис подробно - повторяться не будем. Практические выводы из миграции:
Type=forking нужен реже, чем кажется. Старые демоны часто форкались, потому что SysV этого требовал. Если приложение умеет работать на переднем плане - используем Type=simple, убираем логику форкинга из скрипта. Проще и надёжнее.
/var/run - только через RuntimeDirectory=. Вместо ручного создания директорий в скриптах:
[Service]
RuntimeDirectory=myapp
RuntimeDirectoryMode=0755
systemd создаст /run/myapp/ при старте сервиса и удалит при остановке. Чисто и правильно.
EnvironmentFile= вместо source /etc/default/myapp. Если нужны переменные окружения - кладём их в файл и указываем в unit:
[Service]
EnvironmentFile=-/etc/default/myapp
Минус перед путём означает «не падать если файл не существует». Формат файла - простые KEY=VALUE без export.
После написания unit-файла обязательно:
systemd-analyze verify /etc/systemd/system/myapp.service
systemctl daemon-reload
systemctl enable myapp.service
systemctl start myapp.service
systemctl status myapp.service
Про Ansible
Наши Ansible-плейбуки для провизионирования нужно обновлять параллельно. Таски которые раньше деплоили init.d-скрипты через template и вызывали update-rc.d - теперь деплоят unit-файлы и вызывают systemctl enable. Модуль service в Ansible 1.9 понимает systemd нормально, но условия when: ansible_distribution_version == '7' нужно пересматривать под каждый плейбук отдельно.
Где сейчас
Прошли первую партию серверов - там где нагрузка не критическая и окна обслуживания проще согласовать. Продакшн-серверы с жёсткими требованиями к доступности миграцию ждут: отрабатываем процесс, пишем unit-файлы для всего кастомного заранее, потом делаем за одно окно.
Общее ощущение: Jessie - это Wheezy с systemd и свежими пакетами. Неожиданностей минимум, если навести порядок с init-скриптами до апгрейда, а не после.