Graceful shutdown: как перезапускать приложение, не обрывая запросы и задачи
Плановый перезапуск может оборвать оформление заказа почти так же неприятно, как авария. Сервер успел записать покупку, но не отправил ответ; клиент повторяет действие, а фоновый обработчик одновременно теряет соединение с очередью. Надпись «приложение успешно запущено» после обновления ничего не говорит о судьбе этих операций. Graceful shutdown нужен, чтобы заранее определить, какую работу процесс перестаёт принимать, что завершает и как передаёт незавершённое.
Перезапуск приложения без обрыва запросов требует согласованной работы самого приложения, маршрутизации и системы запуска. Одного обработчика сигнала недостаточно. Разберём практический порядок остановки HTTP-сервера и воркеров, ограничение времени ожидания и проверки, по которым владелец сайта сможет принять результат работы подрядчика.
Что можно ожидать от плановой остановки
Корректное завершение начинается с управляемого события: команда развёртывания, остановка службы или другое предусмотренное приложением уведомление. Процесс получает возможность изменить своё состояние, прекратить приём работы и дождаться разрешённых операций. Программа должна действительно обрабатывать такое событие: настройка длительного ожидания в менеджере процессов не создаёт нужную логику внутри приложения.
При внезапном завершении процесса, потере сервера или принудительном уничтожении такой возможности может не быть. Поэтому graceful shutdown дополняет сохранность данных, безопасные повторы и восстановление задач, но не заменяет их. Нельзя обещать, что всякое выключение сервера пройдёт незаметно, если только увеличить время остановки.
Новые запросы в период обновления должен кто-то обслуживать. Обычно для этого заранее готовят другой работоспособный экземпляр и проверяют, выдержит ли оставшаяся группа текущий поток. Два процесса на одном перегруженном сервере ещё не доказывают наличие достаточной мощности. При единственном экземпляре без подготовленного механизма замены возможен перерыв; его условия нужно согласовать, а не скрывать за словом graceful.
Критерий успеха формулируют через операции: принятые заказы сохранены, разрешённые запросы завершились, задачи получили установленный исход. Отдельно учитывают запросы, которым отказали до принятия работы. Они не должны превращаться в ложное сообщение «всё выполнено».
Сначала перечислите работу, которую выполняет процесс
Один процесс может одновременно принимать HTTP-запросы, обслуживать постоянные соединения, читать очередь и запускать внутренний таймер. Остановка только веб-сервера оставит остальные источники работы активными. Составьте небольшой реестр: откуда приходят операции, где они становятся ответственностью приложения и каким событием подтверждается их завершение.
| Вид работы | Что прекратить | Что требуется сохранить или завершить |
|---|---|---|
| Обычный HTTP-запрос | Приём новых операций на выводимом экземпляре | Принятую обработку и передачу ответа в пределах срока |
| Долгое соединение | Открытие новых сессий и новых действий внутри них | Согласованное завершение, уведомление клиента, возможность продолжения |
| Задача из очереди | Получение дополнительных сообщений | Результат обработки либо предусмотренный путь повторного получения |
| Внутренний запуск по таймеру | Планирование очередного выполнения | Уже начатую работу или пригодную для продолжения контрольную точку |
Укажите также ресурсы, необходимые до последнего шага: соединение с базой, канал подтверждений брокеру, клиент внешнего сервиса. Их закрывают после зависимой работы. Если сначала отключить базу, а затем ждать сохранения заказов, процедура сама создаст ошибки, от которых должна была защищать.
В реестре полезно записать владельца каждого обработчика и способ наблюдения. Формулировку «все фоновые потоки завершатся сами» замените проверяемым условием: известен счётчик активных операций, определены их результаты, установлен общий предел ожидания.
Как прекратить новый трафик и не забыть о задержках маршрутизации
Приложению нужен явный режим завершения. В нём экземпляр больше не считается подходящим для новой работы, а уже принятые операции продолжаются по правилам остановки. Информация об этом должна доходить до балансировщика или другого компонента, который распределяет запросы. Административный механизм включения режима защищают от постороннего доступа.
Не путайте готовность принимать трафик с работоспособностью процесса. В документации Kubernetes о проверках readiness и liveness эти задачи разделены: readiness используется для управления получением трафика, liveness — для обнаружения состояний, требующих перезапуска. Исправно завершающий работу процесс не нужно объявлять сломанным только потому, что он уже не принимает новые операции.
Изменение готовности не является мгновенным запретом на всех путях. Между приложением, управляющей системой, прокси и балансировщиком есть распространение состояния. Часть клиентов использует существующие соединения; возможны и прямые обращения в обход привычного маршрута. Поэтому проверять нужно реальный путь запроса, а не только изменение флага в панели.
Определите поведение для запроса, который приходит в переходный момент. Пока экземпляр ещё обслуживает остаточный трафик, он учитывает принятую работу в общем сроке. Когда приём закрыт, нельзя начинать операцию, которую процесс заведомо не успеет безопасно обработать. Возможность повторить запрос на другом экземпляре зависит от маршрутизации и смысла действия; её проверяют отдельно.
Что означает drain для HTTP-запросов
Drain — это завершение уже принятой работы после ограничения новой. Для HTTP важно различать соединение и запрос: по одному соединению клиент может отправлять несколько запросов, а отдельный запрос способен выполняться долго. Простое закрытие порта не описывает судьбу того, что уже передано обработчикам.
Используйте предусмотренный сервером механизм корректного завершения и выясните его границы. Например, метод Server.Shutdown в Go закрывает слушающие сокеты и простаивающие соединения, затем ожидает завершения активных. Ему можно передать ограниченный срок через контекст. При этом основная программа должна дождаться результата Shutdown, а не закончиться сразу после возврата функции обслуживания сервера. Ошибка по истечении контекста сама по себе не означает, что все обработчики уже остановлены.
Не отменяйте все рабочие контексты в момент начала остановки, если выбранная политика разрешает запросам завершиться. Отмена нужна на согласованной границе. Проверьте также, что тайм-ауты прокси и клиента не короче допустимого ожидания приложения: клиент может уже потерять ответ, пока сервер считает свою обработку штатной.
Закрытие соединения не доказывает отмену бизнес-действия. Если запись уже зафиксирована, а ответ не дошёл, результат для клиента остаётся неопределённым. Надёжный идентификатор операции и проверка существующего результата позволяют разбирать такой случай без слепого повторного создания заказа.
Долгие соединения требуют собственного правила
WebSocket, поток событий, длительная выгрузка и большой файл не укладываются в предположение «ещё немного подождём, и запросов не останется». Соединение может быть рассчитано на часы. Для каждого такого режима задайте, как клиент узнает о завершении и что произойдёт при повторном подключении.
Общий метод остановки сервера не обязательно управляет всеми соединениями. В той же документации Go указано, что Shutdown не закрывает и не ожидает соединения, переданные приложению через механизм hijack, включая WebSocket. Их завершением приложение занимается отдельно. Для другого сервера нужно проверить именно его контракт, не переносить поведение одной библиотеки на все технологии.
Предусмотрите ограниченное время уведомления и закрытия, сохранение необходимого состояния и допустимое продолжение. Если протокол поддерживает восстановление потока с позиции, испытайте этот механизм. Если не поддерживает, явно опишите повторную загрузку или повторное действие. Наличие автоматического переподключения ещё не гарантирует отсутствие пропущенных событий и повторных команд.
Проверьте, что клиент после уведомления не создаёт бесконечный поток новых соединений к тому же уходящему экземпляру. Расписание повторов и выбор доступного адресата должны соответствовать устройству системы.
Корректная остановка воркера начинается с получения задач
Для воркера сначала прекращают поступление новой работы, затем разбирают уже полученную. Не нужно ждать опустошения всей общей очереди: другие экземпляры продолжают её обрабатывать, а новые сообщения могут поступать постоянно. Контрольная область — задачи, за которые отвечает останавливаемый исполнитель.
В документации RabbitMQ об отмене потребителя есть важное уточнение: отмена подписки прекращает будущую отправку этому потребителю, но уже отправленные сообщения могут ещё находиться в пути. Сама отмена не возвращает такие сообщения в очередь. Поэтому обработчик должен учитывать доставленное до отмены и дождаться предусмотренного подтверждения её выполнения.
Нужно видеть не только число работающих обработчиков, но и локально полученные сообщения, которые ещё ждут своей очереди. Большой предварительно полученный запас способен сделать остановку намного длиннее, чем ожидает команда. Для него заранее выбирают: выполнить в оставшееся время либо безопасно вернуть на повторную обработку по правилам конкретного брокера.
Не допускайте, чтобы логика автоматического восстановления соединения снова зарегистрировала потребителя во время завершения процесса. Режим остановки должен быть общим для подписки, внутренних таймеров и обработки повторного подключения. Иначе воркер будет получать новые задания именно тогда, когда команда считает его очередь закрытой.
Ack, commit и lease не означают одно и то же
Подтверждение сообщения сообщает брокеру о принятом результате обработки. Его отправляют в точке, соответствующей договорённости о задаче: после надёжного сохранения результата либо другой явно предусмотренной передачи ответственности. Подтверждать все полученные сообщения только ради быстрого выхода нельзя.
Согласно правилам подтверждений RabbitMQ, при ручном режиме неподтверждённые доставки возвращаются в очередь после закрытия соответствующего канала или соединения. Однако бизнес-действие могло уже выполниться до потери подтверждения. Поэтому повторное получение требует защиты от дублей и сверки результата. Отправка ack сама по себе не создаёт общей транзакции между брокером, базой и внешним поставщиком.
Если обработчик использует commit позиции чтения, отдельно проверьте смысл продвигаемой позиции. Нельзя объявлять завершённым диапазон, внутри которого осталась необработанная запись. При параллельном выполнении порядок окончания задач может отличаться от порядка их получения; механизм фиксации должен это учитывать.
В системах с временной арендой задачи, lease, нужно поддерживать её действительность, пока продолжается разрешённая работа. Близкий пример — период невидимости сообщения Amazon SQS: после его истечения неудалённое сообщение снова становится доступным для получения. Продление этого срока и подтверждение завершения — разные действия. Такой механизм также не отменяет необходимости обработки повторов.
Не освобождайте задачу для другого исполнителя, пока прежний ещё способен изменить её результат. Если остановка наступила во время внешнего вызова, сохраните идентификатор операции и неопределённое состояние. Следующий исполнитель сначала выясняет исход, а затем продолжает предусмотренный процесс.
Назначьте ограниченный срок и порядок закрытия ресурсов
Без конечного срока одна зависшая операция может удерживать старую версию бесконечно. Слишком короткий срок превращает корректную остановку в регулярное принудительное завершение. Выбирайте бюджет по измеренной длительности операций и времени закрытия ресурсов, а для исключительных долгих задач проектируйте продолжение или передачу.
Согласуйте пределы менеджера процессов, контейнера, приложения и внешних клиентов. Внутренний срок приложения должен оставлять время на финальные действия до внешнего принудительного завершения. При этом нельзя выдавать каждой последовательной стадии полный общий бюджет: ожидания складываются.
- Остановите источники новой работы и подтвердите включение режима завершения.
- Дождитесь разрешённых запросов и задач в пределах их оставшегося времени.
- Зафиксируйте результаты, контрольные точки и известные неопределённые операции.
- После зависимых обработчиков закройте соединения с базой, брокером и другими ресурсами.
- Ограничьте финальную отправку диагностических данных и завершите основной процесс.
Повторное уведомление об остановке не должно обнулять счётчик времени или запускать второй независимый цикл очистки. Проверьте также ошибки самой процедуры: недоступность брокера при подтверждении, отказ записи контрольной точки, зависшую отправку журнала. Для каждой ситуации нужен установленный исход, а не безусловная отметка об успехе.
Что учитывать, если приложение работает в Kubernetes
Kubernetes здесь служит проверяемым примером; внедрять его ради graceful shutdown не требуется. Аналогичные вопросы нужно задать о менеджере процессов и балансировщике, которые уже обслуживают сайт.
В описании завершения Pod указано: при обычной остановке настроенный preStop выполняется до отправки сигнала остановки процессу контейнера. Его время входит в общий terminationGracePeriodSeconds. После окончания периода оставшиеся процессы могут быть завершены принудительно. Фактический сигнал следует сверить с конфигурацией среды и образом, а не предполагать, что любое приложение уже умеет его обрабатывать.
Изменение EndpointSlice происходит параллельно локальному завершению. В обычной конфигурации конечные точки завершающегося Pod получают terminating=true и ready=false. В документации EndpointSlice оговорены исключения: при spec.publishNotReadyAddresses=true поле ready остаётся истинным; прокси также могут направлять трафик к конечным точкам с serving=true и terminating=true, если все доступные уже завершаются. Это не глобальный барьер прекращения запросов: учитывайте настройки Service, поведение прокси и задержку распространения состояния на своём маршруте.
Если preStop тратит время на паузу, у приложения остаётся меньше времени после сигнала. Пауза может быть частью проверенной процедуры, но не является универсальным доказательством завершённого drain. Не рассчитывайте на аварийные продления срока как на штатный запас. При нескольких контейнерах отдельно проверьте порядок остановки зависимостей: нельзя заранее выключать компонент, необходимый для завершения основной работы.
Учебный таймлайн: как распределить 60 секунд
Допустим, команда выделила на остановку одного экземпляра 60 секунд. Это условный пример, не рекомендуемое значение для любого сайта. Другой экземпляр уже проверен и обслуживает трафик. В испытании подготовительная стадия занимает пять секунд, а все принятые операции укладываются в оставленный им предел.
| Момент от начала | Действие в учебной процедуре | Что проверяется |
|---|---|---|
| 0–5 секунд | Режим завершения, вывод из маршрутизации, прекращение получения задач | Остаточные поступления учитываются; пять секунд не считаются глобальной гарантией |
| 5–50 секунд | Ожидание разрешённых запросов и задач, завершение долгих соединений | У каждой принятой операции установлен результат или путь продолжения |
| 50–55 секунд | Закрытие ресурсов и ограниченная отправка диагностики | Основной процесс завершается после зависимых обработчиков |
| 55–60 секунд | Запас до внешнего предела | В штатном прогоне процесс уже завершён; запас не расходуется новой работой |
Расчёт: 5 секунд подготовки + 45 секунд завершения + 5 секунд финальных действий = 55 секунд. До внешнего предела остаётся 60 − 55 = 5 секунд. Если первые пять секунд занимает preStop, после него остаётся 55 секунд общего периода, а не новые 60.
Если обработка не укладывается в отметку 50 секунд, запускается заранее испытанный сценарий прекращения или передачи незавершённой работы. Нельзя сначала обнаружить превышение, а затем решить подтвердить все сообщения. Фактические длительности и исходы фиксируются; при неприемлемых потерях или дублях следующий плановый перезапуск приостанавливают до исправления процедуры.
Как проверить завершение, включая принудительный сценарий
Испытайте процедуру на согласованной среде с безопасными данными. Нужны запросы в разные моменты остановки, длительная операция, задача перед подтверждением и уже полученное сообщение, ещё не переданное обработчику. Один пустой сервер почти всегда останавливается красиво и мало рассказывает о сохранности реальной работы.
В отдельном испытании предусмотрите операцию, которая превышает срок. Проверьте, как система обозначает принудительное завершение, когда становится доступна повторная обработка и не создаётся ли лишний бизнес-эффект. Не переносите этот эксперимент на рабочие платежи или настоящие сообщения покупателям. Возможность безопасного повтора должна подтверждаться результатом, а не только настройкой очереди.
- Новая работа перестаёт назначаться уходящему экземпляру в ожидаемых условиях.
- Принятые операции имеют подтверждённые результаты либо зарегистрированный путь продолжения.
- Повторная доставка не создаёт повторного заказа, списания или другого недопустимого эффекта.
- Незавершённые действия не отмечаются успешными ради чистого журнала остановки.
- Оставшиеся экземпляры выдерживают поток, а новые проходят проверку готовности.
- Известны полное время завершения, случаи превышения и причины принудительной остановки.
Сверяйте идентификаторы операций и их состояние до и после перезапуска. Суммарного количества строк недостаточно: потерянный заказ и лишний дубль могут взаимно скрыться в счётчике. Для неопределённых внешних действий назначьте ответственного за сверку. Успешный выход процесса не означает, что все последствия уже проверены.
Что включить в задачу подрядчику
Попросите описать источники работы, механизм режима завершения, оставшееся время каждой стадии и действия при превышении. Приложите результаты испытаний с запросами и задачами, которые находились в работе. В качестве результата принимайте воспроизводимую процедуру с понятными ограничениями, а не только увеличенный тайм-аут остановки.
Периодическое наблюдение может пропустить короткий сбой между проверками, поэтому его результаты сопоставляют с журналом развёртывания и внутренними данными.
Подключите SiteGuard для внешнего HTTP-контроля доступности важных страниц во время плановых перезапусков.