Деплой и эксплуатация
У PHP нет шага компиляции и нет артефакта, который нужно «запустить» — исходники лежат на диске, и веб-сервер читает их как есть. Отсюда родилась иллюзия, что деплой PHP — это git pull или rsync. Она стоила отрасли множества ночных инцидентов, потому что за кажущейся простотой прячутся ровно те механизмы, которые делают PHP быстрым: OPcache держит скомпилированный код в разделяемой памяти, а кэш realpath помнит, куда указывал ваш симлинк. Подмените файлы «на месте» — и часть запросов уедет в полу-скопированное дерево, а после подмены воркеры продолжат исполнять предыдущий релиз, потому что в кэше у них лежит именно он.
Поэтому производственный деплой PHP строится на трёх опорах, и все три стоит назвать сразу. Первая: атомарность — релиз собирают в отдельный каталог целиком и только потом одним движением переключают на него симлинк, так что ни один запрос не видит полуготовое дерево. Вторая: инвалидация — после переключения обязателен сброс OPcache (на практике — graceful-перезагрузка FPM), иначе прогретый кэш будет исполнять старый код сколько угодно долго. Третья: обратная совместимость на время выкатки — пока фронт балансировщика раскатывает релиз по машинам, старый и новый код работают с одной и той же базой одновременно, поэтому схема меняется по схеме expand/contract, а рискованное поведение прячется за фича-флагом, чтобы откат был переключателем, а не миграцией. Разбор по слоям — ниже.
Карта темы
- Конфигурация окружения —
.envи переменные окружения, почему секреты не коммитят и как расходятсяphp.iniу CLI и у FPM. - Стратегии деплоя — атомарная подмена симлинка, обязательный сброс OPcache, выкатка по флоту через балансировщик и expand/contract для схемы базы.
- Логирование и мониторинг — куда уходят warning, необработанное исключение и прикладной лог, что даёт трекер ошибок сверх лог-файла и что в нём обязательно настроить.
- Фича-флаги — где живёт значение флага, как раскатывать процент пользователей липко и чем флаг никогда не является.
- Планировщик задач — чем cron отличается от запроса через FPM, как из шести серверов джобу выполняет ровно один и почему она всё равно обязана быть идемпотентной.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Раскатывать релиз через git pull или rsync поверх живого каталога | Запросы попадают в полу-обновлённое дерево — новый контроллер со старым шаблоном |
| Переключить симлинк и на этом закончить | OPcache держит опкоды предыдущего релиза — воркеры продолжают исполнять старый код |
Считать ln -sfn атомарной операцией | Она снимает симлинк и создаёт заново; атомарна подмена через mv -T (это rename(2)) |
Забыть про кэш realpath | Даже после сброса OPcache воркер может резолвить current/ в старый каталог релиза |
| Возвращать инстанс в балансировщик сразу после копирования файлов | Трафик приходит на непрогретое приложение с ненакаченными кэшами — всплеск 5xx |
Выкатывать DROP COLUMN в том же релизе, что и код, переставший его читать | Старые инстансы, ещё не обновившиеся, падают на отсутствующей колонке |
| «Откатывать» миграцию после того, как на новой схеме уже есть данные | Откат миграции — это потеря данных; откатывают код, схему оставляют расширенной |
Коммитить .env в репозиторий | Пароли и ключи навсегда остаются в истории git |
Считать, что php.ini один | У CLI-SAPI и у FPM свои файлы; memory_limit и max_execution_time у них разные |
Держать display_errors=On в продакшене | Стек-трейс с путями и куском конфига уезжает прямо в браузер пользователю |
Логировать конкатенацией — "user $id failed" | Каждое сообщение уникально, группировать и агрегировать нечего |
Не задать release и environment в трекере ошибок | Ошибка есть, а какой деплой её принёс — определить нечем |
| Использовать фича-флаг как проверку прав доступа | Флаг — операционный переключатель, а не граница безопасности |
| Оставить флаг в коде навсегда | Матрица непротестированных комбинаций растёт экспоненциально |
| Держать cron на всех машинах флота | Джоба выполняется на каждой — списание уходит шесть раз |
| Считать, что блокировка даёт «ровно один раз» | Истёкший по TTL лок и ретрай дают повтор; спасает только идемпотентность |
| Не мониторить, что задача не запустилась | Молчащий cron выглядит ровно как исправный — узнаете через месяц |
Значение для собеседований
Деплой — раздел, где senior-кандидат отличается от middle одной фразой. Вопрос почти всегда звучит как «расскажите про деплой без простоя», и рассказ про симлинк — это только половина ответа, причём лёгкая. Вторая половина — та самая, которую и проверяют: симлинк переключили, а OPcache всё ещё держит скомпилированный старый релиз, поэтому обязателен сброс кэша или graceful-перезагрузка FPM, а до тех пор часть воркеров честно отдаёт предыдущую версию кода. Кандидат, который сам это называет, почти наверняка ловил такой инцидент.
Что обычно проверяют:
- Что делает атомарный деплой и почему подмена симлинка — не последний шаг.
- Почему после деплоя нужен сброс OPcache и что происходит, если его пропустить.
- Как раскатать релиз по флоту за балансировщиком и когда инстанс снова годен для трафика.
- Как менять схему базы так, чтобы старый и новый код работали одновременно, и что такое откат после миграции.
- Куда в продакшене попадают warning, необработанное исключение и прикладной лог.
- Что даёт трекер ошибок сверх
grepпо логу и что в нём обязательно настроить. - Где хранится значение фича-флага и как удержать пользователя на одной стороне флага.
- Чем запуск из cron отличается от запроса через FPM и как заставить джобу выполниться на флоте ровно один раз.
Типичный неверный ответ: «Деплой без простоя — это переключить симлинк, дальше всё само». Дальше не само: прогретый OPcache — это кэш скомпилированного кода, и он переживает подмену файлов; более того, кэш realpath может продолжать резолвить current/index.php в каталог предыдущего релиза, так что даже сброс OPcache без перезагрузки воркеров помогает не всегда. Второй классический провал — обещание «откатить миграцию», если релиз не задался: на данных, уже записанных по новой схеме, откат миграции означает их потерю. Откатывают код; схему оставляют расширенной — ровно затем expand/contract и придуман.