FiberSeniorCoding

Как реализовать graceful shutdown в Fiber?

Graceful shutdown в Fiber реализуется через os/signal: перехватываем SIGTERM/SIGINT, вызываем app.ShutdownWithContext(ctx) с дедлайном, чтобы дать обработчикам время завершить текущие запросы перед остановкой.

Зачем нужен graceful shutdown

При получении SIGTERM (Kubernetes pod eviction, rolling deploy) сервер должен: прекратить принимать новые соединения, дать текущим запросам завершиться (обычно 15–30 секунд), закрыть пул БД и другие ресурсы. Без этого — обрезанные HTTP-ответы и потерянные транзакции.

Базовая реализация

package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New(fiber.Config{
		IdleTimeout: 5 * time.Second,
	})

	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("hello")
	})

	// Запускаем сервер в горутине
	go func() {
		if err := app.Listen(":3000"); err != nil {
			log.Printf("server stopped: %v", err)
		}
	}()

	// Ждём SIGINT или SIGTERM
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("shutting down server...")

	// Даём 30 секунд на завершение текущих запросов
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	if err := app.ShutdownWithContext(ctx); err != nil {
		log.Printf("shutdown error: %v", err)
	}
	log.Println("server exited")
}

Graceful shutdown с cleanup ресурсов

func main() {
	dbPool := setupDB()
	redisClient := setupRedis()

	app := fiber.New()
	registerRoutes(app, dbPool, redisClient)

	go func() {
		if err := app.Listen(":3000"); err != nil {
			log.Println(err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 1. Остановить приём новых соединений
	if err := app.ShutdownWithContext(ctx); err != nil {
		log.Printf("http shutdown: %v", err)
	}

	// 2. Закрыть ресурсы после остановки сервера
	dbPool.Close()
	if err := redisClient.Close(); err != nil {
		log.Printf("redis close: %v", err)
	}
	log.Println("cleanup done")
}

Использование app.Shutdown() (без context)

Метод app.Shutdown() ждёт завершения всех активных соединений без таймаута. В продакшене предпочтительнее ShutdownWithContext с разумным дедлайном — иначе зависший хендлер заблокирует завершение процесса навсегда.

Kubernetes: terminationGracePeriodSeconds

Согласуйте таймаут shutdown с terminationGracePeriodSeconds в Pod spec. Обычная схема:

spec:
  terminationGracePeriodSeconds: 60  # k8s даёт 60 сек
  containers:
    - name: api
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sleep", "5"]  # ждём снятия из endpoints

Внутри приложения таймаут ShutdownWithContext должен быть меньше terminationGracePeriodSeconds (например, 45 секунд).

Подводные камни

  • Закрытие ресурсов до Shutdown: если закрыть БД-пул до app.Shutdown(), хендлеры активных запросов получат ошибки соединения.
  • Нет таймаута на Shutdown: app.Shutdown() без context блокируется навсегда при зависших WebSocket-соединениях.
  • SIGKILL не перехватывается: signal.Notify не ловит SIGKILL; Kubernetes посылает SIGKILL после terminationGracePeriodSeconds — убедитесь, что cleanup укладывается в дедлайн.
  • Prefork и shutdown: в prefork-режиме app.Shutdown() должен вызываться в каждом дочернем процессе; мастер-процесс Fiber делает это автоматически, но кастомный cleanup в fiber.IsChild() нужен явно.
  • Горутины с незавершёнными задачами: Shutdown закрывает HTTP-слой, но фоновые goroutine продолжают работать; используйте sync.WaitGroup для их ожидания.
  • Отсутствие preStop hook: без preStop в Kubernetes запросы могут приходить на pod, который уже начал завершение — добавьте sleep 5 в preStop.

Common mistakes

  • Давать ответ про graceful shutdown в Fiber только на уровне определения, не показывая поведение в реальном приложении.
  • Игнорировать границы ответственности вокруг темы «graceful shutdown в Fiber»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
  • Не связывать graceful shutdown в Fiber с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.

What the interviewer is testing

  • Точно объясняет, что именно делает graceful shutdown в Fiber и где это используется в Go-коде.
  • Связывает graceful shutdown в Fiber с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
  • Не изобретает API и опирается на реальные контракты официальной документации.

Sources

Related topics