본문 바로가기
개발/개발공부

Golang Gin Graceful restart, stop / golang, go, 고랭, 고, 언어

by 치킨개발자 2023. 2. 16.
728x90

Graceful restart, stop은 gin framework에서만 사용하는 것이 아니라, 모든 프로세스에 적용이 가능한 작업이다.

Graceful은 말그대로 우아한 종료, 재시작의 의미인다.

프로그래밍적으로 보자면 Network의 관점에서 보면 될 것 같다.

  - client에서 server로 요청이 들어와서 server에서 아직 처리 중 일때

  - client와 server간 keep-alive나 websocket같은 protocol로 연결이 유지되고 있을 때

앞선 상황에서 서버가 강제로 종료되거나 재시작 된다면 client입장에서는 정상적인 response를 받을 수 없게 된다.

이럴 때 필요한 것이 graceful restart, stop이다.

Golang Gin Graceful restart, stop


package main

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

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "Welcome Gin Server")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	
	select {
	case <-ctx.Done():
		log.Println("timeout of 5 seconds.")
	}
	log.Println("Server exiting")
}

(https://gin-gonic.com/docs/examples/graceful-restart-or-stop/)

gin-gonic 페이지에서 볼 수 있는 기본적인 예시를 가져와서 테스트해봤다.

코드를 보면 router부분에서

time.Sleep(5 * time.Second)

를 통해 http요청이 들어왔을 시 5초 뒤에 응답을 보내도록 되어있고,

syscall.SIGINT, syscall.SIGTERM

과 같이 SIGINT, SIGTERM을 프로세스에서 받게되면 (kubernetes container lifecycle을 공부할 때도 봤던)

하단의 context를 통해 5초간 대기를 했다가 서버가 종료되도록 되어있다.

SIGKILL에 대한 처리를 하지 않은 것은 SIGKILL은 graceful restart, stop이 아니라 강제종료를 의미하기 때문이다.

 

실제로 서버를 실행하고 SIGINT시그널에 해당하는 Ctrl+C or Command+C 를 눌러보면 graceful stop을 확인할 수 있다.

% go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
^C2023/02/16 22:15:00 Shutdown Server ...
2023/02/16 22:15:05 timeout of 5 seconds.
2023/02/16 22:15:05 Server exiting

결과를 보면 22:15:00에 Shutdown이 시작되고 5초뒤인 22:15:05에 종료가 되는 것을 볼 수 있다.

Gin-gonic graceful stop 순서

해당 코드에서 User1이 서버가 종료시그널을 받기 직전 요청을 보냈다면 graceful stop시간 내에 처리된 response을 받을 수 있다.

User2는 graceful stop동안 요청을 보냈기 때문에 아예 서버에 접속이 안될 것이다.

 

하지만 shutdown time이 5초보다 작은 시간으로 설정되어있다면 User1의 요청이 다 처리되기 전에 서버가 종료되어 User1도 response을 못받게 될 케이스가 발생 가능하다.

이런 케이스를 고려하여 down time에 대해 잘 설정하는 것이 중요하다.

그리고 각 SIGINT, SIGTERM과 같은 os signal을 구분하여 거기에 맞는 graceful stop logic을 가져갈 수 도 있다.

 

아예 따로 크게 다뤄야하는 부분이지만 gin-gonic에서 살짝 테스트를 해봤다. 이건 따로 또 공부를 해야겠다.

반응형

댓글