본문 바로가기

소프트웨어-이야기/인프라

[쿠버네티스] uWsgi Graceful Shutdown

발단

회사 시스템에서 API 요청이 비정상적으로 종료되는 오류가 있어서 원인을 분석하게 되었다. 그러다 이 오류는 배포 타이밍과 비슷한 패턴으로 발생한다는 것을 발견하게 되었다. 즉, 이 문제는 Pod이 죽을 때, uWsgi 컨테이너가 Graceful Shutdown을 하고있지 않아서 발생한 문제였다.

이번 글에서는 이 경험에서 학습했던 uWsgi의 특징과 쿠버네티스 파드의 종료 방식에 대해서 정리해보고자 한다. 

 

Graceful Shutdown이란?

프로그램이 종료될 때, 최대한 사이드 이펙트 없이 로직을 잘 처리하고 종료하는 것을 말한다.

예를 들어, 열심히 PPT를 작성하던 도중에 파워포인트가 꺼지는 모습을 상상해보자. 이 때, 아무런 조치없이 프로그램이 종료되고, 이전의 작업 내용이 유실된다면 이는 Hard Shutdown된 상황으로 볼 수 있다. 그러나 프로그램이 종료되기 직전에 PPT를 자동저장해준다면, 이 상황을 Graceful Shutdown한 상황으로 해석할 수 있다.

The Twelve-Factor App 방법론에서는 지속가능한 소프트웨어를 위하여 폐기가능 (Disposability)한 시스템을 구성해야한다고 말한다. 그리고 소프트웨어의 안정성을 높이기 위하여 graceful shutdown이 필요하다고 말한다. 이에 더해 프로세스가 갑작스러운 하드웨어 문제에 의해 죽는 상황이 발생하더라도, 문제가 없는 견고한 프로그램을 만들어야한다고도 말한다. 즉, PPT를 만들다가 갑자기 전기가 나가서 컴퓨터가 종료되는 상황이 발생하더라도, 프로그래머는 이에 준비해야한다는 것이다. 

 

파드 종료 처리

쿠버네티스는 Pod이 종료될 때, 프로세스들이 정상 종료될 수 있도록 컨테이너의 프로세스에 SIGTERM 시그널을 보낸다. 그리고 terminationGracePeriodSeconds에 설정된 시간 이후에는 SIGKILL을 보내어 프로세스를 모두 강제종료한다. terminationGracePeriodSeconds 기본값은 30초이다. 

 

SIGTREM 요약짤

 

 

파드 종료 프로세스 

  1. 파드 종료 시, preStop 훅이 실행된다.
  2. preStop 종료 후, SIGTREM 시그널을 프로세스에 전달한다.
  3. terminationGracePeriodSeconds에 지정한 시간이 지나면, SIGKILL 시그널을 프로세스에 전달하여 프로세스들을 강제종료한다.
    • preStop 훅이 terminationGracePeriodSeconds 시간보다 오래 걸리는 경우, 2초의 임의시간을 더 부여한다. 그러나 이 이상 소요되면 프로세스가 강제종료된다.
    • 때문에 preStop 소요시간이 terminationGracePeriodSeconds 설정시간을 넘지 않도록 적절하게 값을 조정해야한다. 

 

uWsgi에서 Graceful Shutdown 지원하기

uWsgi에서는 graceful shutdown을 지원하지 않는다. 그래서 uWsgi는 SIGTERM 시그널을 받았을 때, 프로세스가 정상적으로 종료될 수 있도록 대기할 수 없다. graceful reloads를 하거나, 즉시 종료시키는 수준의 옵션만 제공한다. 

그러나 쿠버네티스 플랫폼을 사용하고 있다면 컨테이너 라이프사이클 prestop hook을 활용하여, uWsgi GracefulShutdown을 우회하여 구현할 수 있다. preStop 훅은 컨테이너가 Terminatd 상태에 들어가기 전에 명령문이 실행된다.

preStop 훅은 프로세스에 SIGTERM을 보내기 전에 실행된다. 때문에 만약 프로그램에서 SIGTERM 시그널에 대한 Graceful Shutdown을 지원하지 않는 경우, preStrop으로 이를 우회해서 구현할 수 있다. 

 

예시

아래의 예시는 쿠버네티스 컨테이너가 종료되기 전에, sleep을 주어 nginx의 잔여 프로세스가 마무리될 수 있는 시간을 부여하는 것을 의미한다. 이는 배포와 복구 시간을 지연시킨다는 사이드이펙트를 일으키기 때문에 아주 좋은 방법으로 보기는 어렵다. 그러나 서비스 다운타임을 줄인다는 측면에서 uWsgi를 사용하는 입장에서는 의미있는 방식이다.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sleep","5"]

 

참고