Библиотека Go (Golang) разработчика
🛑 Graceful Shutdown: Хватит обрывать запросы при деплое
Знакомая ситуация: вы катите новую версию микросервиса в Kubernetes, и в этот момент графики в Grafana на пару секунд краснеют от 502/503 ошибок. А потом всё снова хорошо.
Что произошло? Оркестратор убил старый под, а ваш Go-сервис моментально схлопнулся, оборвав на полуслове десятки текущих HTTP-запросов, сломав пару транзакций к базе и бросив недокачанные файлы.
Как это работает в здоровом проде?
Когда Kubernetes хочет обновить под, он не стреляет ему в голову сразу. Он посылает вежливый сигнал SIGTERM и дает время (обычно 30 секунд), чтобы собрать вещи.
Мы должны перехватить этот сигнал и сказать нашему HTTP-серверу: "Новые запросы не принимай, а те, что уже в работе - спокойно доделывай".
Как это написать:
Забудьте про ручное создание make(chan os.Signal). Используйте signal.NotifyContext.
func main() {
// 1. Создаем контекст, который отменится при SIGINT (Ctrl+C) или SIGTERM (Docker/K8s)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
srv := &http.Server{Addr: ":8080", Handler: mux}
// 2. Запускаем сервер в отдельной горутине!
go func() {
// http.ErrServerClosed возвращается при успешном Shutdown, это не ошибка
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Критическая ошибка сервера: %v", err)
}
}()
// 3. Блокируемся и ждем сигнала от ОС
<-ctx.Done()
log.Println("Получен сигнал завершения. Тушим свет...")
// 4. Создаем контекст с таймаутом для Shutdown
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 5. Плавно останавливаем сервер (ждет завершения текущих хендлеров)
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("Сервер не смог остановиться плавно: %v", err)
}
// 6. Здесь закрываем коннекты к БД, RabbitMQ, сбрасываем кэши в Redis
// db.Close()
log.Println("Сервер успешно завершил работу.")
}
🔥 Нюансы для Senior-ов:
• Обязательно ставьте таймаут на Shutdown(). Если клиент с медленным интернетом качает от вас гигабайтный файл, srv.Shutdown будет ждать его вечно. В итоге K8s устанет ждать свои 30 секунд и жестко убьет под через SIGKILL (kill -9). Контролируйте таймаут сами (Шаг 4).
• Порядок важен. Сначала останавливаем HTTP-сервер (чтобы перестали прилетать новые задачи), и только потом закрываем пул соединений с БД. Если сделать наоборот, текущие HTTP-запросы упадут с ошибкой sql: database is closed.
#golang #architecture #kubernetes #bestpractices #devops
👉 @golang_lib